Coverage Report

Created: 2026-10-28 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/schemars-1.2.1/src/schema.rs
Line
Count
Source
1
/*!
2
JSON Schema types.
3
*/
4
5
use crate::_alloc_prelude::*;
6
use ref_cast::{ref_cast_custom, RefCastCustom};
7
use serde::{Deserialize, Serialize};
8
use 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)]
66
pub struct Schema(Value);
67
68
impl<'de> Deserialize<'de> for Schema {
69
0
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70
0
    where
71
0
        D: serde::Deserializer<'de>,
72
    {
73
0
        let value = Value::deserialize(deserializer)?;
74
0
        Schema::validate(&value)?;
75
0
        Ok(Schema(value))
76
0
    }
77
}
78
79
impl Serialize for Schema {
80
0
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81
0
    where
82
0
        S: serde::Serializer,
83
    {
84
0
        ser::OrderedKeywordWrapper::from(&self.0).serialize(serializer)
85
0
    }
86
}
87
88
impl PartialEq<bool> for Schema {
89
0
    fn eq(&self, other: &bool) -> bool {
90
0
        self.as_bool() == Some(*other)
91
0
    }
92
}
93
94
impl PartialEq<Map<String, Value>> for Schema {
95
0
    fn eq(&self, other: &Map<String, Value>) -> bool {
96
0
        self.as_object() == Some(other)
97
0
    }
98
}
99
100
impl PartialEq<Value> for Schema {
101
0
    fn eq(&self, other: &Value) -> bool {
102
0
        self.as_value() == other
103
0
    }
104
}
105
106
impl PartialEq<Schema> for bool {
107
0
    fn eq(&self, other: &Schema) -> bool {
108
0
        other == self
109
0
    }
110
}
111
112
impl PartialEq<Schema> for Map<String, Value> {
113
0
    fn eq(&self, other: &Schema) -> bool {
114
0
        other == self
115
0
    }
116
}
117
118
impl PartialEq<Schema> for Value {
119
0
    fn eq(&self, other: &Schema) -> bool {
120
0
        other == self
121
0
    }
122
}
123
124
impl 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
0
    pub fn new_ref(reference: String) -> Self {
131
0
        let mut map = Map::new();
132
0
        map.insert("$ref".to_owned(), Value::String(reference));
133
0
        Self(Value::Object(map))
134
0
    }
135
136
    /// Borrows the `Schema`'s underlying JSON value.
137
    #[must_use]
138
0
    pub fn as_value(&self) -> &Value {
139
0
        &self.0
140
0
    }
141
142
    /// If the `Schema`'s underlying JSON value is a bool, returns the bool value.
143
    #[must_use]
144
0
    pub fn as_bool(&self) -> Option<bool> {
145
0
        self.0.as_bool()
146
0
    }
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
0
    pub fn as_object(&self) -> Option<&Map<String, Value>> {
152
0
        self.0.as_object()
153
0
    }
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
0
    pub fn as_object_mut(&mut self) -> Option<&mut Map<String, Value>> {
159
0
        self.0.as_object_mut()
160
0
    }
161
162
0
    pub(crate) fn try_to_object(self) -> Result<Map<String, Value>, bool> {
163
0
        match self.0 {
164
0
            Value::Object(m) => Ok(m),
165
0
            Value::Bool(b) => Err(b),
166
0
            _ => unreachable!(),
167
        }
168
0
    }
169
170
0
    pub(crate) fn try_as_object_mut(&mut self) -> Result<&mut Map<String, Value>, bool> {
171
0
        match &mut self.0 {
172
0
            Value::Object(m) => Ok(m),
173
0
            Value::Bool(b) => Err(*b),
174
0
            _ => unreachable!(),
175
        }
176
0
    }
177
178
    /// Returns the `Schema`'s underlying JSON value.
179
    #[must_use]
180
0
    pub fn to_value(self) -> Value {
181
0
        self.0
182
0
    }
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
0
    pub fn ensure_object(&mut self) -> &mut Map<String, Value> {
192
0
        if let Some(b) = self.as_bool() {
193
0
            let mut map = Map::new();
194
0
            if !b {
195
0
                map.insert("not".into(), Value::Object(Map::new()));
196
0
            }
197
0
            self.0 = Value::Object(map);
198
0
        }
199
200
0
        self.0
201
0
            .as_object_mut()
202
0
            .expect("Schema value should be of type Object.")
203
0
    }
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
0
    pub fn insert(&mut self, k: String, v: Value) -> Option<Value> {
227
0
        self.ensure_object().insert(k, v)
228
0
    }
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
0
    pub fn get<Q>(&self, key: &Q) -> Option<&Value>
