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