jddf/
schema.rs

1//! JDDF schema representations.
2//!
3//! This module provides both an abstract ([`Schema`](struct.Schema.html)) and a
4//! serializable/deserializable ([`SerdeSchema`](struct.SerdeSchema.html))
5//! representation of JDDF schemas.
6
7use crate::errors::JddfError;
8use failure::{bail, Error};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::{HashMap, HashSet};
12
13/// An abstract representation of a JDDF schema.
14///
15/// This struct is meant for use by validators, code generators, or other
16/// high-level processors of schemas. For serialization and deserialization of
17/// schemas, instead use [`Serde`](struct.Serde.html).
18#[derive(Clone, PartialEq, Debug)]
19pub struct Schema {
20    defs: Option<HashMap<String, Schema>>,
21    form: Box<Form>,
22    extra: HashMap<String, Value>,
23}
24
25impl Schema {
26    /// Construct a new schema from its constituent parts.
27    ///
28    /// `defs` should be present (i.e. not `None`) if and only if the
29    /// constructed schema is a root one. This invariant is not enforced, but
30    /// many users of this crate will presume that root schemas have definitions
31    /// they can unwrap. Likewise, some tooling will assume that any schema
32    /// which has non-`None` definitions are root schemas.
33    pub fn from_parts(
34        defs: Option<HashMap<String, Schema>>,
35        form: Box<Form>,
36        extra: HashMap<String, Value>,
37    ) -> Schema {
38        Schema { defs, form, extra }
39    }
40
41    /// Construct a new, root schema from a `Serde`.
42    pub fn from_serde(serde_schema: Serde) -> Result<Self, Error> {
43        let schema = Self::_from_serde(serde_schema, true)?;
44
45        Self::check_refs(&schema.defs.as_ref().unwrap(), &schema)?;
46        for sub_schema in schema.defs.as_ref().unwrap().values() {
47            Self::check_refs(&schema.defs.as_ref().unwrap(), &sub_schema)?;
48        }
49
50        Ok(schema)
51    }
52
53    fn _from_serde(serde_schema: Serde, is_root: bool) -> Result<Self, Error> {
54        let defs = if is_root {
55            let mut defs = HashMap::new();
56            for (name, sub_schema) in serde_schema.defs.unwrap_or_default() {
57                defs.insert(name, Self::_from_serde(sub_schema, false)?);
58            }
59            Some(defs)
60        } else {
61            if serde_schema.defs.is_some() {
62                bail!(JddfError::InvalidForm);
63            } else {
64                None
65            }
66        };
67
68        let mut form = Form::Empty;
69
70        if let Some(rxf) = serde_schema.rxf {
71            form = Form::Ref(rxf);
72        }
73
74        if let Some(typ) = serde_schema.typ {
75            if form != Form::Empty {
76                bail!(JddfError::InvalidForm);
77            }
78
79            form = Form::Type(match typ.as_ref() {
80                "boolean" => Type::Boolean,
81                "float32" => Type::Float32,
82                "float64" => Type::Float64,
83                "int8" => Type::Int8,
84                "uint8" => Type::Uint8,
85                "int16" => Type::Int16,
86                "uint16" => Type::Uint16,
87                "int32" => Type::Int32,
88                "uint32" => Type::Uint32,
89                "string" => Type::String,
90                "timestamp" => Type::Timestamp,
91                _ => bail!(JddfError::InvalidForm),
92            });
93        }
94
95        if let Some(enm) = serde_schema.enm {
96            if form != Form::Empty {
97                bail!(JddfError::InvalidForm);
98            }
99
100            let mut values = HashSet::new();
101            for val in enm {
102                if values.contains(&val) {
103                    bail!(JddfError::InvalidForm);
104                } else {
105                    values.insert(val);
106                }
107            }
108
109            if values.is_empty() {
110                bail!(JddfError::InvalidForm);
111            }
112
113            form = Form::Enum(values);
114        }
115
116        if let Some(elements) = serde_schema.elems {
117            if form != Form::Empty {
118                bail!(JddfError::InvalidForm);
119            }
120
121            form = Form::Elements(Self::_from_serde(*elements, false)?);
122        }
123
124        if serde_schema.props.is_some() || serde_schema.opt_props.is_some() {
125            if form != Form::Empty {
126                bail!(JddfError::InvalidForm);
127            }
128
129            let allow_additional = serde_schema.additional_props == Some(true);
130            let has_required = serde_schema.props.is_some();
131
132            let mut required = HashMap::new();
133            for (name, sub_schema) in serde_schema.props.unwrap_or_default() {
134                required.insert(name, Self::_from_serde(sub_schema, false)?);
135            }
136
137            let mut optional = HashMap::new();
138            for (name, sub_schema) in serde_schema.opt_props.unwrap_or_default() {
139                if required.contains_key(&name) {
140                    bail!(JddfError::AmbiguousProperty { property: name });
141                }
142
143                optional.insert(name, Self::_from_serde(sub_schema, false)?);
144            }
145
146            form = Form::Properties {
147                required,
148                optional,
149                has_required,
150                allow_additional,
151            };
152        }
153
154        if let Some(values) = serde_schema.values {
155            if form != Form::Empty {
156                bail!(JddfError::InvalidForm);
157            }
158
159            form = Form::Values(Self::_from_serde(*values, false)?);
160        }
161
162        if let Some(discriminator) = serde_schema.discriminator {
163            if form != Form::Empty {
164                bail!(JddfError::InvalidForm);
165            }
166
167            let mut mapping = HashMap::new();
168            for (name, sub_schema) in discriminator.mapping {
169                let sub_schema = Self::_from_serde(sub_schema, false)?;
170                match sub_schema.form.as_ref() {
171                    Form::Properties {
172                        required, optional, ..
173                    } => {
174                        if required.contains_key(&discriminator.tag)
175                            || optional.contains_key(&discriminator.tag)
176                        {
177                            bail!(JddfError::AmbiguousProperty {
178                                property: discriminator.tag,
179                            });
180                        }
181                    }
182                    _ => bail!(JddfError::InvalidForm),
183                };
184
185                mapping.insert(name, sub_schema);
186            }
187
188            form = Form::Discriminator(discriminator.tag, mapping);
189        }
190
191        Ok(Self {
192            defs,
193            form: Box::new(form),
194            extra: serde_schema.extra,
195        })
196    }
197
198    fn check_refs(defs: &HashMap<String, Schema>, schema: &Schema) -> Result<(), Error> {
199        match schema.form() {
200            Form::Ref(ref def) => {
201                if !defs.contains_key(def) {
202                    bail!(JddfError::NoSuchDefinition {
203                        definition: def.clone()
204                    })
205                }
206            }
207            Form::Elements(ref schema) => {
208                Self::check_refs(defs, schema)?;
209            }
210            Form::Properties {
211                ref required,
212                ref optional,
213                ..
214            } => {
215                for schema in required.values() {
216                    Self::check_refs(defs, schema)?;
217                }
218
219                for schema in optional.values() {
220                    Self::check_refs(defs, schema)?;
221                }
222            }
223            Form::Values(ref schema) => {
224                Self::check_refs(defs, schema)?;
225            }
226            Form::Discriminator(_, ref mapping) => {
227                for schema in mapping.values() {
228                    Self::check_refs(defs, schema)?;
229                }
230            }
231            _ => {}
232        };
233
234        Ok(())
235    }
236
237    /// Convert this schema into a `Serde`.
238    pub fn into_serde(self) -> Serde {
239        let mut out = Serde::default();
240
241        if let Some(defs) = self.defs {
242            let mut out_defs = HashMap::new();
243            for (name, value) in defs {
244                out_defs.insert(name, value.into_serde());
245            }
246
247            out.defs = Some(out_defs);
248        }
249
250        match *self.form {
251            Form::Empty => {}
252            Form::Ref(def) => {
253                out.rxf = Some(def);
254            }
255            Form::Type(Type::Boolean) => {
256                out.typ = Some("boolean".to_owned());
257            }
258            Form::Type(Type::Float32) => {
259                out.typ = Some("float32".to_owned());
260            }
261            Form::Type(Type::Float64) => {
262                out.typ = Some("float64".to_owned());
263            }
264            Form::Type(Type::Int8) => {
265                out.typ = Some("int8".to_owned());
266            }
267            Form::Type(Type::Uint8) => {
268                out.typ = Some("uint8".to_owned());
269            }
270            Form::Type(Type::Int16) => {
271                out.typ = Some("int16".to_owned());
272            }
273            Form::Type(Type::Uint16) => {
274                out.typ = Some("uint16".to_owned());
275            }
276            Form::Type(Type::Int32) => {
277                out.typ = Some("int32".to_owned());
278            }
279            Form::Type(Type::Uint32) => {
280                out.typ = Some("uint32".to_owned());
281            }
282            Form::Type(Type::String) => {
283                out.typ = Some("string".to_owned());
284            }
285            Form::Type(Type::Timestamp) => {
286                out.typ = Some("timestamp".to_owned());
287            }
288            Form::Enum(vals) => {
289                out.enm = Some(vals.into_iter().collect());
290            }
291            Form::Elements(sub_schema) => out.elems = Some(Box::new(sub_schema.into_serde())),
292            Form::Properties {
293                required,
294                optional,
295                has_required,
296                ..
297            } => {
298                if has_required || !required.is_empty() {
299                    out.props = Some(
300                        required
301                            .into_iter()
302                            .map(|(k, v)| (k, v.into_serde()))
303                            .collect(),
304                    );
305                }
306
307                if !has_required || !optional.is_empty() {
308                    out.opt_props = Some(
309                        optional
310                            .into_iter()
311                            .map(|(k, v)| (k, v.into_serde()))
312                            .collect(),
313                    );
314                }
315            }
316            Form::Values(sub_schema) => out.values = Some(Box::new(sub_schema.into_serde())),
317            Form::Discriminator(tag, mapping) => {
318                out.discriminator = Some(SerdeDiscriminator {
319                    tag,
320                    mapping: mapping
321                        .into_iter()
322                        .map(|(k, v)| (k, v.into_serde()))
323                        .collect(),
324                });
325            }
326        }
327
328        out.extra = self.extra;
329        out
330    }
331
332    /// Is this schema a root schema?
333    ///
334    /// Under the hood, this is entirely equivalent to checking whether
335    /// `definitions().is_some()`.
336    pub fn is_root(&self) -> bool {
337        self.defs.is_some()
338    }
339
340    /// Get the definitions associated with this schema.
341    ///
342    /// If this schema is non-root, this returns None.
343    pub fn definitions(&self) -> &Option<HashMap<String, Schema>> {
344        &self.defs
345    }
346
347    /// Get the form of the schema.
348    pub fn form(&self) -> &Form {
349        &self.form
350    }
351
352    /// Get extra data associated with this schema.
353    ///
354    /// Essentially, this function returns a JSON object of properties that
355    /// aren't JDDF keywords, but which were included in the schema's JSON. You
356    /// might use these nonstandard fields to implement custom behavior.
357    pub fn extra(&self) -> &HashMap<String, Value> {
358        &self.extra
359    }
360}
361
362/// The various forms which a schema may take on, and their respective data.
363#[derive(Clone, Debug, PartialEq)]
364pub enum Form {
365    /// The empty form.
366    ///
367    /// This schema accepts all data.
368    Empty,
369
370    /// The ref form.
371    ///
372    /// This schema refers to another schema, and does whatever that other
373    /// schema does. The contained string is the name of the definition of the
374    /// referred-to schema -- it is an index into the `defs` of the root schema.
375    Ref(String),
376
377    /// The type form.
378    ///
379    /// This schema asserts that the data is one of the primitive types.
380    Type(Type),
381
382    /// The enum form.
383    ///
384    /// This schema asserts that the data is a string, and that it is one of a
385    /// set of values.
386    Enum(HashSet<String>),
387
388    /// The elements form.
389    ///
390    /// This schema asserts that the instance is an array, and that every
391    /// element of the array matches a given schema.
392    Elements(Schema),
393
394    /// The properties form.
395    ///
396    /// This schema asserts that the instance is an object, and that the
397    /// properties all satisfy their respective schemas.
398    ///
399    /// `required` is the set of required properties and their schemas.
400    /// `optional` is the set of optional properties and their schemas.
401    /// `allow_additional` indicates whether it's acceptable for the instance to
402    /// have properties other than those mentioned in `required` and `optional`.
403    ///
404    /// `has_required` indicates whether `properties` exists on the schema. This
405    /// allows implementations to distinguish the case of an empty `properties`
406    /// field from an omitted one. This is necessary for tooling which wants to
407    /// link to a particular part of a schema in JSON form.
408    Properties {
409        required: HashMap<String, Schema>,
410        optional: HashMap<String, Schema>,
411        allow_additional: bool,
412        has_required: bool,
413    },
414
415    /// The values form.
416    ///
417    /// This schema asserts that the instance is an object, and that all the
418    /// values in the object all satisfy the same schema.
419    Values(Schema),
420
421    /// The discriminator form.
422    ///
423    /// This schema asserts that the instance is an object, and that it has a
424    /// "tag" property. The value of that tag must be one of the expected
425    /// "mapping" keys, and the corresponding mapping value is a schema that the
426    /// instance is expected to satisfy.
427    ///
428    /// The first parameter is the name of the tag property. The second
429    /// parameter is the mapping from tag values to their corresponding schemas.
430    Discriminator(String, HashMap<String, Schema>),
431}
432
433/// The values that the "type" keyword may check for.
434///
435/// In a certain sense, you can consider these types to be JSON's "primitive"
436/// types, with the remaining two types, arrays and objects, being the "complex"
437/// types covered by other keywords.
438#[derive(Clone, Debug, PartialEq, Eq, Hash)]
439pub enum Type {
440    /// The "true" or "false" JSON values.
441    Boolean,
442
443    /// A floating-point number. Signals the intention that the data is meant to
444    /// be a single-precision float.
445    Float32,
446
447    /// A floating-point number. Signals the intention that the data is meant to
448    /// be a double-precision float.
449    Float64,
450
451    /// An integer in the range covered by `i8`.
452    Int8,
453
454    /// An integer in the range covered by `u8`.
455    Uint8,
456
457    /// An integer in the range covered by `i16`.
458    Int16,
459
460    /// An integer in the range covered by `u16`.
461    Uint16,
462
463    /// An integer in the range covered by `i32`.
464    Int32,
465
466    /// An integer in the range covered by `u32`.
467    Uint32,
468
469    /// Any JSON string.
470    String,
471
472    /// A string encoding an RFC3339 timestamp.
473    Timestamp,
474}
475
476/// A serialization/deserialization-friendly representation of a JDDF schema.
477///
478/// This struct is meant for use with the `serde` crate. It is excellent for
479/// parsing from various data formats, but does not enforce all the semantic
480/// rules about how schemas must be formed. For that, consider converting
481/// instances of `Serde` into [`Schema`](struct.Schema.html) using
482/// [`Schema::from_serde`](struct.Schema.html#method.from_serde).
483#[derive(Debug, PartialEq, Deserialize, Serialize, Default, Clone)]
484pub struct Serde {
485    #[serde(skip_serializing_if = "Option::is_none")]
486    #[serde(rename = "definitions")]
487    pub defs: Option<HashMap<String, Serde>>,
488
489    #[serde(skip_serializing_if = "Option::is_none")]
490    #[serde(rename = "additionalProperties")]
491    pub additional_props: Option<bool>,
492
493    #[serde(skip_serializing_if = "Option::is_none")]
494    #[serde(rename = "ref")]
495    pub rxf: Option<String>,
496
497    #[serde(skip_serializing_if = "Option::is_none")]
498    #[serde(rename = "type")]
499    pub typ: Option<String>,
500
501    #[serde(skip_serializing_if = "Option::is_none")]
502    #[serde(rename = "enum")]
503    pub enm: Option<Vec<String>>,
504
505    #[serde(skip_serializing_if = "Option::is_none")]
506    #[serde(rename = "elements")]
507    pub elems: Option<Box<Serde>>,
508
509    #[serde(skip_serializing_if = "Option::is_none")]
510    #[serde(rename = "properties")]
511    pub props: Option<HashMap<String, Serde>>,
512
513    #[serde(skip_serializing_if = "Option::is_none")]
514    #[serde(rename = "optionalProperties")]
515    pub opt_props: Option<HashMap<String, Serde>>,
516
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub values: Option<Box<Serde>>,
519
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub discriminator: Option<SerdeDiscriminator>,
522
523    #[serde(skip_serializing_if = "HashMap::is_empty")]
524    #[serde(flatten)]
525    pub extra: HashMap<String, Value>,
526}
527
528/// A serialization/deserialization-friendly representation of a JDDF
529/// discriminator.
530///
531/// This struct is useful mostly in the context of
532/// [`SerdeSchema`](struct.SerdeSchema.html).
533#[derive(Debug, PartialEq, Deserialize, Serialize, Default, Clone)]
534pub struct SerdeDiscriminator {
535    #[serde(rename = "tag")]
536    pub tag: String,
537    pub mapping: HashMap<String, Serde>,
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543    use serde_json::json;
544
545    #[test]
546    fn roundtrip_json() {
547        let data = r#"{
548  "definitions": {
549    "a": {}
550  },
551  "additionalProperties": true,
552  "ref": "http://example.com/bar",
553  "type": "foo",
554  "enum": [
555    "FOO",
556    "BAR"
557  ],
558  "elements": {},
559  "properties": {
560    "a": {}
561  },
562  "optionalProperties": {
563    "a": {}
564  },
565  "values": {},
566  "discriminator": {
567    "tag": "foo",
568    "mapping": {
569      "a": {}
570    }
571  },
572  "extra": "foo"
573}"#;
574
575        let parsed: Serde = serde_json::from_str(data).expect("failed to parse json");
576        assert_eq!(
577            parsed,
578            Serde {
579                rxf: Some("http://example.com/bar".to_owned()),
580                defs: Some(
581                    [("a".to_owned(), Serde::default())]
582                        .iter()
583                        .cloned()
584                        .collect()
585                ),
586                additional_props: Some(true),
587                typ: Some("foo".to_owned()),
588                enm: Some(vec!["FOO".to_owned(), "BAR".to_owned()]),
589                elems: Some(Box::new(Serde::default())),
590                props: Some(
591                    [("a".to_owned(), Serde::default())]
592                        .iter()
593                        .cloned()
594                        .collect()
595                ),
596                opt_props: Some(
597                    [("a".to_owned(), Serde::default())]
598                        .iter()
599                        .cloned()
600                        .collect()
601                ),
602                values: Some(Box::new(Serde::default())),
603                discriminator: Some(SerdeDiscriminator {
604                    tag: "foo".to_owned(),
605                    mapping: [("a".to_owned(), Serde::default())]
606                        .iter()
607                        .cloned()
608                        .collect(),
609                }),
610                extra: [("extra".to_owned(), json!("foo"))]
611                    .iter()
612                    .cloned()
613                    .collect(),
614            }
615        );
616
617        let round_trip = serde_json::to_string_pretty(&parsed).expect("failed to serialize json");
618        assert_eq!(round_trip, data);
619    }
620
621    #[test]
622    fn from_serde_root() {
623        assert_eq!(
624            Schema::from_serde(
625                serde_json::from_value(json!({
626                    "definitions": {
627                        "a": { "type": "boolean" }
628                    }
629                }))
630                .unwrap()
631            )
632            .unwrap(),
633            Schema {
634                defs: Some(
635                    [(
636                        "a".to_owned(),
637                        Schema {
638                            defs: None,
639                            form: Box::new(Form::Type(Type::Boolean)),
640                            extra: HashMap::new(),
641                        },
642                    )]
643                    .iter()
644                    .cloned()
645                    .collect()
646                ),
647                form: Box::new(Form::Empty),
648                extra: HashMap::new(),
649            }
650        );
651    }
652
653    #[test]
654    fn from_serde_empty() {
655        assert_eq!(
656            Schema::from_serde(serde_json::from_value(json!({})).unwrap()).unwrap(),
657            Schema {
658                defs: Some(HashMap::new()),
659                form: Box::new(Form::Empty),
660                extra: HashMap::new(),
661            }
662        );
663    }
664
665    #[test]
666    fn from_serde_extra() {
667        assert_eq!(
668            Schema::from_serde(serde_json::from_value(json!({ "foo": "bar" })).unwrap()).unwrap(),
669            Schema {
670                defs: Some(HashMap::new()),
671                form: Box::new(Form::Empty),
672                extra: serde_json::from_value(json!({ "foo": "bar" })).unwrap(),
673            }
674        );
675    }
676
677    #[test]
678    fn from_serde_ref() {
679        assert_eq!(
680            Schema::from_serde(
681                serde_json::from_value(json!({
682                    "definitions": {
683                        "a": { "type": "boolean" }
684                    },
685                    "ref": "a",
686                }))
687                .unwrap()
688            )
689            .unwrap(),
690            Schema {
691                defs: Some(
692                    [(
693                        "a".to_owned(),
694                        Schema {
695                            defs: None,
696                            form: Box::new(Form::Type(Type::Boolean)),
697                            extra: HashMap::new(),
698                        },
699                    )]
700                    .iter()
701                    .cloned()
702                    .collect()
703                ),
704                form: Box::new(Form::Ref("a".to_owned())),
705                extra: HashMap::new(),
706            }
707        );
708
709        assert!(Schema::from_serde(
710            serde_json::from_value(json!({
711                "definitions": {
712                    "a": { "type": "boolean" }
713                },
714                "ref": "",
715            }))
716            .unwrap()
717        )
718        .is_err());
719    }
720
721    #[test]
722    fn from_serde_type() {
723        assert_eq!(
724            Schema::from_serde(
725                serde_json::from_value(json!({
726                    "type": "boolean",
727                }))
728                .unwrap()
729            )
730            .unwrap(),
731            Schema {
732                defs: Some(HashMap::new()),
733                form: Box::new(Form::Type(Type::Boolean)),
734                extra: HashMap::new(),
735            },
736        );
737
738        assert_eq!(
739            Schema::from_serde(
740                serde_json::from_value(json!({
741                    "type": "float64",
742                }))
743                .unwrap()
744            )
745            .unwrap(),
746            Schema {
747                defs: Some(HashMap::new()),
748                form: Box::new(Form::Type(Type::Float64)),
749                extra: HashMap::new(),
750            },
751        );
752
753        assert_eq!(
754            Schema::from_serde(
755                serde_json::from_value(json!({
756                    "type": "string",
757                }))
758                .unwrap()
759            )
760            .unwrap(),
761            Schema {
762                defs: Some(HashMap::new()),
763                form: Box::new(Form::Type(Type::String)),
764                extra: HashMap::new(),
765            },
766        );
767
768        assert_eq!(
769            Schema::from_serde(
770                serde_json::from_value(json!({
771                    "type": "timestamp",
772                }))
773                .unwrap()
774            )
775            .unwrap(),
776            Schema {
777                defs: Some(HashMap::new()),
778                form: Box::new(Form::Type(Type::Timestamp)),
779                extra: HashMap::new(),
780            },
781        );
782
783        assert!(Schema::from_serde(
784            serde_json::from_value(json!({
785                "type": "nonsense",
786            }))
787            .unwrap()
788        )
789        .is_err());
790    }
791
792    #[test]
793    fn from_serde_enum() {
794        assert_eq!(
795            Schema::from_serde(
796                serde_json::from_value(json!({
797                    "enum": ["FOO", "BAR"],
798                }))
799                .unwrap()
800            )
801            .unwrap(),
802            Schema {
803                defs: Some(HashMap::new()),
804                form: Box::new(Form::Enum(
805                    vec!["FOO".to_owned(), "BAR".to_owned()]
806                        .iter()
807                        .cloned()
808                        .collect()
809                )),
810                extra: HashMap::new(),
811            },
812        );
813
814        assert!(Schema::from_serde(
815            serde_json::from_value(json!({
816                "enum": [],
817            }))
818            .unwrap()
819        )
820        .is_err());
821
822        assert!(Schema::from_serde(
823            serde_json::from_value(json!({
824                "enum": ["FOO", "FOO"],
825            }))
826            .unwrap()
827        )
828        .is_err());
829    }
830
831    #[test]
832    fn from_serde_elements() {
833        assert_eq!(
834            Schema::from_serde(
835                serde_json::from_value(json!({
836                    "elements": {
837                        "type": "boolean",
838                    },
839                }))
840                .unwrap()
841            )
842            .unwrap(),
843            Schema {
844                defs: Some(HashMap::new()),
845                form: Box::new(Form::Elements(Schema {
846                    defs: None,
847                    form: Box::new(Form::Type(Type::Boolean)),
848                    extra: HashMap::new(),
849                })),
850                extra: HashMap::new(),
851            }
852        );
853    }
854
855    #[test]
856    fn from_serde_properties() {
857        assert_eq!(
858            Schema::from_serde(
859                serde_json::from_value(json!({
860                    "additionalProperties": true,
861                    "properties": {
862                        "a": { "type": "boolean" },
863                    },
864                    "optionalProperties": {
865                        "b": { "type": "boolean" },
866                    },
867                }))
868                .unwrap()
869            )
870            .unwrap(),
871            Schema {
872                defs: Some(HashMap::new()),
873                form: Box::new(Form::Properties {
874                    required: [(
875                        "a".to_owned(),
876                        Schema {
877                            defs: None,
878                            form: Box::new(Form::Type(Type::Boolean)),
879                            extra: HashMap::new(),
880                        }
881                    )]
882                    .iter()
883                    .cloned()
884                    .collect(),
885                    optional: [(
886                        "b".to_owned(),
887                        Schema {
888                            defs: None,
889                            form: Box::new(Form::Type(Type::Boolean)),
890                            extra: HashMap::new(),
891                        }
892                    )]
893                    .iter()
894                    .cloned()
895                    .collect(),
896                    has_required: true,
897                    allow_additional: true,
898                }),
899                extra: HashMap::new(),
900            }
901        );
902
903        assert_eq!(
904            Schema::from_serde(
905                serde_json::from_value(json!({
906                    "optionalProperties": {
907                        "b": { "type": "boolean" },
908                    },
909                }))
910                .unwrap()
911            )
912            .unwrap(),
913            Schema {
914                defs: Some(HashMap::new()),
915                form: Box::new(Form::Properties {
916                    required: HashMap::new(),
917                    optional: [(
918                        "b".to_owned(),
919                        Schema {
920                            defs: None,
921                            form: Box::new(Form::Type(Type::Boolean)),
922                            extra: HashMap::new(),
923                        }
924                    )]
925                    .iter()
926                    .cloned()
927                    .collect(),
928                    has_required: false,
929                    allow_additional: false,
930                }),
931                extra: HashMap::new(),
932            }
933        );
934
935        assert!(Schema::from_serde(
936            serde_json::from_value(json!({
937                "properties": {
938                    "a": { "type": "boolean" },
939                },
940                "optionalProperties": {
941                    "a": { "type": "boolean" },
942                },
943            }))
944            .unwrap()
945        )
946        .is_err());
947    }
948
949    #[test]
950    fn from_serde_values() {
951        assert_eq!(
952            Schema::from_serde(
953                serde_json::from_value(json!({
954                    "values": {
955                        "type": "boolean",
956                    },
957                }))
958                .unwrap()
959            )
960            .unwrap(),
961            Schema {
962                defs: Some(HashMap::new()),
963                form: Box::new(Form::Values(Schema {
964                    defs: None,
965                    form: Box::new(Form::Type(Type::Boolean)),
966                    extra: HashMap::new(),
967                })),
968                extra: HashMap::new(),
969            }
970        );
971    }
972
973    #[test]
974    fn from_serde_discriminator() {
975        assert_eq!(
976            Schema::from_serde(
977                serde_json::from_value(json!({
978                    "discriminator": {
979                        "tag": "foo",
980                        "mapping": {
981                            "a": { "properties": {} },
982                            "b": { "properties": {} },
983                        },
984                    },
985                }))
986                .unwrap()
987            )
988            .unwrap(),
989            Schema {
990                defs: Some(HashMap::new()),
991                form: Box::new(Form::Discriminator(
992                    "foo".to_owned(),
993                    [
994                        (
995                            "a".to_owned(),
996                            Schema {
997                                defs: None,
998                                form: Box::new(Form::Properties {
999                                    required: HashMap::new(),
1000                                    optional: HashMap::new(),
1001                                    has_required: true,
1002                                    allow_additional: false,
1003                                }),
1004                                extra: HashMap::new(),
1005                            }
1006                        ),
1007                        (
1008                            "b".to_owned(),
1009                            Schema {
1010                                defs: None,
1011                                form: Box::new(Form::Properties {
1012                                    required: HashMap::new(),
1013                                    optional: HashMap::new(),
1014                                    has_required: true,
1015                                    allow_additional: false,
1016                                }),
1017                                extra: HashMap::new(),
1018                            }
1019                        )
1020                    ]
1021                    .iter()
1022                    .cloned()
1023                    .collect(),
1024                )),
1025                extra: HashMap::new(),
1026            }
1027        );
1028
1029        assert!(Schema::from_serde(
1030            serde_json::from_value(json!({
1031                "discriminator": {
1032                    "tag": "foo",
1033                    "mapping": {
1034                        "a": { "type": "boolean" },
1035                    }
1036                },
1037            }))
1038            .unwrap()
1039        )
1040        .is_err());
1041
1042        assert!(Schema::from_serde(
1043            serde_json::from_value(json!({
1044                "discriminator": {
1045                    "tag": "foo",
1046                    "mapping": {
1047                        "a": {
1048                            "properties": {
1049                                "foo": { "type": "boolean" },
1050                            },
1051                        },
1052                    },
1053                },
1054            }))
1055            .unwrap()
1056        )
1057        .is_err());
1058    }
1059}