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}