schemars/
schema.rs

1/*!
2JSON Schema types.
3*/
4
5use crate::_alloc_prelude::*;
6use ref_cast::{ref_cast_custom, RefCastCustom};
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9
10/// A JSON Schema.
11///
12/// This wraps a JSON [`Value`] that must be either an [object](Value::Object) or a
13/// [bool](Value::Bool).
14///
15/// A custom JSON schema can be created using the [`json_schema!`](crate::json_schema) macro:
16/// ```
17/// use schemars::{Schema, json_schema};
18///
19/// let my_schema: Schema = json_schema!({
20///     "type": ["object", "null"]
21/// });
22/// ```
23///
24/// Because a `Schema` is a thin wrapper around a `Value`, you can also use
25/// [`TryFrom::try_from`]/[`TryInto::try_into`] to create a `Schema` from an existing `Value`.
26/// This operation is fallible, because only [objects](Value::Object) and [bools](Value::Bool) can
27/// be converted in this way.
28///
29/// ```
30/// use schemars::{Schema, json_schema};
31/// use serde_json::json;
32///
33/// let json_object = json!({"type": ["object", "null"]});
34/// let object_schema: Schema = json_object.try_into().unwrap();
35///
36/// let json_bool = json!(true);
37/// let bool_schema: Schema = json_bool.try_into().unwrap();
38///
39/// let json_string = json!("This is neither an object nor a bool!");
40/// assert!(Schema::try_from(json_string).is_err());
41///
42/// // You can also convert a `&Value`/`&mut Value` to a `&Schema`/`&mut Schema` the same way:
43///
44/// let json_object = json!({"type": ["object", "null"]});
45/// let object_schema_ref: &Schema = (&json_object).try_into().unwrap();
46///
47/// let mut json_object = json!({"type": ["object", "null"]});
48/// let object_schema_mut: &mut Schema = (&mut json_object).try_into().unwrap();
49/// ```
50///
51/// Similarly, you can use [`From`]/[`Into`] to (infallibly) create a `Schema` from an existing
52/// [`Map<String, Value>`] or [`bool`].
53///
54/// ```
55/// use schemars::{Schema, json_schema};
56/// use serde_json::{Map, json};
57///
58/// let mut map = Map::new();
59/// map.insert("type".to_owned(), json!(["object", "null"]));
60/// let object_schema: Schema = map.into();
61///
62/// let bool_schema: Schema = true.into();
63/// ```
64#[derive(Debug, Clone, PartialEq, RefCastCustom)]
65#[repr(transparent)]
66pub struct Schema(Value);
67
68impl<'de> Deserialize<'de> for Schema {
69    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70    where
71        D: serde::Deserializer<'de>,
72    {
73        let value = Value::deserialize(deserializer)?;
74        Schema::validate(&value)?;
75        Ok(Schema(value))
76    }
77}
78
79impl Serialize for Schema {
80    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81    where
82        S: serde::Serializer,
83    {
84        ser::OrderedKeywordWrapper::from(&self.0).serialize(serializer)
85    }
86}
87
88impl PartialEq<bool> for Schema {
89    fn eq(&self, other: &bool) -> bool {
90        self.as_bool() == Some(*other)
91    }
92}
93
94impl PartialEq<Map<String, Value>> for Schema {
95    fn eq(&self, other: &Map<String, Value>) -> bool {
96        self.as_object() == Some(other)
97    }
98}
99
100impl PartialEq<Value> for Schema {
101    fn eq(&self, other: &Value) -> bool {
102        self.as_value() == other
103    }
104}
105
106impl PartialEq<Schema> for bool {
107    fn eq(&self, other: &Schema) -> bool {
108        other == self
109    }
110}
111
112impl PartialEq<Schema> for Map<String, Value> {
113    fn eq(&self, other: &Schema) -> bool {
114        other == self
115    }
116}
117
118impl PartialEq<Schema> for Value {
119    fn eq(&self, other: &Schema) -> bool {
120        other == self
121    }
122}
123
124impl Schema {
125    /// Creates a new schema object with a single string property `"$ref"`.
126    ///
127    /// The given reference string should be a URI reference. This will usually be a JSON Pointer
128    /// in [URI Fragment representation](https://tools.ietf.org/html/rfc6901#section-6).
129    #[must_use]
130    pub fn new_ref(reference: String) -> Self {
131        let mut map = Map::new();
132        map.insert("$ref".to_owned(), Value::String(reference));
133        Self(Value::Object(map))
134    }
135
136    /// Borrows the `Schema`'s underlying JSON value.
137    #[must_use]
138    pub fn as_value(&self) -> &Value {
139        &self.0
140    }
141
142    /// If the `Schema`'s underlying JSON value is a bool, returns the bool value.
143    #[must_use]
144    pub fn as_bool(&self) -> Option<bool> {
145        self.0.as_bool()
146    }
147
148    /// If the `Schema`'s underlying JSON value is an object, borrows the object as a `Map` of
149    /// properties.
150    #[must_use]
151    pub fn as_object(&self) -> Option<&Map<String, Value>> {
152        self.0.as_object()
153    }
154
155    /// If the `Schema`'s underlying JSON value is an object, mutably borrows the object as a `Map`
156    /// of properties.
157    #[must_use]
158    pub fn as_object_mut(&mut self) -> Option<&mut Map<String, Value>> {
159        self.0.as_object_mut()
160    }
161
162    pub(crate) fn try_to_object(self) -> Result<Map<String, Value>, bool> {
163        match self.0 {
164            Value::Object(m) => Ok(m),
165            Value::Bool(b) => Err(b),
166            _ => unreachable!(),
167        }
168    }
169
170    pub(crate) fn try_as_object_mut(&mut self) -> Result<&mut Map<String, Value>, bool> {
171        match &mut self.0 {
172            Value::Object(m) => Ok(m),
173            Value::Bool(b) => Err(*b),
174            _ => unreachable!(),
175        }
176    }
177
178    /// Returns the `Schema`'s underlying JSON value.
179    #[must_use]
180    pub fn to_value(self) -> Value {
181        self.0
182    }
183
184    /// Converts the `Schema` (if it wraps a bool value) into an equivalent object schema. Then
185    /// mutably borrows the object as a `Map` of properties.
186    ///
187    /// `true` is transformed into an empty schema `{}`, which successfully validates against all
188    /// possible values. `false` is transformed into the schema `{"not": {}}`, which does not
189    /// successfully validate against any value.
190    #[allow(clippy::missing_panics_doc)]
191    pub fn ensure_object(&mut self) -> &mut Map<String, Value> {
192        if let Some(b) = self.as_bool() {
193            let mut map = Map::new();
194            if !b {
195                map.insert("not".into(), Value::Object(Map::new()));
196            }
197            self.0 = Value::Object(map);
198        }
199
200        self.0
201            .as_object_mut()
202            .expect("Schema value should be of type Object.")
203    }
204
205    /// Inserts a property into the schema, replacing any previous value.
206    ///
207    /// If the schema wraps a bool value, it will first be converted into an equivalent object
208    /// schema.
209    ///
210    /// If the schema did not have this key present, `None` is returned.
211    ///
212    /// If the schema did have this key present, the value is updated, and the old value is
213    /// returned.
214    ///
215    /// # Example
216    /// ```
217    /// use schemars::json_schema;
218    /// use serde_json::json;
219    ///
220    /// let mut schema = json_schema!(true);
221    /// assert_eq!(schema.insert("type".to_owned(), "array".into()), None);
222    /// assert_eq!(schema.insert("type".to_owned(), "object".into()), Some(json!("array")));
223    ///
224    /// assert_eq!(schema, json_schema!({"type": "object"}));
225    /// ```
226    pub fn insert(&mut self, k: String, v: Value) -> Option<Value> {
227        self.ensure_object().insert(k, v)
228    }
229
230    /// If the `Schema`'s underlying JSON value is an object, gets a reference to that object's
231    /// value for the given key if it exists.
232    ///
233    /// This always returns `None` for bool schemas.
234    ///
235    /// # Example
236    /// ```
237    /// use schemars::json_schema;
238    /// use serde_json::json;
239    ///
240    /// let obj_schema = json_schema!({"type": "array"});
241    /// assert_eq!(obj_schema.get("type"), Some(&json!("array")));
242    /// assert_eq!(obj_schema.get("format"), None);
243    ///
244    /// let bool_schema = json_schema!(true);
245    /// assert_eq!(bool_schema.get("type"), None);
246    /// ```
247    #[must_use]
248    pub fn get<Q>(&self, key: &Q) -> Option<&Value>
249    where
250        String: core::borrow::Borrow<Q>,
251        Q: ?Sized + Ord + Eq + core::hash::Hash,
252    {
253        self.0.as_object().and_then(|o| o.get(key))
254    }
255
256    /// If the `Schema`'s underlying JSON value is an object, gets a mutable reference to that
257    /// object's value for the given key if it exists.
258    ///
259    /// This always returns `None` for bool schemas.
260    ///
261    /// # Example
262    /// ```
263    /// use schemars::json_schema;
264    /// use serde_json::{json, Value};
265    ///
266    /// let mut obj_schema = json_schema!({ "properties": {} });
267    /// if let Some(Value::Object(properties)) = obj_schema.get_mut("properties") {
268    ///     properties.insert("anything".to_owned(), true.into());
269    /// }
270    /// assert_eq!(obj_schema, json_schema!({ "properties": { "anything": true } }));
271    /// ```
272    #[must_use]
273    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Value>
274    where
275        String: core::borrow::Borrow<Q>,
276        Q: ?Sized + Ord + Eq + core::hash::Hash,
277    {
278        self.0.as_object_mut().and_then(|o| o.get_mut(key))
279    }
280
281    /// If the `Schema`'s underlying JSON value is an object, looks up a value within the schema
282    /// by a JSON Pointer.
283    ///
284    /// If the given pointer begins with a `#`, then the rest of the value is assumed to be in
285    /// "URI Fragment Identifier Representation", and will be percent-decoded accordingly.
286    ///
287    /// For more information on JSON Pointer, read [RFC6901](https://tools.ietf.org/html/rfc6901).
288    ///
289    /// This always returns `None` for bool schemas.
290    ///
291    /// # Example
292    /// ```
293    /// use schemars::json_schema;
294    /// use serde_json::json;
295    ///
296    /// let schema = json_schema!({
297    ///     "properties": {
298    ///         "anything": true
299    ///     },
300    ///     "$defs": {
301    ///         "🚀": true
302    ///     }
303    /// });
304    ///
305    /// assert_eq!(schema.pointer("/properties/anything").unwrap(), &json!(true));
306    /// assert_eq!(schema.pointer("#/$defs/%F0%9F%9A%80").unwrap(), &json!(true));
307    /// assert_eq!(schema.pointer("/does/not/exist"), None);
308    /// ```
309    #[must_use]
310    pub fn pointer(&self, pointer: &str) -> Option<&Value> {
311        if let Some(percent_encoded) = pointer.strip_prefix('#') {
312            let decoded = crate::encoding::percent_decode(percent_encoded)?;
313            self.0.pointer(&decoded)
314        } else {
315            self.0.pointer(pointer)
316        }
317    }
318
319    /// If the `Schema`'s underlying JSON value is an object, looks up a value by a JSON Pointer
320    /// and returns a mutable reference to that value.
321    ///
322    /// If the given pointer begins with a `#`, then the rest of the value is assumed to be in
323    /// "URI Fragment Identifier Representation", and will be percent-decoded accordingly.
324    ///
325    /// For more information on JSON Pointer, read [RFC6901](https://tools.ietf.org/html/rfc6901).
326    ///
327    /// This always returns `None` for bool schemas.
328    ///
329    /// # Example
330    /// ```
331    /// use schemars::{json_schema, Schema};
332    /// use serde_json::json;
333    ///
334    /// let mut schema = json_schema!({
335    ///     "properties": {
336    ///         "anything": true
337    ///     },
338    ///     "$defs": {
339    ///         "🚀": true
340    ///     }
341    /// });
342    ///
343    /// assert_eq!(schema.pointer_mut("/properties/anything").unwrap(), &json!(true));
344    /// assert_eq!(schema.pointer_mut("#/$defs/%F0%9F%9A%80").unwrap(), &json!(true));
345    /// assert_eq!(schema.pointer_mut("/does/not/exist"), None);
346    /// ```
347    #[must_use]
348    pub fn pointer_mut(&mut self, pointer: &str) -> Option<&mut Value> {
349        if let Some(percent_encoded) = pointer.strip_prefix('#') {
350            let decoded = crate::encoding::percent_decode(percent_encoded)?;
351            self.0.pointer_mut(&decoded)
352        } else {
353            self.0.pointer_mut(pointer)
354        }
355    }
356
357    /// If the `Schema`'s underlying JSON value is an object, removes and returns its value for the
358    /// given key.
359    ///
360    /// This always returns `None` for bool schemas, without modifying them.
361    ///
362    /// # Example
363    /// ```
364    /// use schemars::json_schema;
365    /// use serde_json::json;
366    ///
367    /// let mut schema = json_schema!({"type": "array"});
368    /// assert_eq!(schema.remove("type"), Some(json!("array")));
369    /// assert_eq!(schema, json_schema!({}));
370    /// ```
371    pub fn remove<Q>(&mut self, key: &Q) -> Option<Value>
372    where
373        String: core::borrow::Borrow<Q>,
374        Q: ?Sized + Ord + Eq + core::hash::Hash,
375    {
376        self.0.as_object_mut().and_then(|o| o.remove(key))
377    }
378
379    pub(crate) fn has_type(&self, ty: &str) -> bool {
380        match self.0.get("type") {
381            Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)),
382            Some(Value::String(s)) => s == ty,
383            _ => false,
384        }
385    }
386
387    fn validate<E: serde::de::Error>(value: &Value) -> Result<(), E> {
388        use serde::de::Unexpected;
389        let unexpected = match value {
390            Value::Bool(_) | Value::Object(_) => return Ok(()),
391            Value::Null => Unexpected::Unit,
392            Value::Number(n) => {
393                if let Some(u) = n.as_u64() {
394                    Unexpected::Unsigned(u)
395                } else if let Some(i) = n.as_i64() {
396                    Unexpected::Signed(i)
397                } else if let Some(f) = n.as_f64() {
398                    Unexpected::Float(f)
399                } else {
400                    unreachable!()
401                }
402            }
403            Value::String(s) => Unexpected::Str(s),
404            Value::Array(_) => Unexpected::Seq,
405        };
406
407        Err(E::invalid_type(unexpected, &"object or boolean"))
408    }
409
410    #[ref_cast_custom]
411    fn ref_cast(value: &Value) -> &Self;
412
413    #[ref_cast_custom]
414    fn ref_cast_mut(value: &mut Value) -> &mut Self;
415}
416
417impl From<Schema> for Value {
418    fn from(v: Schema) -> Value {
419        v.0
420    }
421}
422
423impl core::convert::TryFrom<Value> for Schema {
424    type Error = serde_json::Error;
425
426    fn try_from(value: Value) -> serde_json::Result<Schema> {
427        Schema::validate(&value)?;
428        Ok(Schema(value))
429    }
430}
431
432impl<'a> core::convert::TryFrom<&'a Value> for &'a Schema {
433    type Error = serde_json::Error;
434
435    fn try_from(value: &Value) -> serde_json::Result<&Schema> {
436        Schema::validate(value)?;
437        Ok(Schema::ref_cast(value))
438    }
439}
440
441impl<'a> core::convert::TryFrom<&'a mut Value> for &'a mut Schema {
442    type Error = serde_json::Error;
443
444    fn try_from(value: &mut Value) -> serde_json::Result<&mut Schema> {
445        Schema::validate(value)?;
446        Ok(Schema::ref_cast_mut(value))
447    }
448}
449
450impl Default for Schema {
451    fn default() -> Self {
452        Self(Value::Object(Map::new()))
453    }
454}
455
456impl From<Map<String, Value>> for Schema {
457    fn from(o: Map<String, Value>) -> Self {
458        Schema(Value::Object(o))
459    }
460}
461
462impl From<bool> for Schema {
463    fn from(b: bool) -> Self {
464        Schema(Value::Bool(b))
465    }
466}
467
468impl crate::JsonSchema for Schema {
469    fn schema_name() -> alloc::borrow::Cow<'static, str> {
470        "Schema".into()
471    }
472
473    fn schema_id() -> alloc::borrow::Cow<'static, str> {
474        "schemars::Schema".into()
475    }
476
477    fn json_schema(_: &mut crate::SchemaGenerator) -> Schema {
478        crate::json_schema!({
479            "type": ["object", "boolean"]
480        })
481    }
482}
483
484mod ser {
485    use serde::ser::{Serialize, SerializeMap, SerializeSeq};
486    use serde_json::Value;
487
488    // The order of properties in a JSON Schema object is insignificant, but we explicitly order
489    // some of them here to make them easier for a human to read. All other properties are ordered
490    // either lexicographically (by default) or by insertion order (if `preserve_order` is enabled)
491    const ORDERED_KEYWORDS_START: [&str; 7] = [
492        "$id",
493        "$schema",
494        "title",
495        "description",
496        "type",
497        "format",
498        "properties",
499    ];
500    const ORDERED_KEYWORDS_END: [&str; 2] = ["$defs", "definitions"];
501
502    // `no_reorder` is true when the value is expected to be an object that is NOT a schema,
503    // but the object's property values are expected to be schemas. In this case, we do not
504    // reorder the object's direct properties, but we do reorder nested (subschema) properties.
505    //
506    // When `no_reorder` is false, then the value is expected to be one of:
507    // - a JSON schema object
508    // - an array of JSON schemas
509    // - a JSON primitive value (null/string/number/bool)
510    //
511    // If any of these expectations are not met, then the value should still be serialized in a
512    // valid way, but the property ordering may be unclear.
513    pub(super) struct OrderedKeywordWrapper<'a> {
514        value: &'a Value,
515        no_reorder: bool,
516    }
517
518    impl<'a> From<&'a Value> for OrderedKeywordWrapper<'a> {
519        fn from(value: &'a Value) -> Self {
520            Self {
521                value,
522                no_reorder: false,
523            }
524        }
525    }
526
527    impl Serialize for OrderedKeywordWrapper<'_> {
528        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
529        where
530            S: serde::Serializer,
531        {
532            fn serialize_schema_property<S>(
533                map: &mut S::SerializeMap,
534                key: &str,
535                value: &Value,
536            ) -> Result<(), S::Error>
537            where
538                S: serde::Serializer,
539            {
540                if matches!(key, "examples" | "default") || key.starts_with("x-") {
541                    // Value(s) of `examples`/`default` are plain values, not schemas.
542                    // Also don't reorder values of custom properties.
543                    map.serialize_entry(key, value)
544                } else {
545                    let no_reorder = matches!(
546                        key,
547                        "properties"
548                            | "patternProperties"
549                            | "dependentSchemas"
550                            | "$defs"
551                            | "definitions"
552                    );
553                    map.serialize_entry(key, &OrderedKeywordWrapper { value, no_reorder })
554                }
555            }
556
557            match self.value {
558                Value::Array(array) => {
559                    let mut seq = serializer.serialize_seq(Some(array.len()))?;
560                    for value in array {
561                        seq.serialize_element(&OrderedKeywordWrapper::from(value))?;
562                    }
563                    seq.end()
564                }
565                Value::Object(object) if self.no_reorder => {
566                    let mut map = serializer.serialize_map(Some(object.len()))?;
567
568                    for (key, value) in object {
569                        // Don't use `serialize_schema_property` because `object` is NOT expected
570                        // to be a schema (but `value` is expected to be a schema)
571                        map.serialize_entry(key, &OrderedKeywordWrapper::from(value))?;
572                    }
573
574                    map.end()
575                }
576                Value::Object(object) => {
577                    let mut map = serializer.serialize_map(Some(object.len()))?;
578
579                    for key in ORDERED_KEYWORDS_START {
580                        if let Some(value) = object.get(key) {
581                            serialize_schema_property::<S>(&mut map, key, value)?;
582                        }
583                    }
584
585                    for (key, value) in object {
586                        if !ORDERED_KEYWORDS_START.contains(&key.as_str())
587                            && !ORDERED_KEYWORDS_END.contains(&key.as_str())
588                        {
589                            serialize_schema_property::<S>(&mut map, key, value)?;
590                        }
591                    }
592
593                    for key in ORDERED_KEYWORDS_END {
594                        if let Some(value) = object.get(key) {
595                            serialize_schema_property::<S>(&mut map, key, value)?;
596                        }
597                    }
598
599                    map.end()
600                }
601                Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {
602                    self.value.serialize(serializer)
603                }
604            }
605        }
606    }
607}