jtd/
schema.rs

1use crate::SerdeSchema;
2use serde_json::Value;
3use std::collections::{BTreeMap, BTreeSet};
4use thiserror::Error;
5
6/// A convenience alias for the JSON Typedef `definitions` keyword value.
7pub type Definitions = BTreeMap<String, Schema>;
8
9/// A convenience alias for the JSON Typedef `metadata` keyword value.
10pub type Metadata = BTreeMap<String, Value>;
11
12/// A pattern-matching-friendly representation of a JSON Typedef schema.
13///
14/// Each variant of this schema corresponds to one of the eight "forms" a schema
15/// may take on. All of the forms share the following fields:
16///
17/// * `definitions` corresponds to the JSON Typedef keyword of the same name.
18///    This should only be non-empty on root schemas. Otherwise,
19///    [`Schema::validate`] will return
20///    [`SchemaValidateError::NonRootDefinitions`].
21///
22/// * `metadata` corresponds to the JSON Typedef keyword of the same name. Use
23///   this to convey information not pertinent to validation, such as hints for
24///   code generation. Do not expect other parties to understand the fields
25///   inside metadata unless you've agreed upon them out-of-band.
26///
27/// Except for [`Schema::Empty`], all of the forms also share one additional
28/// field:
29///
30/// * `nullable` corresponds to the JSON Typedef keyword of the same name. If
31///   set to "true", then regardless of any other considerations the schema will
32///   accept JSON `null` as valid.
33///
34/// [`Schema::Empty`] omits `nullable` because it's redundant; schemas of the
35/// empty form already accept `null` anyway.
36///
37/// For convenience, these three common properties have associated borrowing
38/// "getters": [`Schema::definitions`], [`Schema::metadata`], and
39/// [`Schema::nullable`].
40///
41/// If you are trying to parse a JSON Typedef schema from JSON, see
42/// [`SerdeSchema`] and [`Schema::from_serde_schema`].
43///
44/// ```
45/// use jtd::{SerdeSchema, Schema};
46/// use serde_json::json;
47///
48/// assert_eq!(
49///     Schema::from_serde_schema(serde_json::from_value(json!({
50///         "elements": {
51///             "type": "uint32",
52///             "nullable": true
53///         }
54///     })).unwrap()).unwrap(),
55///     jtd::Schema::Elements {
56///         definitions: Default::default(),
57///         metadata: Default::default(),
58///         nullable: false,
59///         elements: Box::new(jtd::Schema::Type {
60///             definitions: Default::default(),
61///             metadata: Default::default(),
62///             nullable: true,
63///             type_: jtd::Type::Uint32,
64///         })
65///     }
66/// );
67/// ```
68#[derive(Clone, Debug, PartialEq)]
69pub enum Schema {
70    /// The [empty](https://tools.ietf.org/html/rfc8927#section-2.2.1) form.
71    ///
72    /// The empty form will accept all inputs. It corresponds to the "top" type
73    /// of many programming language, like Java's `Object` or TypeScript's
74    /// `any`.
75    Empty {
76        definitions: Definitions,
77        metadata: Metadata,
78    },
79
80    /// The [ref](https://tools.ietf.org/html/rfc8927#section-2.2.2) form.
81    ///
82    /// The ref form accepts whatever the definition it refers to accepts.
83    Ref {
84        definitions: Definitions,
85        metadata: Metadata,
86        nullable: bool,
87
88        /// The name of the definition being referred to.
89        ref_: String,
90    },
91
92    /// The [type](https://tools.ietf.org/html/rfc8927#section-2.2.3) form.
93    ///
94    /// The type form accepts JSON "primitives" (booleans, numbers, strings)
95    /// whose value fall within a certain "type". These types are enumerated in
96    /// [`Type`].
97    Type {
98        definitions: Definitions,
99        metadata: Metadata,
100        nullable: bool,
101
102        /// The type of primitive value accepted.
103        type_: Type,
104    },
105
106    /// The [enum](https://tools.ietf.org/html/rfc8927#section-2.2.4) form.
107    ///
108    /// The enum form accepts JSON strings whose values are within an enumerated
109    /// set.
110    Enum {
111        definitions: Definitions,
112        metadata: Metadata,
113        nullable: bool,
114
115        /// The values the schema accepts.
116        enum_: BTreeSet<String>,
117    },
118
119    /// The [elements](https://tools.ietf.org/html/rfc8927#section-2.2.5) form.
120    ///
121    /// The elements form accepts JSON arrays, and each element of the array is
122    /// validated against a sub-schema.
123    Elements {
124        definitions: Definitions,
125        metadata: Metadata,
126        nullable: bool,
127
128        /// A schema for the elements of the array.
129        elements: Box<Schema>,
130    },
131
132    /// The [properties](https://tools.ietf.org/html/rfc8927#section-2.2.6)
133    /// form.
134    ///
135    /// The properties form accepts JSON objects being used as "structs".
136    Properties {
137        definitions: Definitions,
138        metadata: Metadata,
139        nullable: bool,
140
141        /// The required properties of the "struct", and the schema that each
142        /// must satisfy.
143        properties: BTreeMap<String, Schema>,
144
145        /// The optional properties of the "struct", and the schema that each
146        /// must satisfy if present.
147        optional_properties: BTreeMap<String, Schema>,
148
149        /// Whether the `properties` keyword is present on the schema.
150        ///
151        /// It is invalid to set this to `false` while having `properties` be
152        /// non-empty.
153        ///
154        /// This is used only to handle the corner case of a properties-form
155        /// schema being used to validate a non-object; in order to ensure the
156        /// returned `schema_path` points to a part of the schema that really
157        /// exists, validators need to be able to tell the difference between
158        /// `properties` being an empty object versus being omitted from the
159        /// schema.
160        ///
161        /// This field does not affect whether an input is valid. It only
162        /// affects the `schema_path` that will be returned if that input is not
163        /// an object. For more details, see the first sub-bullet after
164        /// "Otherwise" in [RFC 8927, Section
165        /// 3.3.6](https://tools.ietf.org/html/rfc8927#section-3.3.6).
166        ///
167        /// [`Schema::from_serde_schema`] correctly handles populating this
168        /// field. If you are constructing schemas by hand and want to play it
169        /// safe, it is always safe to set this to `true`.
170        properties_is_present: bool,
171
172        /// Whether additional properties not specified in `properties` or
173        /// `optional_properties` are permitted.
174        additional_properties: bool,
175    },
176
177    /// The [values](https://tools.ietf.org/html/rfc8927#section-2.2.7) form.
178    ///
179    /// The values form accepts JSON objects being used as "dictionaries"; each
180    /// value of the dictionary is validated against a sub-schema.
181    Values {
182        definitions: Definitions,
183        metadata: Metadata,
184        nullable: bool,
185
186        /// A schema for the values of the "dictionary" object.
187        values: Box<Schema>,
188    },
189
190    /// The [discriminator](https://tools.ietf.org/html/rfc8927#section-2.2.8)
191    /// form.
192    ///
193    /// The discriminator form accepts JSON objects being used as "discriminated
194    /// unions", or "tagged unions".
195    Discriminator {
196        definitions: Definitions,
197        metadata: Metadata,
198        nullable: bool,
199
200        /// The "discriminator" property of the schema.
201        ///
202        /// For an input to be valid, this property must exist and its value
203        /// must be a key in `mapping`.
204        discriminator: String,
205
206        /// A mapping from the value of the `discriminator` property in the
207        /// input to a schema that the rest of the input (without the
208        /// `discriminator` property) must satisfy.
209        mapping: BTreeMap<String, Schema>,
210    },
211}
212
213/// The values [`Schema::Type::type_`] may take on.
214#[derive(Clone, Debug, PartialEq, Eq)]
215pub enum Type {
216    /// Either JSON `true` or `false`.
217    Boolean,
218
219    /// A JSON number with zero fractional part within the range of [`i8`].
220    Int8,
221
222    /// A JSON number with zero fractional part within the range of [`u8`].
223    Uint8,
224
225    /// A JSON number with zero fractional part within the range of [`i16`].
226    Int16,
227
228    /// A JSON number with zero fractional part within the range of [`u16`].
229    Uint16,
230
231    /// A JSON number with zero fractional part within the range of [`i32`].
232    Int32,
233
234    /// A JSON number with zero fractional part within the range of [`u32`].
235    Uint32,
236
237    /// A JSON number. Code generators will treat this like a Rust [`f32`].
238    Float32,
239
240    /// A JSON number. Code generators will treat this like a Rust [`f64`].
241    Float64,
242
243    /// A JSON string.
244    String,
245
246    /// A JSON string encoding a [RFC3339](https://tools.ietf.org/html/rfc3339)
247    /// timestamp.
248    Timestamp,
249}
250
251/// Errors that may arise from [`Schema::from_serde_schema`].
252#[derive(Clone, Debug, PartialEq, Eq, Error)]
253pub enum FromSerdeSchemaError {
254    /// Indicates the schema uses an invalid combination of keywords.
255    ///
256    /// ```
257    /// use jtd::{FromSerdeSchemaError, Schema, SerdeSchema};
258    ///
259    /// assert_eq!(
260    ///     Err(FromSerdeSchemaError::InvalidForm),
261    ///
262    ///     // it's invalid to have both "type" and "enum" on a schema
263    ///     Schema::from_serde_schema(SerdeSchema {
264    ///         type_: Some("uint8".to_owned()),
265    ///         enum_: Some(Default::default()),
266    ///         ..Default::default()
267    ///     })
268    /// )
269    /// ```
270    #[error("invalid combination of keywords in schema")]
271    InvalidForm,
272
273    /// Indicates the schema uses a value for `type` that isn't in [`Type`].
274    ///
275    /// ```
276    /// use jtd::{FromSerdeSchemaError, Schema, SerdeSchema};
277    ///
278    /// assert_eq!(
279    ///     Err(FromSerdeSchemaError::InvalidType("uint64".to_owned())),
280    ///
281    ///     // there is no uint64 in JSON Typedef
282    ///     Schema::from_serde_schema(SerdeSchema {
283    ///         type_: Some("uint64".to_owned()),
284    ///         ..Default::default()
285    ///     })
286    /// )
287    /// ```
288    #[error("invalid type: {0:?}")]
289    InvalidType(String),
290
291    /// Indicates the schema has the same value appearing twice in an `enum`.
292    ///
293    /// ```
294    /// use jtd::{FromSerdeSchemaError, Schema, SerdeSchema};
295    ///
296    /// assert_eq!(
297    ///     Err(FromSerdeSchemaError::DuplicatedEnumValue("foo".to_owned())),
298    ///
299    ///     // it's invalid to have the same value appear twice in an enum array
300    ///     Schema::from_serde_schema(SerdeSchema {
301    ///         enum_: Some(vec!["foo".into(), "bar".into(), "foo".into()]),
302    ///         ..Default::default()
303    ///     })
304    /// )
305    /// ```
306    #[error("duplicated enum value: {0:?}")]
307    DuplicatedEnumValue(String),
308}
309
310/// Errors that may arise from [`Schema::validate`].
311#[derive(Clone, Debug, PartialEq, Eq, Error)]
312pub enum SchemaValidateError {
313    /// Indicates the schema has a `ref` to a definition that doesn't exist.
314    ///
315    /// ```
316    /// use jtd::{Schema, SchemaValidateError};
317    ///
318    /// assert_eq!(
319    ///     Err(SchemaValidateError::NoSuchDefinition("foo".into())),
320    ///
321    ///     // a "ref" without definitions is always invalid
322    ///     Schema::Ref {
323    ///         definitions: Default::default(),
324    ///         metadata: Default::default(),
325    ///         nullable: Default::default(),
326    ///         ref_: "foo".into(),
327    ///     }.validate(),
328    /// )
329    /// ```
330    #[error("no such definition: {0:?}")]
331    NoSuchDefinition(String),
332
333    /// Indicates the schema has non-empty `definitions` below the root level.
334    ///
335    /// ```
336    /// use jtd::{Schema, SchemaValidateError};
337    ///
338    /// assert_eq!(
339    ///     Err(SchemaValidateError::NonRootDefinitions),
340    ///
341    ///     // definitions can only be present at the root level
342    ///     Schema::Elements {
343    ///         definitions: Default::default(),
344    ///         metadata: Default::default(),
345    ///         nullable: Default::default(),
346    ///         elements: Box::new(Schema::Empty {
347    ///             definitions: vec![(
348    ///                 "foo".to_owned(),
349    ///                 Schema::Empty {
350    ///                     definitions: Default::default(),
351    ///                     metadata: Default::default(),
352    ///                 }
353    ///             )].into_iter().collect(),
354    ///             metadata: Default::default(),
355    ///         }),
356    ///     }.validate(),
357    /// )
358    /// ```
359    #[error("non-root definitions")]
360    NonRootDefinitions,
361
362    /// Indicates the schema has an `enum` with no values in it.
363    ///
364    /// ```
365    /// use jtd::{Schema, SchemaValidateError};
366    ///
367    /// assert_eq!(
368    ///     Err(SchemaValidateError::EmptyEnum),
369    ///
370    ///     // empty enums are illegal
371    ///     Schema::Enum {
372    ///         definitions: Default::default(),
373    ///         metadata: Default::default(),
374    ///         nullable: Default::default(),
375    ///         enum_: Default::default(),
376    ///     }.validate(),
377    /// )
378    /// ```
379    #[error("empty enum")]
380    EmptyEnum,
381
382    /// Indicates the schema has the same property appear in `properties` and
383    /// `optional_properties`.
384    ///
385    /// ```
386    /// use jtd::{Schema, SchemaValidateError};
387    ///
388    /// assert_eq!(
389    ///     Err(SchemaValidateError::RepeatedProperty("foo".into())),
390    ///
391    ///     // properties and optional_properties must not overlap
392    ///     Schema::Properties {
393    ///         definitions: Default::default(),
394    ///         metadata: Default::default(),
395    ///         nullable: Default::default(),
396    ///         properties: vec![(
397    ///             "foo".to_owned(),
398    ///             Schema::Empty {
399    ///                 definitions: Default::default(),
400    ///                 metadata: Default::default(),
401    ///             },
402    ///         )].into_iter().collect(),
403    ///         optional_properties: vec![(
404    ///             "foo".to_owned(),
405    ///             Schema::Empty {
406    ///                 definitions: Default::default(),
407    ///                 metadata: Default::default(),
408    ///             },
409    ///         )].into_iter().collect(),
410    ///         properties_is_present: true,
411    ///         additional_properties: false,
412    ///     }.validate(),
413    /// )
414    /// ```
415    #[error("property repeated in optionalProperties: {0:?}")]
416    RepeatedProperty(String),
417
418    /// Indicates the schema has a value in `mapping` with `nullable` set to
419    /// `true`.
420    ///
421    /// ```
422    /// use jtd::{Schema, SchemaValidateError};
423    ///
424    /// assert_eq!(
425    ///     Err(SchemaValidateError::NullableMapping),
426    ///
427    ///     // mappings must not be nullable
428    ///     Schema::Discriminator {
429    ///         definitions: Default::default(),
430    ///         metadata: Default::default(),
431    ///         nullable: Default::default(),
432    ///         discriminator: "foo".into(),
433    ///         mapping: vec![(
434    ///             "bar".to_owned(),
435    ///             Schema::Properties {
436    ///                 definitions: Default::default(),
437    ///                 metadata: Default::default(),
438    ///                 nullable: true,
439    ///                 properties: Default::default(),
440    ///                 optional_properties: Default::default(),
441    ///                 properties_is_present: true,
442    ///                 additional_properties: false,
443    ///             }
444    ///         )].into_iter().collect(),
445    ///     }.validate(),
446    /// );
447    /// ```
448    #[error("nullable schema in mapping")]
449    NullableMapping,
450
451    /// Indicates the schema has a value in `mapping` that isn't a
452    /// [`Schema::Properties`].
453    ///
454    /// ```
455    /// use jtd::{Schema, SchemaValidateError};
456    ///
457    /// assert_eq!(
458    ///     Err(SchemaValidateError::NonPropertiesMapping),
459    ///
460    ///     // mappings must be of the properties form
461    ///     Schema::Discriminator {
462    ///         definitions: Default::default(),
463    ///         metadata: Default::default(),
464    ///         nullable: Default::default(),
465    ///         discriminator: "foo".into(),
466    ///         mapping: vec![(
467    ///             "bar".to_owned(),
468    ///             Schema::Empty {
469    ///                 definitions: Default::default(),
470    ///                 metadata: Default::default(),
471    ///             }
472    ///         )].into_iter().collect(),
473    ///     }.validate(),
474    /// );
475    /// ```
476    #[error("non-properties schema in mapping")]
477    NonPropertiesMapping,
478
479    /// Indicates the schema has a value in `mapping` whose `properties` or
480    /// `optional_properties` contains `discriminator`.
481    ///
482    /// ```
483    /// use jtd::{Schema, SchemaValidateError};
484    ///
485    /// assert_eq!(
486    ///     Err(SchemaValidateError::RepeatedDiscriminator("foo".into())),
487    ///
488    ///     // mappings must not re-define the discriminator property
489    ///     Schema::Discriminator {
490    ///         definitions: Default::default(),
491    ///         metadata: Default::default(),
492    ///         nullable: Default::default(),
493    ///         discriminator: "foo".into(),
494    ///         mapping: vec![(
495    ///             "bar".to_owned(),
496    ///             Schema::Properties {
497    ///                 definitions: Default::default(),
498    ///                 metadata: Default::default(),
499    ///                 nullable: Default::default(),
500    ///                 properties: vec![(
501    ///                     "foo".into(),
502    ///                     Schema::Empty {
503    ///                         definitions: Default::default(),
504    ///                         metadata: Default::default(),
505    ///                     }
506    ///                 )].into_iter().collect(),
507    ///                 optional_properties: Default::default(),
508    ///                 properties_is_present: true,
509    ///                 additional_properties: false,
510    ///             }
511    ///         )].into_iter().collect(),
512    ///     }.validate(),
513    /// );
514    /// ```
515    #[error("discriminator redefined in mapping: {0:?}")]
516    RepeatedDiscriminator(String),
517}
518
519// Index of valid form "signatures" -- i.e., combinations of the presence of the
520// keywords (in order):
521//
522// ref type enum elements properties optionalProperties additionalProperties
523// values discriminator mapping
524//
525// The keywords "definitions", "nullable", and "metadata" are not included here,
526// because they would restrict nothing.
527const VALID_FORM_SIGNATURES: [[bool; 10]; 13] = [
528    // Empty form
529    [
530        false, false, false, false, false, false, false, false, false, false,
531    ],
532    // Ref form
533    [
534        true, false, false, false, false, false, false, false, false, false,
535    ],
536    // Type form
537    [
538        false, true, false, false, false, false, false, false, false, false,
539    ],
540    // Enum form
541    [
542        false, false, true, false, false, false, false, false, false, false,
543    ],
544    // Elements form
545    [
546        false, false, false, true, false, false, false, false, false, false,
547    ],
548    // Properties form -- properties or optional properties or both, and never
549    // additional properties on its own
550    [
551        false, false, false, false, true, false, false, false, false, false,
552    ],
553    [
554        false, false, false, false, false, true, false, false, false, false,
555    ],
556    [
557        false, false, false, false, true, true, false, false, false, false,
558    ],
559    [
560        false, false, false, false, true, false, true, false, false, false,
561    ],
562    [
563        false, false, false, false, false, true, true, false, false, false,
564    ],
565    [
566        false, false, false, false, true, true, true, false, false, false,
567    ],
568    // Values form
569    [
570        false, false, false, false, false, false, false, true, false, false,
571    ],
572    // Discriminator form
573    [
574        false, false, false, false, false, false, false, false, true, true,
575    ],
576];
577
578impl Schema {
579    /// Converts a [`Schema`] into a [`SerdeSchema`].
580    ///
581    /// ```
582    /// use jtd::{Schema, SerdeSchema, Type};
583    ///
584    /// assert_eq!(
585    ///     SerdeSchema {
586    ///         type_: Some("uint8".to_owned()),
587    ///         ..Default::default()
588    ///     },
589    ///     Schema::Type {
590    ///         definitions: Default::default(),
591    ///         metadata: Default::default(),
592    ///         nullable: false,
593    ///         type_: Type::Uint8,
594    ///     }.into_serde_schema(),
595    /// );
596    /// ```
597    pub fn into_serde_schema(self) -> SerdeSchema {
598        let mut serde_schema: SerdeSchema = Default::default();
599
600        match self {
601            Schema::Empty {
602                definitions,
603                metadata,
604            } => {
605                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
606                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
607            }
608
609            Schema::Ref {
610                definitions,
611                metadata,
612                nullable,
613                ref_,
614            } => {
615                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
616                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
617                serde_schema.nullable = Self::nullable_into_serde_schema(nullable);
618                serde_schema.ref_ = Some(ref_);
619            }
620
621            Schema::Type {
622                definitions,
623                metadata,
624                nullable,
625                type_,
626            } => {
627                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
628                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
629                serde_schema.nullable = Self::nullable_into_serde_schema(nullable);
630                serde_schema.type_ = Some(
631                    match type_ {
632                        Type::Boolean => "boolean",
633                        Type::Int8 => "int8",
634                        Type::Uint8 => "uint8",
635                        Type::Int16 => "int16",
636                        Type::Uint16 => "uint16",
637                        Type::Int32 => "int32",
638                        Type::Uint32 => "uint32",
639                        Type::Float32 => "float32",
640                        Type::Float64 => "float64",
641                        Type::String => "string",
642                        Type::Timestamp => "timestamp",
643                    }
644                    .to_owned(),
645                );
646            }
647
648            Schema::Enum {
649                definitions,
650                metadata,
651                nullable,
652                enum_,
653            } => {
654                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
655                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
656                serde_schema.nullable = Self::nullable_into_serde_schema(nullable);
657                serde_schema.enum_ = Some(enum_.into_iter().collect());
658            }
659
660            Schema::Elements {
661                definitions,
662                metadata,
663                nullable,
664                elements,
665            } => {
666                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
667                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
668                serde_schema.nullable = Self::nullable_into_serde_schema(nullable);
669                serde_schema.elements = Some(Box::new(elements.into_serde_schema()));
670            }
671
672            Schema::Properties {
673                definitions,
674                metadata,
675                nullable,
676                properties,
677                optional_properties,
678                properties_is_present,
679                additional_properties,
680            } => {
681                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
682                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
683                serde_schema.nullable = Self::nullable_into_serde_schema(nullable);
684
685                if properties_is_present {
686                    serde_schema.properties = Some(
687                        properties
688                            .into_iter()
689                            .map(|(k, v)| (k, v.into_serde_schema()))
690                            .collect(),
691                    );
692                }
693
694                if !optional_properties.is_empty() {
695                    serde_schema.optional_properties = Some(
696                        optional_properties
697                            .into_iter()
698                            .map(|(k, v)| (k, v.into_serde_schema()))
699                            .collect(),
700                    );
701                }
702
703                if additional_properties {
704                    serde_schema.additional_properties = Some(additional_properties);
705                }
706            }
707
708            Schema::Values {
709                definitions,
710                metadata,
711                nullable,
712                values,
713            } => {
714                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
715                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
716                serde_schema.nullable = Self::nullable_into_serde_schema(nullable);
717                serde_schema.values = Some(Box::new(values.into_serde_schema()));
718            }
719
720            Schema::Discriminator {
721                definitions,
722                metadata,
723                nullable,
724                discriminator,
725                mapping,
726            } => {
727                serde_schema.definitions = Self::definitions_into_serde_schema(definitions);
728                serde_schema.metadata = Self::metadata_into_serde_schema(metadata);
729                serde_schema.nullable = Self::nullable_into_serde_schema(nullable);
730                serde_schema.discriminator = Some(discriminator);
731                serde_schema.mapping = Some(
732                    mapping
733                        .into_iter()
734                        .map(|(k, v)| (k, v.into_serde_schema()))
735                        .collect(),
736                );
737            }
738        }
739
740        serde_schema
741    }
742
743    fn definitions_into_serde_schema(
744        definitions: Definitions,
745    ) -> Option<BTreeMap<String, SerdeSchema>> {
746        if definitions.is_empty() {
747            None
748        } else {
749            Some(
750                definitions
751                    .into_iter()
752                    .map(|(k, v)| (k, v.into_serde_schema()))
753                    .collect(),
754            )
755        }
756    }
757
758    fn metadata_into_serde_schema(metadata: Metadata) -> Option<BTreeMap<String, Value>> {
759        if metadata.is_empty() {
760            None
761        } else {
762            Some(metadata)
763        }
764    }
765
766    fn nullable_into_serde_schema(nullable: bool) -> Option<bool> {
767        if nullable {
768            Some(true)
769        } else {
770            None
771        }
772    }
773
774    /// Constructs a [`Schema`] from a [`SerdeSchema`].
775    ///
776    /// ```
777    /// use jtd::{Schema, SerdeSchema, Type};
778    ///
779    /// assert_eq!(
780    ///     Schema::Type {
781    ///         definitions: Default::default(),
782    ///         metadata: Default::default(),
783    ///         nullable: false,
784    ///         type_: Type::Uint8,
785    ///     },
786    ///     Schema::from_serde_schema(SerdeSchema {
787    ///         type_: Some("uint8".to_owned()),
788    ///         ..Default::default()
789    ///     }).unwrap(),
790    /// );
791    /// ```
792    ///
793    /// See the documentation for [`FromSerdeSchemaError`] for examples of how
794    /// this function may return an error.
795    pub fn from_serde_schema(serde_schema: SerdeSchema) -> Result<Self, FromSerdeSchemaError> {
796        let mut definitions = BTreeMap::new();
797        for (name, sub_schema) in serde_schema.definitions.unwrap_or_default() {
798            definitions.insert(name, Self::from_serde_schema(sub_schema)?);
799        }
800
801        let metadata = serde_schema.metadata.unwrap_or_default();
802        let nullable = serde_schema.nullable.unwrap_or(false);
803
804        // Ensure the schema is using a valid combination of keywords.
805        let form_signature = [
806            serde_schema.ref_.is_some(),
807            serde_schema.type_.is_some(),
808            serde_schema.enum_.is_some(),
809            serde_schema.elements.is_some(),
810            serde_schema.properties.is_some(),
811            serde_schema.optional_properties.is_some(),
812            serde_schema.additional_properties.is_some(),
813            serde_schema.values.is_some(),
814            serde_schema.discriminator.is_some(),
815            serde_schema.mapping.is_some(),
816        ];
817
818        if !VALID_FORM_SIGNATURES.contains(&form_signature) {
819            return Err(FromSerdeSchemaError::InvalidForm);
820        }
821
822        // From here on out, we can use the presence of certain keywords to
823        // determine the form the schema takes on.
824        //
825        // We'll handle the empty form as a fallback, and handle the other forms
826        // in standard order.
827        if let Some(ref_) = serde_schema.ref_ {
828            return Ok(Schema::Ref {
829                definitions,
830                metadata,
831                nullable,
832                ref_,
833            });
834        }
835
836        if let Some(type_) = serde_schema.type_ {
837            let type_ = match &type_[..] {
838                "boolean" => Type::Boolean,
839                "int8" => Type::Int8,
840                "uint8" => Type::Uint8,
841                "int16" => Type::Int16,
842                "uint16" => Type::Uint16,
843                "int32" => Type::Int32,
844                "uint32" => Type::Uint32,
845                "float32" => Type::Float32,
846                "float64" => Type::Float64,
847                "string" => Type::String,
848                "timestamp" => Type::Timestamp,
849                _ => return Err(FromSerdeSchemaError::InvalidType(type_)),
850            };
851
852            return Ok(Schema::Type {
853                definitions,
854                metadata,
855                nullable,
856                type_,
857            });
858        }
859
860        if let Some(enum_) = serde_schema.enum_ {
861            // We do this construction by hand, rather than using collect, to
862            // detect the case of an enum value being repeated. This can't be
863            // detected once the values are put in the set.
864            let mut values = BTreeSet::new();
865            for value in enum_ {
866                if values.contains(&value) {
867                    return Err(FromSerdeSchemaError::DuplicatedEnumValue(value));
868                }
869
870                values.insert(value);
871            }
872
873            return Ok(Schema::Enum {
874                definitions,
875                metadata,
876                nullable,
877                enum_: values,
878            });
879        }
880
881        if let Some(elements) = serde_schema.elements {
882            return Ok(Schema::Elements {
883                definitions,
884                metadata,
885                nullable,
886                elements: Box::new(Self::from_serde_schema(*elements)?),
887            });
888        }
889
890        if serde_schema.properties.is_some() || serde_schema.optional_properties.is_some() {
891            let properties_is_present = serde_schema.properties.is_some();
892            let additional_properties = serde_schema.additional_properties.unwrap_or(false);
893
894            let mut properties = BTreeMap::new();
895            for (name, sub_schema) in serde_schema.properties.unwrap_or_default() {
896                properties.insert(name, Self::from_serde_schema(sub_schema)?);
897            }
898
899            let mut optional_properties = BTreeMap::new();
900            for (name, sub_schema) in serde_schema.optional_properties.unwrap_or_default() {
901                optional_properties.insert(name, Self::from_serde_schema(sub_schema)?);
902            }
903
904            return Ok(Schema::Properties {
905                definitions,
906                metadata,
907                nullable,
908                properties,
909                optional_properties,
910                properties_is_present,
911                additional_properties,
912            });
913        }
914
915        if let Some(values) = serde_schema.values {
916            return Ok(Schema::Values {
917                definitions,
918                metadata,
919                nullable,
920                values: Box::new(Self::from_serde_schema(*values)?),
921            });
922        }
923
924        if let Some(discriminator) = serde_schema.discriminator {
925            // This is safe because the form signature check ensures mapping is
926            // present if discriminator is present.
927            let mut mapping = BTreeMap::new();
928            for (name, sub_schema) in serde_schema.mapping.unwrap() {
929                mapping.insert(name, Self::from_serde_schema(sub_schema)?);
930            }
931
932            return Ok(Schema::Discriminator {
933                definitions,
934                metadata,
935                nullable,
936                discriminator,
937                mapping,
938            });
939        }
940
941        Ok(Schema::Empty {
942            definitions,
943            metadata,
944        })
945    }
946
947    /// Ensures a [`Schema`] is well-formed.
948    ///
949    /// ```
950    /// use jtd::{Schema, Type};
951    ///
952    /// let schema = Schema::Type {
953    ///     definitions: Default::default(),
954    ///     metadata: Default::default(),
955    ///     nullable: false,
956    ///     type_: Type::Uint8,
957    /// };
958    ///
959    /// schema.validate().expect("Invalid schema");
960    /// ```
961    ///
962    /// See the documentation for [`SchemaValidateError`] for examples of how
963    /// this function may return an error.
964    pub fn validate(&self) -> Result<(), SchemaValidateError> {
965        self._validate(None)
966    }
967
968    fn _validate(&self, root: Option<&Self>) -> Result<(), SchemaValidateError> {
969        let sub_root = root.or(Some(self));
970
971        if root.is_some() && !self.definitions().is_empty() {
972            return Err(SchemaValidateError::NonRootDefinitions);
973        }
974
975        for sub_schema in self.definitions().values() {
976            sub_schema._validate(sub_root)?;
977        }
978
979        match self {
980            Self::Empty { .. } => {}
981            Self::Ref { ref_, .. } => {
982                if !sub_root
983                    .map(|r| r.definitions())
984                    .unwrap()
985                    .contains_key(ref_)
986                {
987                    return Err(SchemaValidateError::NoSuchDefinition(ref_.clone()));
988                }
989            }
990            Self::Type { .. } => {}
991            Self::Enum { enum_, .. } => {
992                if enum_.is_empty() {
993                    return Err(SchemaValidateError::EmptyEnum);
994                }
995            }
996            Self::Elements { elements, .. } => {
997                elements._validate(sub_root)?;
998            }
999            Self::Properties {
1000                properties,
1001                optional_properties,
1002                ..
1003            } => {
1004                for key in properties.keys() {
1005                    if optional_properties.contains_key(key) {
1006                        return Err(SchemaValidateError::RepeatedProperty(key.clone()));
1007                    }
1008                }
1009
1010                for sub_schema in properties.values() {
1011                    sub_schema._validate(sub_root)?;
1012                }
1013
1014                for sub_schema in optional_properties.values() {
1015                    sub_schema._validate(sub_root)?;
1016                }
1017            }
1018            Self::Values { values, .. } => {
1019                values._validate(sub_root)?;
1020            }
1021            Self::Discriminator {
1022                discriminator,
1023                mapping,
1024                ..
1025            } => {
1026                for sub_schema in mapping.values() {
1027                    if let Self::Properties {
1028                        nullable,
1029                        properties,
1030                        optional_properties,
1031                        ..
1032                    } = sub_schema
1033                    {
1034                        if *nullable {
1035                            return Err(SchemaValidateError::NullableMapping);
1036                        }
1037
1038                        if properties.contains_key(discriminator)
1039                            || optional_properties.contains_key(discriminator)
1040                        {
1041                            return Err(SchemaValidateError::RepeatedDiscriminator(
1042                                discriminator.clone(),
1043                            ));
1044                        }
1045                    } else {
1046                        return Err(SchemaValidateError::NonPropertiesMapping);
1047                    }
1048
1049                    sub_schema._validate(sub_root)?;
1050                }
1051            }
1052        }
1053
1054        Ok(())
1055    }
1056
1057    /// Gets the schema's definitions.
1058    ///
1059    /// ```
1060    /// use jtd::{Definitions, Schema};
1061    ///
1062    /// assert_eq!(
1063    ///     &vec![(
1064    ///         "foo".to_owned(),
1065    ///         Schema::Empty {
1066    ///             definitions: Default::default(),
1067    ///             metadata: Default::default(),
1068    ///         },
1069    ///     )].into_iter().collect::<Definitions>(),
1070    ///
1071    ///      Schema::Empty {
1072    ///          definitions: vec![(
1073    ///             "foo".to_owned(),
1074    ///             Schema::Empty {
1075    ///                 definitions: Default::default(),
1076    ///                 metadata: Default::default(),
1077    ///             },
1078    ///         )].into_iter().collect(),
1079    ///          metadata: Default::default(),
1080    ///      }.definitions(),
1081    /// );
1082    /// ```
1083    pub fn definitions(&self) -> &BTreeMap<String, Schema> {
1084        match self {
1085            Self::Empty { definitions, .. } => definitions,
1086            Self::Ref { definitions, .. } => definitions,
1087            Self::Enum { definitions, .. } => definitions,
1088            Self::Type { definitions, .. } => definitions,
1089            Self::Elements { definitions, .. } => definitions,
1090            Self::Properties { definitions, .. } => definitions,
1091            Self::Values { definitions, .. } => definitions,
1092            Self::Discriminator { definitions, .. } => definitions,
1093        }
1094    }
1095
1096    /// Gets the schema's metadata.
1097    ///
1098    /// ```
1099    /// use jtd::{Metadata, Schema};
1100    /// use serde_json::json;
1101    ///
1102    /// assert_eq!(
1103    ///     &vec![(
1104    ///         "foo".to_owned(),
1105    ///         json!("bar"),
1106    ///     )].into_iter().collect::<Metadata>(),
1107    ///
1108    ///     Schema::Empty {
1109    ///         definitions: Default::default(),
1110    ///         metadata: vec![(
1111    ///            "foo".to_owned(),
1112    ///            json!("bar"),
1113    ///        )].into_iter().collect(),
1114    ///     }.metadata(),
1115    /// );
1116    /// ```
1117    pub fn metadata(&self) -> &BTreeMap<String, Value> {
1118        match self {
1119            Self::Empty { metadata, .. } => metadata,
1120            Self::Ref { metadata, .. } => metadata,
1121            Self::Enum { metadata, .. } => metadata,
1122            Self::Type { metadata, .. } => metadata,
1123            Self::Elements { metadata, .. } => metadata,
1124            Self::Properties { metadata, .. } => metadata,
1125            Self::Values { metadata, .. } => metadata,
1126            Self::Discriminator { metadata, .. } => metadata,
1127        }
1128    }
1129
1130    /// Gets whether the schema is nullable.
1131    ///
1132    /// For [`Schema::Empty`], this always returns true. For all other forms,
1133    /// this fetches the `nullable` property.
1134    ///
1135    /// ```
1136    /// use jtd::{Schema, Type};
1137    ///
1138    /// assert!(
1139    ///     Schema::Empty {
1140    ///         definitions: Default::default(),
1141    ///         metadata: Default::default(),
1142    ///     }.nullable(),
1143    /// );
1144    ///
1145    /// assert!(
1146    ///     !Schema::Type {
1147    ///         definitions: Default::default(),
1148    ///         metadata: Default::default(),
1149    ///         nullable: false,
1150    ///         type_: Type::Uint8,
1151    ///     }.nullable(),
1152    /// );
1153    /// ```
1154    pub fn nullable(&self) -> bool {
1155        match self {
1156            Self::Empty { .. } => true,
1157            Self::Ref { nullable, .. } => *nullable,
1158            Self::Enum { nullable, .. } => *nullable,
1159            Self::Type { nullable, .. } => *nullable,
1160            Self::Elements { nullable, .. } => *nullable,
1161            Self::Properties { nullable, .. } => *nullable,
1162            Self::Values { nullable, .. } => *nullable,
1163            Self::Discriminator { nullable, .. } => *nullable,
1164        }
1165    }
1166}
1167
1168#[cfg(test)]
1169mod tests {
1170    use crate::{Schema, SerdeSchema};
1171
1172    #[test]
1173    fn invalid_schemas() {
1174        use std::collections::BTreeMap;
1175
1176        let test_cases: BTreeMap<String, serde_json::Value> = serde_json::from_str(include_str!(
1177            "../json-typedef-spec/tests/invalid_schemas.json"
1178        ))
1179        .expect("parse invalid_schemas.json");
1180
1181        for (test_case_name, test_case) in test_cases {
1182            if let Ok(serde_schema) = serde_json::from_value::<SerdeSchema>(test_case) {
1183                if let Ok(schema) = Schema::from_serde_schema(serde_schema) {
1184                    if schema.validate().is_ok() {
1185                        panic!(
1186                            "failed to detect invalid schema: {}, got: {:?}",
1187                            test_case_name, schema
1188                        );
1189                    }
1190                }
1191            }
1192        }
1193    }
1194
1195    #[test]
1196    fn valid_schemas() {
1197        use std::collections::BTreeMap;
1198
1199        #[derive(serde::Deserialize)]
1200        struct TestCase {
1201            schema: serde_json::Value,
1202        }
1203
1204        let test_cases: BTreeMap<String, TestCase> =
1205            serde_json::from_str(include_str!("../json-typedef-spec/tests/validation.json"))
1206                .expect("parse validation.json");
1207
1208        for (test_case_name, test_case) in test_cases {
1209            let serde_schema =
1210                serde_json::from_value::<SerdeSchema>(test_case.schema).expect(&test_case_name);
1211            let schema = Schema::from_serde_schema(serde_schema).expect(&test_case_name);
1212            schema.validate().expect(&test_case_name);
1213        }
1214    }
1215}