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}