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}