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