schemars/generate.rs
1/*!
2JSON Schema generator and settings.
3
4This module is useful if you want more control over how the schema generated than the [`schema_for!`] macro gives you.
5There are two main types in this module:
6* [`SchemaSettings`], which defines what JSON Schema features should be used when generating schemas (for example, how `Option`s should be represented).
7* [`SchemaGenerator`], which manages the generation of a schema document.
8*/
9
10use crate::consts::meta_schemas;
11use crate::Schema;
12use crate::_alloc_prelude::*;
13use crate::{transform::*, JsonSchema};
14use alloc::collections::{BTreeMap, BTreeSet};
15use core::{any::Any, fmt::Debug};
16use dyn_clone::DynClone;
17use serde::Serialize;
18use serde_json::{Map as JsonMap, Value};
19
20type CowStr = alloc::borrow::Cow<'static, str>;
21
22/// Settings to customize how Schemas are generated.
23///
24/// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added.
25/// If you rely on generated schemas conforming to draft 2020-12, consider using the
26/// [`SchemaSettings::draft2020_12()`] method.
27#[derive(Debug, Clone)]
28#[non_exhaustive]
29#[allow(clippy::struct_excessive_bools)]
30pub struct SchemaSettings {
31 /// A JSON pointer to the expected location of referenceable subschemas within the resulting
32 /// root schema.
33 ///
34 /// A single leading `#` and/or single trailing `/` are ignored.
35 ///
36 /// Defaults to `"/$defs"`.
37 pub definitions_path: CowStr,
38 /// The URI of the meta-schema describing the structure of the generated schemas.
39 ///
40 /// Defaults to [`meta_schemas::DRAFT2020_12`] (`https://json-schema.org/draft/2020-12/schema`).
41 pub meta_schema: Option<CowStr>,
42 /// A list of [`Transform`]s that get applied to generated root schemas.
43 ///
44 /// Defaults to an empty vec (no transforms).
45 pub transforms: Vec<Box<dyn GenTransform>>,
46 /// Inline all subschemas instead of using references.
47 ///
48 /// Some references may still be generated in schemas for recursive types.
49 ///
50 /// Defaults to `false`.
51 pub inline_subschemas: bool,
52 /// Whether the generated schemas should describe how types are serialized or *de*serialized.
53 ///
54 /// Defaults to `Contract::Deserialize`.
55 pub contract: Contract,
56 /// Whether to include enum variant names in their schema's `title` when using the [untagged
57 /// enum representation](https://serde.rs/enum-representations.html#untagged).
58 ///
59 /// This setting is respected by `#[derive(JsonSchema)]` on enums, but manual implementations
60 /// of `JsonSchema` may ignore this setting.
61 ///
62 /// Defaults to `false`.
63 pub untagged_enum_variant_titles: bool,
64}
65
66impl Default for SchemaSettings {
67 /// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12),
68 /// but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added.
69 /// If you rely on generated schemas conforming to draft 2020-12, consider using [`SchemaSettings::draft2020_12()`] instead.
70 fn default() -> SchemaSettings {
71 SchemaSettings::draft2020_12()
72 }
73}
74
75impl SchemaSettings {
76 /// Creates `SchemaSettings` that conform to [JSON Schema Draft 7](https://json-schema.org/specification-links#draft-7).
77 #[must_use]
78 pub fn draft07() -> SchemaSettings {
79 SchemaSettings {
80 definitions_path: "/definitions".into(),
81 meta_schema: Some(meta_schemas::DRAFT07.into()),
82 transforms: vec![
83 Box::new(ReplaceUnevaluatedProperties),
84 Box::new(RemoveRefSiblings),
85 Box::new(ReplacePrefixItems),
86 ],
87 inline_subschemas: false,
88 contract: Contract::Deserialize,
89 untagged_enum_variant_titles: false,
90 }
91 }
92
93 /// Creates `SchemaSettings` that conform to [JSON Schema 2019-09](https://json-schema.org/specification-links#draft-2019-09-(formerly-known-as-draft-8)).
94 #[must_use]
95 pub fn draft2019_09() -> SchemaSettings {
96 SchemaSettings {
97 definitions_path: "/$defs".into(),
98 meta_schema: Some(meta_schemas::DRAFT2019_09.into()),
99 transforms: vec![Box::new(ReplacePrefixItems)],
100 inline_subschemas: false,
101 contract: Contract::Deserialize,
102 untagged_enum_variant_titles: false,
103 }
104 }
105
106 /// Creates `SchemaSettings` that conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12).
107 #[must_use]
108 pub fn draft2020_12() -> SchemaSettings {
109 SchemaSettings {
110 definitions_path: "/$defs".into(),
111 meta_schema: Some(meta_schemas::DRAFT2020_12.into()),
112 transforms: Vec::new(),
113 inline_subschemas: false,
114 contract: Contract::Deserialize,
115 untagged_enum_variant_titles: false,
116 }
117 }
118
119 /// Creates `SchemaSettings` that conform to [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.4.md#schema).
120 #[must_use]
121 pub fn openapi3() -> SchemaSettings {
122 SchemaSettings {
123 definitions_path: "/components/schemas".into(),
124 meta_schema: Some(meta_schemas::OPENAPI3.into()),
125 transforms: vec![
126 Box::new(ReplaceUnevaluatedProperties),
127 Box::new(ReplaceBoolSchemas {
128 skip_additional_properties: true,
129 }),
130 Box::new(AddNullable::default()),
131 Box::new(RemoveRefSiblings),
132 Box::new(SetSingleExample),
133 Box::new(ReplaceConstValue),
134 Box::new(ReplacePrefixItems),
135 ],
136 inline_subschemas: false,
137 contract: Contract::Deserialize,
138 untagged_enum_variant_titles: false,
139 }
140 }
141
142 /// Modifies the `SchemaSettings` by calling the given function.
143 ///
144 /// # Example
145 /// ```
146 /// use schemars::generate::{SchemaGenerator, SchemaSettings};
147 ///
148 /// let settings = SchemaSettings::default().with(|s| {
149 /// s.meta_schema = None;
150 /// s.inline_subschemas = true;
151 /// });
152 /// let generator = settings.into_generator();
153 /// ```
154 #[must_use]
155 pub fn with(mut self, configure_fn: impl FnOnce(&mut Self)) -> Self {
156 configure_fn(&mut self);
157 self
158 }
159
160 /// Appends the given transform to the list of [transforms](SchemaSettings::transforms) for
161 /// these `SchemaSettings`.
162 #[must_use]
163 pub fn with_transform(mut self, transform: impl Transform + Clone + 'static + Send) -> Self {
164 self.transforms.push(Box::new(transform));
165 self
166 }
167
168 /// Creates a new [`SchemaGenerator`] using these settings.
169 #[must_use]
170 pub fn into_generator(self) -> SchemaGenerator {
171 SchemaGenerator::new(self)
172 }
173
174 /// Updates the settings to generate schemas describing how types are **deserialized**.
175 #[must_use]
176 pub fn for_deserialize(mut self) -> Self {
177 self.contract = Contract::Deserialize;
178 self
179 }
180
181 /// Updates the settings to generate schemas describing how types are **serialized**.
182 #[must_use]
183 pub fn for_serialize(mut self) -> Self {
184 self.contract = Contract::Serialize;
185 self
186 }
187}
188
189/// A setting to specify whether generated schemas should describe how types are serialized or
190/// *de*serialized.
191///
192/// This enum is marked as `#[non_exhaustive]` to reserve space to introduce further variants
193/// in future.
194#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
195#[allow(missing_docs)]
196#[non_exhaustive]
197pub enum Contract {
198 Deserialize,
199 Serialize,
200}
201
202impl Contract {
203 /// Returns true if `self` is the `Deserialize` contract.
204 #[must_use]
205 pub fn is_deserialize(&self) -> bool {
206 self == &Contract::Deserialize
207 }
208
209 /// Returns true if `self` is the `Serialize` contract.
210 #[must_use]
211 pub fn is_serialize(&self) -> bool {
212 self == &Contract::Serialize
213 }
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
217struct SchemaUid(CowStr, Contract);
218
219/// The main type used to generate JSON Schemas.
220///
221/// # Example
222/// ```
223/// use schemars::{JsonSchema, SchemaGenerator};
224///
225/// #[derive(JsonSchema)]
226/// struct MyStruct {
227/// foo: i32,
228/// }
229///
230/// let generator = SchemaGenerator::default();
231/// let schema = generator.into_root_schema_for::<MyStruct>();
232/// ```
233#[derive(Debug)]
234pub struct SchemaGenerator {
235 settings: SchemaSettings,
236 definitions: JsonMap<String, Value>,
237 pending_schema_ids: BTreeSet<SchemaUid>,
238 schema_id_to_name: BTreeMap<SchemaUid, CowStr>,
239 used_schema_names: BTreeSet<CowStr>,
240 // It's unlikely that `root_schema_id_stack` will ever contain more than one item, but it is
241 // possible, e.g. if a `json_schema()` implementation calls `generator.root_schema_for<...>()`
242 root_schema_id_stack: Vec<SchemaUid>,
243}
244
245impl Default for SchemaGenerator {
246 fn default() -> Self {
247 SchemaSettings::default().into_generator()
248 }
249}
250
251impl Clone for SchemaGenerator {
252 fn clone(&self) -> Self {
253 Self {
254 settings: self.settings.clone(),
255 definitions: self.definitions.clone(),
256 pending_schema_ids: BTreeSet::new(),
257 schema_id_to_name: BTreeMap::new(),
258 used_schema_names: BTreeSet::new(),
259 root_schema_id_stack: Vec::new(),
260 }
261 }
262}
263
264impl From<SchemaSettings> for SchemaGenerator {
265 fn from(settings: SchemaSettings) -> Self {
266 settings.into_generator()
267 }
268}
269
270impl SchemaGenerator {
271 /// Creates a new `SchemaGenerator` using the given settings.
272 #[must_use]
273 pub fn new(settings: SchemaSettings) -> SchemaGenerator {
274 SchemaGenerator {
275 settings,
276 definitions: JsonMap::new(),
277 pending_schema_ids: BTreeSet::new(),
278 schema_id_to_name: BTreeMap::new(),
279 used_schema_names: BTreeSet::new(),
280 root_schema_id_stack: Vec::new(),
281 }
282 }
283
284 /// Borrows the [`SchemaSettings`] being used by this `SchemaGenerator`.
285 ///
286 /// # Example
287 /// ```
288 /// use schemars::SchemaGenerator;
289 ///
290 /// let generator = SchemaGenerator::default();
291 /// let settings = generator.settings();
292 ///
293 /// assert_eq!(settings.inline_subschemas, false);
294 /// ```
295 #[must_use]
296 pub fn settings(&self) -> &SchemaSettings {
297 &self.settings
298 }
299
300 /// Generates a JSON Schema for the type `T`, and returns either the schema itself or a `$ref`
301 /// schema referencing `T`'s schema.
302 ///
303 /// If `T` is not [inlined](JsonSchema::inline_schema), this will add `T`'s schema to
304 /// this generator's definitions, and return a `$ref` schema referencing that schema.
305 /// Otherwise, this method behaves identically to [`JsonSchema::json_schema`].
306 ///
307 /// If `T`'s schema depends on any [non-inlined](JsonSchema::inline_schema) schemas, then
308 /// this method will add them to the `SchemaGenerator`'s schema definitions.
309 pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
310 struct FindRef {
311 schema: Schema,
312 name_to_be_inserted: Option<CowStr>,
313 }
314
315 /// Non-generic inner function to improve compile times.
316 fn find_ref(
317 this: &mut SchemaGenerator,
318 uid: &SchemaUid,
319 inline_schema: bool,
320 schema_name: fn() -> CowStr,
321 ) -> Option<FindRef> {
322 let return_ref = !inline_schema
323 && (!this.settings.inline_subschemas || this.pending_schema_ids.contains(uid));
324
325 if !return_ref {
326 return None;
327 }
328
329 if this.root_schema_id_stack.last() == Some(uid) {
330 return Some(FindRef {
331 schema: Schema::new_ref("#".to_owned()),
332 name_to_be_inserted: None,
333 });
334 }
335
336 let name = this.schema_id_to_name.get(uid).cloned().unwrap_or_else(|| {
337 let base_name = schema_name();
338 let mut name = CowStr::Borrowed("");
339
340 if this.used_schema_names.contains(base_name.as_ref()) {
341 for i in 2.. {
342 name = format!("{base_name}{i}").into();
343 if !this.used_schema_names.contains(&name) {
344 break;
345 }
346 }
347 } else {
348 name = base_name;
349 }
350
351 this.used_schema_names.insert(name.clone());
352 this.schema_id_to_name.insert(uid.clone(), name.clone());
353 name
354 });
355
356 let reference = format!(
357 "#{}/{}",
358 this.definitions_path_stripped(),
359 crate::encoding::encode_ref_name(&name)
360 );
361
362 Some(FindRef {
363 schema: Schema::new_ref(reference),
364 name_to_be_inserted: (!this.definitions().contains_key(name.as_ref()))
365 .then_some(name),
366 })
367 }
368
369 let uid = self.schema_uid::<T>();
370
371 let Some(FindRef {
372 schema,
373 name_to_be_inserted,
374 }) = find_ref(self, &uid, T::inline_schema(), T::schema_name)
375 else {
376 return self.json_schema_internal::<T>(&uid);
377 };
378
379 if let Some(name) = name_to_be_inserted {
380 self.insert_new_subschema_for::<T>(name, &uid);
381 }
382
383 schema
384 }
385
386 fn insert_new_subschema_for<T: ?Sized + JsonSchema>(&mut self, name: CowStr, uid: &SchemaUid) {
387 // TODO: If we've already added a schema for T with the "opposite" contract, then check
388 // whether the new schema is identical. If so, re-use the original for both contracts.
389
390 let dummy = false.into();
391 // insert into definitions BEFORE calling json_schema to avoid infinite recursion
392 self.definitions.insert(name.clone().into(), dummy);
393
394 let schema = self.json_schema_internal::<T>(uid);
395
396 self.definitions.insert(name.into(), schema.to_value());
397 }
398
399 /// Borrows the collection of all [non-inlined](JsonSchema::inline_schema) schemas that
400 /// have been generated.
401 ///
402 /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the
403 /// values are the schemas themselves.
404 #[must_use]
405 pub fn definitions(&self) -> &JsonMap<String, Value> {
406 &self.definitions
407 }
408
409 /// Mutably borrows the collection of all [non-inlined](JsonSchema::inline_schema)
410 /// schemas that have been generated.
411 ///
412 /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the
413 /// values are the schemas themselves.
414 #[must_use]
415 pub fn definitions_mut(&mut self) -> &mut JsonMap<String, Value> {
416 &mut self.definitions
417 }
418
419 /// Returns the collection of all [non-inlined](JsonSchema::inline_schema) schemas that
420 /// have been generated, leaving an empty `Map` in its place.
421 ///
422 /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the
423 /// values are the schemas themselves.
424 ///
425 /// To apply this generator's [transforms](SchemaSettings::transforms) to each of the returned
426 /// schemas, set `apply_transforms` to `true`.
427 pub fn take_definitions(&mut self, apply_transforms: bool) -> JsonMap<String, Value> {
428 let mut definitions = core::mem::take(&mut self.definitions);
429
430 if apply_transforms {
431 for schema in definitions.values_mut().flat_map(<&mut Schema>::try_from) {
432 self.apply_transforms(schema);
433 }
434 }
435
436 definitions
437 }
438
439 /// Returns an iterator over the [transforms](SchemaSettings::transforms) being used by this
440 /// `SchemaGenerator`.
441 pub fn transforms_mut(&mut self) -> impl Iterator<Item = &mut dyn GenTransform> {
442 self.settings.transforms.iter_mut().map(Box::as_mut)
443 }
444
445 /// Generates a JSON Schema for the type `T`.
446 ///
447 /// If `T`'s schema depends on any [non-inlined](JsonSchema::inline_schema) schemas, then
448 /// this method will include them in the returned `Schema` at the [definitions
449 /// path](SchemaSettings::definitions_path) (by default `"$defs"`).
450 pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
451 let schema_uid = self.schema_uid::<T>();
452 self.root_schema_id_stack.push(schema_uid.clone());
453
454 let mut schema = self.json_schema_internal::<T>(&schema_uid);
455
456 let object = schema.ensure_object();
457
458 object
459 .entry("title")
460 .or_insert_with(|| T::schema_name().into());
461
462 if let Some(meta_schema) = self.settings.meta_schema.as_deref() {
463 object.insert("$schema".into(), meta_schema.into());
464 }
465
466 self.add_definitions(object, self.definitions.clone());
467 self.apply_transforms(&mut schema);
468
469 self.root_schema_id_stack.pop();
470
471 schema
472 }
473
474 /// Consumes `self` and generates a JSON Schema for the type `T`.
475 ///
476 /// If `T`'s schema depends on any [non-inlined](JsonSchema::inline_schema) schemas, then
477 /// this method will include them in the returned `Schema` at the [definitions
478 /// path](SchemaSettings::definitions_path) (by default `"$defs"`).
479 #[must_use]
480 pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> Schema {
481 let schema_uid = self.schema_uid::<T>();
482 self.root_schema_id_stack.push(schema_uid.clone());
483
484 let mut schema = self.json_schema_internal::<T>(&schema_uid);
485
486 let object = schema.ensure_object();
487
488 object
489 .entry("title")
490 .or_insert_with(|| T::schema_name().into());
491
492 if let Some(meta_schema) = core::mem::take(&mut self.settings.meta_schema) {
493 object.insert("$schema".into(), meta_schema.into());
494 }
495
496 let definitions = self.take_definitions(false);
497 self.add_definitions(object, definitions);
498 self.apply_transforms(&mut schema);
499
500 schema
501 }
502
503 /// Generates a JSON Schema for the given example value.
504 ///
505 /// If the value implements [`JsonSchema`], then prefer using the
506 /// [`root_schema_for()`](Self::root_schema_for()) function which will generally produce a
507 /// more precise schema, particularly when the value contains any enums.
508 ///
509 /// If the `Serialize` implementation of the value decides to fail, this will return an [`Err`].
510 pub fn root_schema_for_value<T: ?Sized + Serialize>(
511 &mut self,
512 value: &T,
513 ) -> Result<Schema, serde_json::Error> {
514 let mut schema = value.serialize(crate::ser::Serializer {
515 generator: self,
516 include_title: true,
517 })?;
518
519 let object = schema.ensure_object();
520
521 if let Ok(example) = serde_json::to_value(value) {
522 object.insert("examples".into(), vec![example].into());
523 }
524
525 if let Some(meta_schema) = self.settings.meta_schema.as_deref() {
526 object.insert("$schema".into(), meta_schema.into());
527 }
528
529 self.add_definitions(object, self.definitions.clone());
530 self.apply_transforms(&mut schema);
531
532 Ok(schema)
533 }
534
535 /// Consumes `self` and generates a JSON Schema for the given example value.
536 ///
537 /// If the value implements [`JsonSchema`], then prefer using the
538 /// [`into_root_schema_for()!`](Self::into_root_schema_for()) function which will generally
539 /// produce a more precise schema, particularly when the value contains any enums.
540 ///
541 /// If the `Serialize` implementation of the value decides to fail, this will return an [`Err`].
542 pub fn into_root_schema_for_value<T: ?Sized + Serialize>(
543 mut self,
544 value: &T,
545 ) -> Result<Schema, serde_json::Error> {
546 let mut schema = value.serialize(crate::ser::Serializer {
547 generator: &mut self,
548 include_title: true,
549 })?;
550
551 let object = schema.ensure_object();
552
553 if let Ok(example) = serde_json::to_value(value) {
554 object.insert("examples".into(), vec![example].into());
555 }
556
557 if let Some(meta_schema) = core::mem::take(&mut self.settings.meta_schema) {
558 object.insert("$schema".into(), meta_schema.into());
559 }
560
561 let definitions = self.take_definitions(false);
562 self.add_definitions(object, definitions);
563 self.apply_transforms(&mut schema);
564
565 Ok(schema)
566 }
567
568 /// Returns a reference to the [contract](SchemaSettings::contract) for the settings on this
569 /// `SchemaGenerator`.
570 ///
571 /// This specifies whether generated schemas describe serialize or *de*serialize behaviour.
572 #[must_use]
573 pub fn contract(&self) -> &Contract {
574 &self.settings.contract
575 }
576
577 fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, uid: &SchemaUid) -> Schema {
578 let did_add = self.pending_schema_ids.insert(uid.clone());
579
580 let schema = T::json_schema(self);
581
582 if did_add {
583 self.pending_schema_ids.remove(uid);
584 }
585
586 schema
587 }
588
589 fn add_definitions(
590 &mut self,
591 schema_object: &mut JsonMap<String, Value>,
592 mut definitions: JsonMap<String, Value>,
593 ) {
594 if definitions.is_empty() {
595 return;
596 }
597
598 let pointer = self.definitions_path_stripped();
599 let Some(target) = json_pointer_mut(schema_object, pointer, true) else {
600 return;
601 };
602
603 target.append(&mut definitions);
604 }
605
606 fn apply_transforms(&mut self, schema: &mut Schema) {
607 for transform in self.transforms_mut() {
608 transform.transform(schema);
609 }
610 }
611
612 /// Returns `self.settings.definitions_path` as a plain JSON pointer to the definitions object,
613 /// i.e. without a leading '#' or trailing '/'
614 fn definitions_path_stripped(&self) -> &str {
615 let path = &self.settings.definitions_path;
616 let path = path.strip_prefix('#').unwrap_or(path);
617 path.strip_suffix('/').unwrap_or(path)
618 }
619
620 fn schema_uid<T: ?Sized + JsonSchema>(&self) -> SchemaUid {
621 SchemaUid(T::schema_id(), self.settings.contract.clone())
622 }
623}
624
625fn json_pointer_mut<'a>(
626 mut object: &'a mut JsonMap<String, Value>,
627 pointer: &str,
628 create_if_missing: bool,
629) -> Option<&'a mut JsonMap<String, Value>> {
630 use serde_json::map::Entry;
631
632 let pointer = pointer.strip_prefix('/')?;
633 if pointer.is_empty() {
634 return Some(object);
635 }
636
637 for mut segment in pointer.split('/') {
638 let replaced: String;
639 if segment.contains('~') {
640 replaced = segment.replace("~1", "/").replace("~0", "~");
641 segment = &replaced;
642 }
643
644 let next_value = match object.entry(segment) {
645 Entry::Occupied(o) => o.into_mut(),
646 Entry::Vacant(v) if create_if_missing => v.insert(Value::Object(JsonMap::new())),
647 Entry::Vacant(_) => return None,
648 };
649
650 object = next_value.as_object_mut()?;
651 }
652
653 Some(object)
654}
655
656/// A [`Transform`] which implements additional traits required to be included in a
657/// [`SchemaSettings`].
658///
659/// You will rarely need to use this trait directly as it is automatically implemented for any type
660/// which implements all of:
661/// - [`Transform`]
662/// - [`std::any::Any`] (implemented for all `'static` types)
663/// - [`std::clone::Clone`]
664/// - [`std::marker::Send`]
665///
666/// # Example
667/// ```
668/// use schemars::transform::Transform;
669/// use schemars::generate::GenTransform;
670///
671/// #[derive(Debug, Clone)]
672/// struct MyTransform;
673///
674/// impl Transform for MyTransform {
675/// fn transform(&mut self, schema: &mut schemars::Schema) {
676/// todo!()
677/// }
678/// }
679///
680/// let v: &dyn GenTransform = &MyTransform;
681/// assert!(v.is::<MyTransform>());
682/// ```
683pub trait GenTransform: Transform + DynClone + Any + Send {
684 #[deprecated = "Only to support pre-1.86 rustc"]
685 #[doc(hidden)]
686 fn _as_any(&self) -> &dyn Any;
687
688 #[deprecated = "Only to support pre-1.86 rustc"]
689 #[doc(hidden)]
690 fn _as_any_mut(&mut self) -> &mut dyn Any;
691
692 #[deprecated = "Only to support pre-1.86 rustc"]
693 #[doc(hidden)]
694 fn _into_any(self: Box<Self>) -> Box<dyn Any>;
695}
696
697#[allow(deprecated, clippy::used_underscore_items)]
698impl dyn GenTransform {
699 /// Returns `true` if the inner transform is of type `T`.
700 #[must_use]
701 pub fn is<T: Transform + Clone + Any + Send>(&self) -> bool {
702 self._as_any().is::<T>()
703 }
704
705 /// Returns some reference to the inner transform if it is of type `T`, or
706 /// `None` if it isn't.
707 ///
708 /// # Example
709 /// To remove a specific transform from an instance of `SchemaSettings`:
710 /// ```
711 /// use schemars::generate::SchemaSettings;
712 /// use schemars::transform::ReplaceBoolSchemas;
713 ///
714 /// let mut settings = SchemaSettings::openapi3();
715 /// let original_len = settings.transforms.len();
716 ///
717 /// settings.transforms.retain(|t| !t.is::<ReplaceBoolSchemas>());
718 ///
719 /// assert_eq!(settings.transforms.len(), original_len - 1);
720 /// ```
721 #[must_use]
722 pub fn downcast_ref<T: Transform + Clone + Any + Send>(&self) -> Option<&T> {
723 self._as_any().downcast_ref::<T>()
724 }
725
726 /// Returns some mutable reference to the inner transform if it is of type `T`, or
727 /// `None` if it isn't.
728 ///
729 /// # Example
730 /// To modify a specific transform in an instance of `SchemaSettings`:
731 /// ```
732 /// use schemars::generate::SchemaSettings;
733 /// use schemars::transform::ReplaceBoolSchemas;
734 ///
735 /// let mut settings = SchemaSettings::openapi3();
736 /// for t in &mut settings.transforms {
737 /// if let Some(replace_bool_schemas) = t.downcast_mut::<ReplaceBoolSchemas>() {
738 /// replace_bool_schemas.skip_additional_properties = false;
739 /// }
740 /// }
741 /// ```
742 #[must_use]
743 pub fn downcast_mut<T: Transform + Clone + Any + Send>(&mut self) -> Option<&mut T> {
744 self._as_any_mut().downcast_mut::<T>()
745 }
746
747 /// Attempts to downcast the box to a concrete type.
748 ///
749 /// If the inner transform is not of type `T`, this returns `self` wrapped in an `Err` so that
750 /// it can still be used.
751 #[allow(clippy::missing_panics_doc)] // should never panic - `is()` ensures that downcast succeeds
752 pub fn downcast<T: Transform + Clone + Any + Send>(
753 self: Box<Self>,
754 ) -> Result<Box<T>, Box<Self>> {
755 if self.is::<T>() {
756 Ok(self._into_any().downcast().unwrap())
757 } else {
758 Err(self)
759 }
760 }
761}
762
763dyn_clone::clone_trait_object!(GenTransform);
764
765impl<T> GenTransform for T
766where
767 T: Transform + Clone + Any + Send,
768{
769 fn _as_any(&self) -> &dyn Any {
770 self
771 }
772
773 fn _as_any_mut(&mut self) -> &mut dyn Any {
774 self
775 }
776
777 fn _into_any(self: Box<Self>) -> Box<dyn Any> {
778 self
779 }
780}
781
782impl Debug for Box<dyn GenTransform> {
783 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
784 #[allow(clippy::used_underscore_items)]
785 self._debug_type_name(f)
786 }
787}
788
789fn _assert_send() {
790 fn assert<T: Send>() {}
791
792 assert::<SchemaSettings>();
793 assert::<SchemaGenerator>();
794}