schemars/
visit.rs

1/*!
2Contains the [`Visitor`] trait, used to recursively modify a constructed schema and its subschemas.
3
4Sometimes 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.
8
9# Example
10To add a custom property to all schemas:
11```
12use schemars::schema::SchemaObject;
13use schemars::visit::{Visitor, visit_schema_object};
14
15pub struct MyVisitor;
16
17impl 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"));
21
22        // 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};
29
30/// 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.
35    fn visit_root_schema(&mut self, root: &mut RootSchema) {
36        visit_root_schema(self, root)
37    }
38
39    /// 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.
42    fn visit_schema(&mut self, schema: &mut Schema) {
43        visit_schema(self, schema)
44    }
45
46    /// 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.
49    fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
50        visit_schema_object(self, schema)
51    }
52}
53
54/// 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}
59
60/// Visits all subschemas of the [`Schema`].
61pub fn visit_schema<V: Visitor + ?Sized>(v: &mut V, schema: &mut Schema) {
62    if let Schema::Object(schema) = schema {
63        v.visit_schema_object(schema)
64    }
65}
66
67/// Visits all subschemas of the [`SchemaObject`].
68pub fn visit_schema_object<V: Visitor + ?Sized>(v: &mut V, schema: &mut SchemaObject) {
69    if 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    }
78
79    if 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    }
84
85    if 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}
92
93fn visit_box<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Box<Schema>>) {
94    if let Some(s) = target {
95        v.visit_schema(s)
96    }
97}
98
99fn visit_vec<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Vec<Schema>>) {
100    if let Some(vec) = target {
101        for s in vec {
102            v.visit_schema(s)
103        }
104    }
105}
106
107fn visit_map_values<V: Visitor + ?Sized>(v: &mut V, target: &mut crate::Map<String, Schema>) {
108    for s in target.values_mut() {
109        v.visit_schema(s)
110    }
111}
112
113fn visit_single_or_vec<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<SingleOrVec<Schema>>) {
114    match target {
115        None => {}
116        Some(SingleOrVec::Single(s)) => v.visit_schema(s),
117        Some(SingleOrVec::Vec(vec)) => {
118            for s in vec {
119                v.visit_schema(s)
120            }
121        }
122    }
123}
124
125/// 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.
131    pub skip_additional_properties: bool,
132}
133
134impl Visitor for ReplaceBoolSchemas {
135    fn visit_schema(&mut self, schema: &mut Schema) {
136        visit_schema(self, schema);
137
138        if let Schema::Bool(b) = *schema {
139            *schema = Schema::Bool(b).into_object().into()
140        }
141    }
142
143    fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
144        if self.skip_additional_properties {
145            if let Some(obj) = &mut schema.object {
146                if let Some(ap) = &obj.additional_properties {
147                    if let Schema::Bool(_) = ap.as_ref() {
148                        let additional_properties = obj.additional_properties.take();
149                        visit_schema_object(self, schema);
150                        schema.object().additional_properties = additional_properties;
151
152                        return;
153                    }
154                }
155            }
156        }
157
158        visit_schema_object(self, schema);
159    }
160}
161
162/// 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;
167
168impl Visitor for RemoveRefSiblings {
169    fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
170        visit_schema_object(self, schema);
171
172        if let Some(reference) = schema.reference.take() {
173            if schema == &SchemaObject::default() {
174                schema.reference = Some(reference);
175            } else {
176                let ref_schema = Schema::new_ref(reference);
177                let all_of = &mut schema.subschemas().all_of;
178                match all_of {
179                    Some(vec) => vec.push(ref_schema),
180                    None => *all_of = Some(vec![ref_schema]),
181                }
182            }
183        }
184    }
185}
186
187/// 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`.
193    pub retain_examples: bool,
194}
195
196impl Visitor for SetSingleExample {
197    fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
198        visit_schema_object(self, schema);
199
200        let first_example = schema.metadata.as_mut().and_then(|m| {
201            if self.retain_examples {
202                m.examples.first().cloned()
203            } else {
204                m.examples.drain(..).next()
205            }
206        });
207
208        if let Some(example) = first_example {
209            schema.extensions.insert("example".to_owned(), example);
210        }
211    }
212}