249
0
    where
250
0
        String: core::borrow::Borrow<Q>,
251
0
        Q: ?Sized + Ord + Eq + core::hash::Hash,
252
    {
253
0
        self.0.as_object().and_then(|o| o.get(key))
254
0
    }
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
0
    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Value>
274
0
    where
275
0
        String: core::borrow::Borrow<Q>,
276
0
        Q: ?Sized + Ord + Eq + core::hash::Hash,
277
    {
278
0
        self.0.as_object_mut().and_then(|o| o.get_mut(key))
279
0
    }
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
0
    pub fn pointer(&self, pointer: &str) -> Option<&Value> {
311
0
        if let Some(percent_encoded) = pointer.strip_prefix('#') {
312
0
            let decoded = crate::encoding::percent_decode(percent_encoded)?;
313
0
            self.0.pointer(&decoded)
314
        } else {
315
0
            self.0.pointer(pointer)
316
        }
317
0
    }
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
0
    pub fn pointer_mut(&mut self, pointer: &str) -> Option<&mut Value> {
349
0
        if let Some(percent_encoded) = pointer.strip_prefix('#') {
350
0
            let decoded = crate::encoding::percent_decode(percent_encoded)?;
351
0
            self.0.pointer_mut(&decoded)
352
        } else {
353
0
            self.0.pointer_mut(pointer)
354
        }
355
0
    }
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
0
    pub fn remove<Q>(&mut self, key: &Q) -> Option<Value>
372
0
    where
373
0
        String: core::borrow::Borrow<Q>,
374
0
        Q: ?Sized + Ord + Eq + core::hash::Hash,
375
    {
376
0
        self.0.as_object_mut().and_then(|o| o.remove(key))
377
0
    }
378
379
0
    pub(crate) fn has_type(&self, ty: &str) -> bool {
380
0
        match self.0.get("type") {
381
0
            Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)),
382
0
            Some(Value::String(s)) => s == ty,
383
0
            _ => false,
384
        }
385
0
    }
386
387
0
    fn validate<E: serde::de::Error>(value: &Value) -> Result<(), E> {
388
        use serde::de::Unexpected;
389
0
        let unexpected = match value {
390
0
            Value::Bool(_) | Value::Object(_) => return Ok(()),
391
0
            Value::Null => Unexpected::Unit,
392
0
            Value::Number(n) => {
393
0
                if let Some(u) = n.as_u64() {
394
0
                    Unexpected::Unsigned(u)
395
0
                } else if let Some(i) = n.as_i64() {
396
0
                    Unexpected::Signed(i)
397
0
                } else if let Some(f) = n.as_f64() {
398
0
                    Unexpected::Float(f)
399
                } else {
400
0
                    unreachable!()
401
                }
402
            }
403
0
            Value::String(s) => Unexpected::Str(s),
404
0
            Value::Array(_) => Unexpected::Seq,
405
        };
406
407
0
        Err(E::invalid_type(unexpected, &"object or boolean"))
408
0
    }
409
410
    #[ref_cast_custom]
411
0
    fn ref_cast(value: &Value) -> &Self;
412
413
    #[ref_cast_custom]
414
0
    fn ref_cast_mut(value: &mut Value) -> &mut Self;
415
}
416
417
impl From<Schema> for Value {
418
0
    fn from(v: Schema) -> Value {
419
0
        v.0
420
0
    }
421
}
422
423
impl core::convert::TryFrom<Value> for Schema {
424
    type Error = serde_json::Error;
425
426
0
    fn try_from(value: Value) -> serde_json::Result<Schema> {
427
0
        Schema::validate(&value)?;
428
0
        Ok(Schema(value))
429
0
    }
430
}
431
432
impl<'a> core::convert::TryFrom<&'a Value> for &'a Schema {
433
    type Error = serde_json::Error;
434
435
0
    fn try_from(value: &Value) -> serde_json::Result<&Schema> {
436
0
        Schema::validate(value)?;
437
0
        Ok(Schema::ref_cast(value))
438
0
    }
