schemars/
gen.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::schema::*;
11use crate::{visit::*, JsonSchema, Map};
12use dyn_clone::DynClone;
13use serde::Serialize;
14use std::borrow::Cow;
15use std::collections::HashMap;
16use std::{any::Any, collections::HashSet, fmt::Debug};
17
18/// Settings to customize how Schemas are generated.
19///
20/// The default settings currently conform to [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added.
21/// If you require your generated schemas to conform to draft 7, consider using the [`draft07`](#method.draft07) method.
22#[derive(Debug, Clone)]
23#[non_exhaustive]
24pub struct SchemaSettings {
25    /// If `true`, schemas for [`Option<T>`](Option) will include a `nullable` property.
26    ///
27    /// This is not part of the JSON Schema spec, but is used in Swagger/OpenAPI schemas.
28    ///
29    /// Defaults to `false`.
30    pub option_nullable: bool,
31    /// If `true`, schemas for [`Option<T>`](Option) will have `null` added to their [`type`](../schema/struct.SchemaObject.html#structfield.instance_type).
32    ///
33    /// Defaults to `true`.
34    pub option_add_null_type: bool,
35    /// A JSON pointer to the expected location of referenceable subschemas within the resulting root schema.
36    ///
37    /// Defaults to `"#/definitions/"`.
38    pub definitions_path: String,
39    /// The URI of the meta-schema describing the structure of the generated schemas.
40    ///
41    /// Defaults to `"http://json-schema.org/draft-07/schema#"`.
42    pub meta_schema: Option<String>,
43    /// A list of visitors that get applied to all generated root schemas.
44    pub visitors: Vec<Box<dyn GenVisitor>>,
45    /// Inline all subschemas instead of using references.
46    ///
47    /// Some references may still be generated in schemas for recursive types.
48    ///
49    /// Defaults to `false`.
50    pub inline_subschemas: bool,
51}
52
53impl Default for SchemaSettings {
54    fn default() -> SchemaSettings {
55        SchemaSettings::draft07()
56    }
57}
58
59impl SchemaSettings {
60    /// Creates `SchemaSettings` that conform to [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7).
61    pub fn draft07() -> SchemaSettings {
62        SchemaSettings {
63            option_nullable: false,
64            option_add_null_type: true,
65            definitions_path: "#/definitions/".to_owned(),
66            meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()),
67            visitors: vec![Box::new(RemoveRefSiblings)],
68            inline_subschemas: false,
69        }
70    }
71
72    /// Creates `SchemaSettings` that conform to [JSON Schema 2019-09](https://json-schema.org/specification-links.html#2019-09-formerly-known-as-draft-8).
73    pub fn draft2019_09() -> SchemaSettings {
74        SchemaSettings {
75            option_nullable: false,
76            option_add_null_type: true,
77            definitions_path: "#/definitions/".to_owned(),
78            meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
79            visitors: Vec::default(),
80            inline_subschemas: false,
81        }
82    }
83
84    /// Creates `SchemaSettings` that conform to [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject).
85    pub fn openapi3() -> SchemaSettings {
86        SchemaSettings {
87            option_nullable: true,
88            option_add_null_type: false,
89            definitions_path: "#/components/schemas/".to_owned(),
90            meta_schema: Some(
91                "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema"
92                    .to_owned(),
93            ),
94            visitors: vec![
95                Box::new(RemoveRefSiblings),
96                Box::new(ReplaceBoolSchemas {
97                    skip_additional_properties: true,
98                }),
99                Box::new(SetSingleExample {
100                    retain_examples: false,
101                }),
102            ],
103            inline_subschemas: false,
104        }
105    }
106
107    /// Modifies the `SchemaSettings` by calling the given function.
108    ///
109    /// # Example
110    /// ```
111    /// use schemars::gen::{SchemaGenerator, SchemaSettings};
112    ///
113    /// let settings = SchemaSettings::default().with(|s| {
114    ///     s.option_nullable = true;
115    ///     s.option_add_null_type = false;
116    /// });
117    /// let gen = settings.into_generator();
118    /// ```
119    pub fn with(mut self, configure_fn: impl FnOnce(&mut Self)) -> Self {
120        configure_fn(&mut self);
121        self
122    }
123
124    /// Appends the given visitor to the list of [visitors](SchemaSettings::visitors) for these `SchemaSettings`.
125    pub fn with_visitor(mut self, visitor: impl Visitor + Debug + Clone + 'static) -> Self {
126        self.visitors.push(Box::new(visitor));
127        self
128    }
129
130    /// Creates a new [`SchemaGenerator`] using these settings.
131    pub fn into_generator(self) -> SchemaGenerator {
132        SchemaGenerator::new(self)
133    }
134}
135
136/// The main type used to generate JSON Schemas.
137///
138/// # Example
139/// ```
140/// use schemars::{JsonSchema, gen::SchemaGenerator};
141///
142/// #[derive(JsonSchema)]
143/// struct MyStruct {
144///     foo: i32,
145/// }
146///
147/// let gen = SchemaGenerator::default();
148/// let schema = gen.into_root_schema_for::<MyStruct>();
149/// ```
150#[derive(Debug, Default)]
151pub struct SchemaGenerator {
152    settings: SchemaSettings,
153    definitions: Map<String, Schema>,
154    pending_schema_ids: HashSet<Cow<'static, str>>,
155    schema_id_to_name: HashMap<Cow<'static, str>, String>,
156    used_schema_names: HashSet<String>,
157}
158
159impl Clone for SchemaGenerator {
160    fn clone(&self) -> Self {
161        Self {
162            settings: self.settings.clone(),
163            definitions: self.definitions.clone(),
164            pending_schema_ids: HashSet::new(),
165            schema_id_to_name: HashMap::new(),
166            used_schema_names: HashSet::new(),
167        }
168    }
169}
170
171impl From<SchemaSettings> for SchemaGenerator {
172    fn from(settings: SchemaSettings) -> Self {
173        settings.into_generator()
174    }
175}
176
177impl SchemaGenerator {
178    /// Creates a new `SchemaGenerator` using the given settings.
179    pub fn new(settings: SchemaSettings) -> SchemaGenerator {
180        SchemaGenerator {
181            settings,
182            ..Default::default()
183        }
184    }
185
186    /// Borrows the [`SchemaSettings`] being used by this `SchemaGenerator`.
187    ///
188    /// # Example
189    /// ```
190    /// use schemars::gen::SchemaGenerator;
191    ///
192    /// let gen = SchemaGenerator::default();
193    /// let settings = gen.settings();
194    ///
195    /// assert_eq!(settings.option_add_null_type, true);
196    /// ```
197    pub fn settings(&self) -> &SchemaSettings {
198        &self.settings
199    }
200
201    #[deprecated = "This method no longer has any effect."]
202    pub fn make_extensible(&self, _schema: &mut SchemaObject) {}
203
204    #[deprecated = "Use `Schema::Bool(true)` instead"]
205    pub fn schema_for_any(&self) -> Schema {
206        Schema::Bool(true)
207    }
208
209    #[deprecated = "Use `Schema::Bool(false)` instead"]
210    pub fn schema_for_none(&self) -> Schema {
211        Schema::Bool(false)
212    }
213
214    /// Generates a JSON Schema for the type `T`, and returns either the schema itself or a `$ref` schema referencing `T`'s schema.
215    ///
216    /// If `T` is [referenceable](JsonSchema::is_referenceable), this will add `T`'s schema to this generator's definitions, and
217    /// return a `$ref` schema referencing that schema. Otherwise, this method behaves identically to [`JsonSchema::json_schema`].
218    ///
219    /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
220    /// add them to the `SchemaGenerator`'s schema definitions.
221    pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
222        let id = T::schema_id();
223        let return_ref = T::is_referenceable()
224            && (!self.settings.inline_subschemas || self.pending_schema_ids.contains(&id));
225
226        if return_ref {
227            let name = match self.schema_id_to_name.get(&id).cloned() {
228                Some(n) => n,
229                None => {
230                    let base_name = T::schema_name();
231                    let mut name = String::new();
232
233                    if self.used_schema_names.contains(&base_name) {
234                        for i in 2.. {
235                            name = format!("{}{}", base_name, i);
236                            if !self.used_schema_names.contains(&name) {
237                                break;
238                            }
239                        }
240                    } else {
241                        name = base_name;
242                    }
243
244                    self.used_schema_names.insert(name.clone());
245                    self.schema_id_to_name.insert(id.clone(), name.clone());
246                    name
247                }
248            };
249
250            let reference = format!("{}{}", self.settings.definitions_path, name);
251            if !self.definitions.contains_key(&name) {
252                self.insert_new_subschema_for::<T>(name, id);
253            }
254            Schema::new_ref(reference)
255        } else {
256            self.json_schema_internal::<T>(id)
257        }
258    }
259
260    fn insert_new_subschema_for<T: ?Sized + JsonSchema>(
261        &mut self,
262        name: String,
263        id: Cow<'static, str>,
264    ) {
265        let dummy = Schema::Bool(false);
266        // insert into definitions BEFORE calling json_schema to avoid infinite recursion
267        self.definitions.insert(name.clone(), dummy);
268
269        let schema = self.json_schema_internal::<T>(id);
270
271        self.definitions.insert(name, schema);
272    }
273
274    /// Borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated.
275    ///
276    /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
277    /// themselves.
278    pub fn definitions(&self) -> &Map<String, Schema> {
279        &self.definitions
280    }
281
282    /// Mutably borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated.
283    ///
284    /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
285    /// themselves.
286    pub fn definitions_mut(&mut self) -> &mut Map<String, Schema> {
287        &mut self.definitions
288    }
289
290    /// Returns the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated,
291    /// leaving an empty map in its place.
292    ///
293    /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
294    /// themselves.
295    pub fn take_definitions(&mut self) -> Map<String, Schema> {
296        std::mem::take(&mut self.definitions)
297    }
298
299    /// Returns an iterator over the [visitors](SchemaSettings::visitors) being used by this `SchemaGenerator`.
300    pub fn visitors_mut(&mut self) -> impl Iterator<Item = &mut dyn GenVisitor> {
301        self.settings.visitors.iter_mut().map(|v| v.as_mut())
302    }
303
304    /// Generates a root JSON Schema for the type `T`.
305    ///
306    /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
307    /// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
308    /// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
309    pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
310        let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object();
311        schema.metadata().title.get_or_insert_with(T::schema_name);
312        let mut root = RootSchema {
313            meta_schema: self.settings.meta_schema.clone(),
314            definitions: self.definitions.clone(),
315            schema,
316        };
317
318        for visitor in &mut self.settings.visitors {
319            visitor.visit_root_schema(&mut root)
320        }
321
322        root
323    }
324
325    /// Consumes `self` and generates a root JSON Schema for the type `T`.
326    ///
327    /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
328    /// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
329    pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
330        let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object();
331        schema.metadata().title.get_or_insert_with(T::schema_name);
332        let mut root = RootSchema {
333            meta_schema: self.settings.meta_schema,
334            definitions: self.definitions,
335            schema,
336        };
337
338        for visitor in &mut self.settings.visitors {
339            visitor.visit_root_schema(&mut root)
340        }
341
342        root
343    }
344
345    /// Generates a root JSON Schema for the given example value.
346    ///
347    /// If the value implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`root_schema_for()`](Self::root_schema_for())
348    /// function which will generally produce a more precise schema, particularly when the value contains any enums.
349    pub fn root_schema_for_value<T: ?Sized + Serialize>(
350        &mut self,
351        value: &T,
352    ) -> Result<RootSchema, serde_json::Error> {
353        let mut schema = value
354            .serialize(crate::ser::Serializer {
355                gen: self,
356                include_title: true,
357            })?
358            .into_object();
359
360        if let Ok(example) = serde_json::to_value(value) {
361            schema.metadata().examples.push(example);
362        }
363
364        let mut root = RootSchema {
365            meta_schema: self.settings.meta_schema.clone(),
366            definitions: self.definitions.clone(),
367            schema,
368        };
369
370        for visitor in &mut self.settings.visitors {
371            visitor.visit_root_schema(&mut root)
372        }
373
374        Ok(root)
375    }
376
377    /// Consumes `self` and generates a root JSON Schema for the given example value.
378    ///
379    /// If the value  implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`into_root_schema_for()!`](Self::into_root_schema_for())
380    /// function which will generally produce a more precise schema, particularly when the value contains any enums.
381    pub fn into_root_schema_for_value<T: ?Sized + Serialize>(
382        mut self,
383        value: &T,
384    ) -> Result<RootSchema, serde_json::Error> {
385        let mut schema = value
386            .serialize(crate::ser::Serializer {
387                gen: &mut self,
388                include_title: true,
389            })?
390            .into_object();
391
392        if let Ok(example) = serde_json::to_value(value) {
393            schema.metadata().examples.push(example);
394        }
395
396        let mut root = RootSchema {
397            meta_schema: self.settings.meta_schema,
398            definitions: self.definitions,
399            schema,
400        };
401
402        for visitor in &mut self.settings.visitors {
403            visitor.visit_root_schema(&mut root)
404        }
405
406        Ok(root)
407    }
408
409    /// Attemps to find the schema that the given `schema` is referencing.
410    ///
411    /// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers
412    /// to another schema in `self`'s schema definitions, the referenced schema will be returned. Otherwise, returns `None`.
413    ///
414    /// # Example
415    /// ```
416    /// use schemars::{JsonSchema, gen::SchemaGenerator};
417    ///
418    /// #[derive(JsonSchema)]
419    /// struct MyStruct {
420    ///     foo: i32,
421    /// }
422    ///
423    /// let mut gen = SchemaGenerator::default();
424    /// let ref_schema = gen.subschema_for::<MyStruct>();
425    ///
426    /// assert!(ref_schema.is_ref());
427    ///
428    /// let dereferenced = gen.dereference(&ref_schema);
429    ///
430    /// assert!(dereferenced.is_some());
431    /// assert!(!dereferenced.unwrap().is_ref());
432    /// assert_eq!(dereferenced, gen.definitions().get("MyStruct"));
433    /// ```
434    pub fn dereference<'a>(&'a self, schema: &Schema) -> Option<&'a Schema> {
435        match schema {
436            Schema::Object(SchemaObject {
437                reference: Some(ref schema_ref),
438                ..
439            }) => {
440                let definitions_path = &self.settings().definitions_path;
441                if schema_ref.starts_with(definitions_path) {
442                    let name = &schema_ref[definitions_path.len()..];
443                    self.definitions.get(name)
444                } else {
445                    None
446                }
447            }
448            _ => None,
449        }
450    }
451
452    fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, id: Cow<'static, str>) -> Schema {
453        struct PendingSchemaState<'a> {
454            gen: &'a mut SchemaGenerator,
455            id: Cow<'static, str>,
456            did_add: bool,
457        }
458
459        impl<'a> PendingSchemaState<'a> {
460            fn new(gen: &'a mut SchemaGenerator, id: Cow<'static, str>) -> Self {
461                let did_add = gen.pending_schema_ids.insert(id.clone());
462                Self { gen, id, did_add }
463            }
464        }
465
466        impl Drop for PendingSchemaState<'_> {
467            fn drop(&mut self) {
468                if self.did_add {
469                    self.gen.pending_schema_ids.remove(&self.id);
470                }
471            }
472        }
473
474        let pss = PendingSchemaState::new(self, id);
475        T::json_schema(pss.gen)
476    }
477}
478
479/// A [Visitor](Visitor) which implements additional traits required to be included in a [SchemaSettings].
480///
481/// You will rarely need to use this trait directly as it is automatically implemented for any type which implements all of:
482/// - [`Visitor`]
483/// - [`std::fmt::Debug`]
484/// - [`std::any::Any`] (implemented for all `'static` types)
485/// - [`std::clone::Clone`]
486///
487/// # Example
488/// ```
489/// use schemars::visit::Visitor;
490/// use schemars::gen::GenVisitor;
491///
492/// #[derive(Debug, Clone)]
493/// struct MyVisitor;
494///
495/// impl Visitor for MyVisitor { }
496///
497/// let v: &dyn GenVisitor = &MyVisitor;
498/// assert!(v.as_any().is::<MyVisitor>());
499/// ```
500pub trait GenVisitor: Visitor + Debug + DynClone + Any {
501    /// Upcasts this visitor into an `Any`, which can be used to inspect and manipulate it as its concrete type.
502    fn as_any(&self) -> &dyn Any;
503}
504
505dyn_clone::clone_trait_object!(GenVisitor);
506
507impl<T> GenVisitor for T
508where
509    T: Visitor + Debug + Clone + Any,
510{
511    fn as_any(&self) -> &dyn Any {
512        self
513    }
514}