1/*!
2Contains the [`Visitor`] trait, used to recursively modify a constructed schema and its subschemas.
34Sometimes you may want to apply a change to a schema, as well as all schemas contained within it.
5The easiest way to achieve this is by defining a type that implements [`Visitor`].
6All methods of `Visitor` have a default implementation that makes no change but recursively visits all subschemas.
7When overriding one of these methods, you will *usually* want to still call this default implementation.
89# Example
10To add a custom property to all schemas:
11```
12use schemars::schema::SchemaObject;
13use schemars::visit::{Visitor, visit_schema_object};
1415pub struct MyVisitor;
1617impl Visitor for MyVisitor {
18 fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
19 // First, make our change to this schema
20 schema.extensions.insert("my_property".to_string(), serde_json::json!("hello world"));
2122 // Then delegate to default implementation to visit any subschemas
23 visit_schema_object(self, schema);
24 }
25}
26```
27*/
28use crate::schema::{RootSchema, Schema, SchemaObject, SingleOrVec};
2930/// Trait used to recursively modify a constructed schema and its subschemas.
31pub trait Visitor {
32/// Override this method to modify a [`RootSchema`] and (optionally) its subschemas.
33 ///
34 /// When overriding this method, you will usually want to call the [`visit_root_schema`] function to visit subschemas.
35fn visit_root_schema(&mut self, root: &mut RootSchema) {
36 visit_root_schema(self, root)
37 }
3839/// Override this method to modify a [`Schema`] and (optionally) its subschemas.
40 ///
41 /// When overriding this method, you will usually want to call the [`visit_schema`] function to visit subschemas.
42fn visit_schema(&mut self, schema: &mut Schema) {
43 visit_schema(self, schema)
44 }
4546/// Override this method to modify a [`SchemaObject`] and (optionally) its subschemas.
47 ///
48 /// When overriding this method, you will usually want to call the [`visit_schema_object`] function to visit subschemas.
49fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
50 visit_schema_object(self, schema)
51 }
52}
5354/// Visits all subschemas of the [`RootSchema`].
55pub fn visit_root_schema<V: Visitor + ?Sized>(v: &mut V, root: &mut RootSchema) {
56 v.visit_schema_object(&mut root.schema);
57 visit_map_values(v, &mut root.definitions);
58}
5960/// Visits all subschemas of the [`Schema`].
61pub fn visit_schema<V: Visitor + ?Sized>(v: &mut V, schema: &mut Schema) {
62if let Schema::Object(schema) = schema {
63 v.visit_schema_object(schema)
64 }
65}
6667/// Visits all subschemas of the [`SchemaObject`].
68pub fn visit_schema_object<V: Visitor + ?Sized>(v: &mut V, schema: &mut SchemaObject) {
69if let Some(sub) = &mut schema.subschemas {
70 visit_vec(v, &mut sub.all_of);
71 visit_vec(v, &mut sub.any_of);
72 visit_vec(v, &mut sub.one_of);
73 visit_box(v, &mut sub.not);
74 visit_box(v, &mut sub.if_schema);
75 visit_box(v, &mut sub.then_schema);
76 visit_box(v, &mut sub.else_schema);
77 }
7879if let Some(arr) = &mut schema.array {
80 visit_single_or_vec(v, &mut arr.items);
81 visit_box(v, &mut arr.additional_items);
82 visit_box(v, &mut arr.contains);
83 }
8485if let Some(obj) = &mut schema.object {
86 visit_map_values(v, &mut obj.properties);
87 visit_map_values(v, &mut obj.pattern_properties);
88 visit_box(v, &mut obj.additional_properties);
89 visit_box(v, &mut obj.property_names);
90 }
91}
9293fn visit_box<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Box<Schema>>) {
94if let Some(s) = target {
95 v.visit_schema(s)
96 }
97}
9899fn visit_vec<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Vec<Schema>>) {
100if let Some(vec) = target {
101for s in vec {
102 v.visit_schema(s)
103 }
104 }
105}
106107fn visit_map_values<V: Visitor + ?Sized>(v: &mut V, target: &mut crate::Map<String, Schema>) {
108for s in target.values_mut() {
109 v.visit_schema(s)
110 }
111}
112113fn visit_single_or_vec<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<SingleOrVec<Schema>>) {
114match target {
115None => {}
116Some(SingleOrVec::Single(s)) => v.visit_schema(s),
117Some(SingleOrVec::Vec(vec)) => {
118for s in vec {
119 v.visit_schema(s)
120 }
121 }
122 }
123}
124125/// This visitor will replace all boolean JSON Schemas with equivalent object schemas.
126///
127/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support booleans as schemas.
128#[derive(Debug, Clone)]
129pub struct ReplaceBoolSchemas {
130/// When set to `true`, a schema's `additionalProperties` property will not be changed from a boolean.
131pub skip_additional_properties: bool,
132}
133134impl Visitor for ReplaceBoolSchemas {
135fn visit_schema(&mut self, schema: &mut Schema) {
136 visit_schema(self, schema);
137138if let Schema::Bool(b) = *schema {
139*schema = Schema::Bool(b).into_object().into()
140 }
141 }
142143fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
144if self.skip_additional_properties {
145if let Some(obj) = &mut schema.object {
146if let Some(ap) = &obj.additional_properties {
147if let Schema::Bool(_) = ap.as_ref() {
148let additional_properties = obj.additional_properties.take();
149 visit_schema_object(self, schema);
150 schema.object().additional_properties = additional_properties;
151152return;
153 }
154 }
155 }
156 }
157158 visit_schema_object(self, schema);
159 }
160}
161162/// This visitor will restructure JSON Schema objects so that the `$ref` property will never appear alongside any other properties.
163///
164/// This is useful for dialects of JSON Schema (e.g. Draft 7) that do not support other properties alongside `$ref`.
165#[derive(Debug, Clone)]
166pub struct RemoveRefSiblings;
167168impl Visitor for RemoveRefSiblings {
169fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
170 visit_schema_object(self, schema);
171172if let Some(reference) = schema.reference.take() {
173if schema == &SchemaObject::default() {
174 schema.reference = Some(reference);
175 } else {
176let ref_schema = Schema::new_ref(reference);
177let all_of = &mut schema.subschemas().all_of;
178match all_of {
179Some(vec) => vec.push(ref_schema),
180None => *all_of = Some(vec![ref_schema]),
181 }
182 }
183 }
184 }
185}
186187/// This visitor will remove the `examples` schema property and (if present) set its first value as the `example` property.
188///
189/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property.
190#[derive(Debug, Clone)]
191pub struct SetSingleExample {
192/// When set to `true`, the `examples` property will not be removed, but its first value will still be copied to `example`.
193pub retain_examples: bool,
194}
195196impl Visitor for SetSingleExample {
197fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
198 visit_schema_object(self, schema);
199200let first_example = schema.metadata.as_mut().and_then(|m| {
201if self.retain_examples {
202 m.examples.first().cloned()
203 } else {
204 m.examples.drain(..).next()
205 }
206 });
207208if let Some(example) = first_example {
209 schema.extensions.insert("example".to_owned(), example);
210 }
211 }
212}