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