1use crate::errors::JddfError;
8use failure::{bail, Error};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::{HashMap, HashSet};
12
13#[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    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    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    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    pub fn is_root(&self) -> bool {
337        self.defs.is_some()
338    }
339
340    pub fn definitions(&self) -> &Option<HashMap<String, Schema>> {
344        &self.defs
345    }
346
347    pub fn form(&self) -> &Form {
349        &self.form
350    }
351
352    pub fn extra(&self) -> &HashMap<String, Value> {
358        &self.extra
359    }
360}
361
362#[derive(Clone, Debug, PartialEq)]
364pub enum Form {
365    Empty,
369
370    Ref(String),
376
377    Type(Type),
381
382    Enum(HashSet<String>),
387
388    Elements(Schema),
393
394    Properties {
409        required: HashMap<String, Schema>,
410        optional: HashMap<String, Schema>,
411        allow_additional: bool,
412        has_required: bool,
413    },
414
415    Values(Schema),
420
421    Discriminator(String, HashMap<String, Schema>),
431}
432
433#[derive(Clone, Debug, PartialEq, Eq, Hash)]
439pub enum Type {
440    Boolean,
442
443    Float32,
446
447    Float64,
450
451    Int8,
453
454    Uint8,
456
457    Int16,
459
460    Uint16,
462
463    Int32,
465
466    Uint32,
468
469    String,
471
472    Timestamp,
474}
475
476#[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#[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}