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}