439
}
440
441
impl<'a> core::convert::TryFrom<&'a mut Value> for &'a mut Schema {
442
    type Error = serde_json::Error;
443
444
0
    fn try_from(value: &mut Value) -> serde_json::Result<&mut Schema> {
445
0
        Schema::validate(value)?;
446
0
        Ok(Schema::ref_cast_mut(value))
447
0
    }
448
}
449
450
impl Default for Schema {
451
0
    fn default() -> Self {
452
0
        Self(Value::Object(Map::new()))
453
0
    }
454
}
455
456
impl From<Map<String, Value>> for Schema {
457
0
    fn from(o: Map<String, Value>) -> Self {
458
0
        Schema(Value::Object(o))
459
0
    }
460
}
461
462
impl From<bool> for Schema {
463
0
    fn from(b: bool) -> Self {
464
0
        Schema(Value::Bool(b))
465
0
    }
466
}
467
468
impl crate::JsonSchema for Schema {
469
0
    fn schema_name() -> alloc::borrow::Cow<'static, str> {
470
0
        "Schema".into()
471
0
    }
472
473
0
    fn schema_id() -> alloc::borrow::Cow<'static, str> {
474
0
        "schemars::Schema".into()
475
0
    }
476
477
0
    fn json_schema(_: &mut crate::SchemaGenerator) -> Schema {
478
0
        crate::json_schema!({
479
0
            "type": ["object", "boolean"]
480
        })
481
0
    }
482
}
483
484
mod 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
0
        fn from(value: &'a Value) -> Self {
520
0
            Self {
521
0
                value,
522
0
                no_reorder: false,
523
0
            }
524
0
        }
525
    }
526
527
    impl Serialize for OrderedKeywordWrapper<'_> {
528
0
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
529
0
        where
530
0
            S: serde::Serializer,
531
        {
532
0
            fn serialize_schema_property<S>(
533
0
                map: &mut S::SerializeMap,
534
0
                key: &str,
535
0
                value: &Value,
536
0
            ) -> Result<(), S::Error>
537
0
            where
538
0
                S: serde::Serializer,
539
            {
540
0
                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
0
                    map.serialize_entry(key, value)
544
                } else {
545
0
                    let no_reorder = matches!(
546
0
                        key,
547
0
                        "properties"
548
0
                            | "patternProperties"
549
0
                            | "dependentSchemas"
550
0
                            | "$defs"
551
0
                            | "definitions"
552
                    );
553
0
                    map.serialize_entry(key, &OrderedKeywordWrapper { value, no_reorder })
554
                }
555
0
            }
556
557
0
            match self.value {
558
0
                Value::Array(array) => {
559
0
                    let mut seq = serializer.serialize_seq(Some(array.len()))?;
560
0
                    for value in array {
561
0
                        seq.serialize_element(&OrderedKeywordWrapper::from(value))?;
562
                    }
563
0
                    seq.end()
564
                }
565
0
                Value::Object(object) if self.no_reorder => {
566
0
                    let mut map = serializer.serialize_map(Some(object.len()))?;
567
568
0
                    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
0
                        map.serialize_entry(key, &OrderedKeywordWrapper::from(value))?;
572
                    }
573
574
0
                    map.end()
575
                }
576
0
                Value::Object(object) => {
577
0
                    let mut map = serializer.serialize_map(Some(object.len()))?;
578
579
0
                    for key in ORDERED_KEYWORDS_START {
580
0
                        if let Some(value) = object.get(key) {
581
0
                            serialize_schema_property::<S>(&mut map, key, value)?;
582
0
                        }
583
                    }
584
585
0
                    for (key, value) in object {
586
0
                        if !ORDERED_KEYWORDS_START.contains(&key.as_str())
587
0
                            && !ORDERED_KEYWORDS_END.contains(&key.as_str())
588
                        {
589
0
                            serialize_schema_property::<S>(&mut map, key, value)?;
590
0
                        }
591
                    }
592
593
0
                    for key in ORDERED_KEYWORDS_END {
594
0
                        if let Some(value) = object.get(key) {
595
0
                            serialize_schema_property::<S>(&mut map, key, value)?;
596
0
                        }
597
                    }
598
599
0
                    map.end()
600
                }
601
                Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {
602
0
                    self.value.serialize(serializer)
603
                }
604
            }
605
0
        }
606
    }
607
}