1use cedar_policy_core::entities::CedarValueJson;
18use serde::{
19 de::{MapAccess, Visitor},
20 Deserialize, Serialize,
21};
22use serde_with::serde_as;
23use smol_str::SmolStr;
24use std::collections::{BTreeMap, HashMap, HashSet};
25
26use crate::Result;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(transparent)]
35pub struct SchemaFragment(
36 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
37 pub HashMap<SmolStr, NamespaceDefinition>,
38);
39
40impl SchemaFragment {
41 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
44 serde_json::from_value(json).map_err(Into::into)
45 }
46
47 pub fn from_file(file: impl std::io::Read) -> Result<Self> {
49 serde_json::from_reader(file).map_err(Into::into)
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55#[serde_as]
56#[serde(deny_unknown_fields)]
57#[doc(hidden)]
58pub struct NamespaceDefinition {
59 #[serde(default)]
60 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
61 #[serde(rename = "commonTypes")]
62 pub common_types: HashMap<SmolStr, SchemaType>,
63 #[serde(rename = "entityTypes")]
64 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
65 pub entity_types: HashMap<SmolStr, EntityType>,
66 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
67 pub actions: HashMap<SmolStr, ActionType>,
68}
69
70impl NamespaceDefinition {
71 pub fn new(
72 entity_types: impl IntoIterator<Item = (SmolStr, EntityType)>,
73 actions: impl IntoIterator<Item = (SmolStr, ActionType)>,
74 ) -> Self {
75 Self {
76 common_types: HashMap::new(),
77 entity_types: entity_types.into_iter().collect(),
78 actions: actions.into_iter().collect(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[serde(deny_unknown_fields)]
88pub struct EntityType {
89 #[serde(default)]
90 #[serde(rename = "memberOfTypes")]
91 pub member_of_types: Vec<SmolStr>,
92 #[serde(default)]
93 pub shape: AttributesOrContext,
94}
95
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97#[serde(transparent)]
98pub struct AttributesOrContext(
99 pub SchemaType,
102);
103
104impl AttributesOrContext {
105 pub fn into_inner(self) -> SchemaType {
106 self.0
107 }
108}
109
110impl Default for AttributesOrContext {
111 fn default() -> Self {
112 Self(SchemaType::Type(SchemaTypeVariant::Record {
113 attributes: BTreeMap::new(),
114 additional_attributes: partial_schema_default(),
115 }))
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122#[serde(deny_unknown_fields)]
123pub struct ActionType {
124 #[serde(default)]
128 pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
129 #[serde(default)]
130 #[serde(rename = "appliesTo")]
131 pub applies_to: Option<ApplySpec>,
132 #[serde(default)]
133 #[serde(rename = "memberOf")]
134 pub member_of: Option<Vec<ActionEntityUID>>,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146#[serde(deny_unknown_fields)]
147pub struct ApplySpec {
148 #[serde(default)]
149 #[serde(rename = "resourceTypes")]
150 pub resource_types: Option<Vec<SmolStr>>,
151 #[serde(default)]
152 #[serde(rename = "principalTypes")]
153 pub principal_types: Option<Vec<SmolStr>>,
154 #[serde(default)]
155 pub context: AttributesOrContext,
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159#[serde(deny_unknown_fields)]
160pub struct ActionEntityUID {
161 pub id: SmolStr,
162
163 #[serde(rename = "type")]
164 #[serde(default)]
165 pub ty: Option<SmolStr>,
166}
167
168impl ActionEntityUID {
169 pub fn default_type(id: SmolStr) -> Self {
170 Self { id, ty: None }
171 }
172}
173
174impl std::fmt::Display for ActionEntityUID {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 if let Some(ty) = &self.ty {
177 write!(f, "{}::", ty)?
178 } else {
179 write!(f, "Action::")?
180 }
181 write!(f, "\"{}\"", self.id)
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
188#[serde(untagged)]
193pub enum SchemaType {
194 Type(SchemaTypeVariant),
195 TypeDef {
196 #[serde(rename = "type")]
197 type_name: SmolStr,
198 },
199}
200
201impl<'de> Deserialize<'de> for SchemaType {
202 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
203 where
204 D: serde::Deserializer<'de>,
205 {
206 deserializer.deserialize_any(SchemaTypeVisitor)
207 }
208}
209
210#[derive(Hash, Eq, PartialEq, Deserialize)]
212#[serde(field_identifier, rename_all = "camelCase")]
213enum TypeFields {
214 Type,
215 Element,
216 Attributes,
217 AdditionalAttributes,
218 Name,
219}
220
221macro_rules! type_field_name {
225 (Type) => {
226 "type"
227 };
228 (Element) => {
229 "element"
230 };
231 (Attributes) => {
232 "attributes"
233 };
234 (AdditionalAttributes) => {
235 "additionalAttributes"
236 };
237 (Name) => {
238 "name"
239 };
240}
241
242impl TypeFields {
243 fn as_str(&self) -> &'static str {
244 match self {
245 TypeFields::Type => type_field_name!(Type),
246 TypeFields::Element => type_field_name!(Element),
247 TypeFields::Attributes => type_field_name!(Attributes),
248 TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
249 TypeFields::Name => type_field_name!(Name),
250 }
251 }
252}
253
254#[derive(Deserialize)]
259struct AttributesTypeMap(
260 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
261 BTreeMap<SmolStr, TypeOfAttribute>,
262);
263
264struct SchemaTypeVisitor;
265
266impl<'de> Visitor<'de> for SchemaTypeVisitor {
267 type Value = SchemaType;
268
269 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
270 formatter.write_str("builtin type or reference to type defined in commonTypes")
271 }
272
273 fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
274 where
275 M: MapAccess<'de>,
276 {
277 use TypeFields::*;
278
279 let mut type_name: Option<std::result::Result<SmolStr, M::Error>> = None;
285 let mut element: Option<std::result::Result<SchemaType, M::Error>> = None;
286 let mut attributes: Option<std::result::Result<AttributesTypeMap, M::Error>> = None;
287 let mut additional_attributes: Option<std::result::Result<bool, M::Error>> = None;
288 let mut name: Option<std::result::Result<SmolStr, M::Error>> = None;
289
290 while let Some(key) = map.next_key()? {
294 match key {
295 Type => {
296 if type_name.is_some() {
297 return Err(serde::de::Error::duplicate_field(Type.as_str()));
298 }
299 type_name = Some(map.next_value());
300 }
301 Element => {
302 if element.is_some() {
303 return Err(serde::de::Error::duplicate_field(Element.as_str()));
304 }
305 element = Some(map.next_value());
306 }
307 Attributes => {
308 if attributes.is_some() {
309 return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
310 }
311 attributes = Some(map.next_value());
312 }
313 AdditionalAttributes => {
314 if additional_attributes.is_some() {
315 return Err(serde::de::Error::duplicate_field(
316 AdditionalAttributes.as_str(),
317 ));
318 }
319 additional_attributes = Some(map.next_value());
320 }
321 Name => {
322 if name.is_some() {
323 return Err(serde::de::Error::duplicate_field(Name.as_str()));
324 }
325 name = Some(map.next_value());
326 }
327 }
328 }
329
330 Self::build_schema_type::<M>(type_name, element, attributes, additional_attributes, name)
331 }
332}
333
334impl SchemaTypeVisitor {
335 fn build_schema_type<'de, M>(
340 type_name: Option<std::result::Result<SmolStr, M::Error>>,
341 element: Option<std::result::Result<SchemaType, M::Error>>,
342 attributes: Option<std::result::Result<AttributesTypeMap, M::Error>>,
343 additional_attributes: Option<std::result::Result<bool, M::Error>>,
344 name: Option<std::result::Result<SmolStr, M::Error>>,
345 ) -> std::result::Result<SchemaType, M::Error>
346 where
347 M: MapAccess<'de>,
348 {
349 use TypeFields::*;
350 let present_fields = [
351 (Type, type_name.is_some()),
352 (Element, element.is_some()),
353 (Attributes, attributes.is_some()),
354 (AdditionalAttributes, additional_attributes.is_some()),
355 (Name, name.is_some()),
356 ]
357 .into_iter()
358 .filter(|(_, present)| *present)
359 .map(|(field, _)| field)
360 .collect::<HashSet<_>>();
361 let error_if_fields = |fs: &[TypeFields],
364 expected: &'static [&'static str]|
365 -> std::result::Result<(), M::Error> {
366 for f in fs {
367 if present_fields.contains(f) {
368 return Err(serde::de::Error::unknown_field(f.as_str(), expected));
369 }
370 }
371 Ok(())
372 };
373 let error_if_any_fields = || -> std::result::Result<(), M::Error> {
374 error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
375 };
376
377 match type_name.transpose()?.as_ref().map(|s| s.as_str()) {
378 Some("String") => {
379 error_if_any_fields()?;
380 Ok(SchemaType::Type(SchemaTypeVariant::String))
381 }
382 Some("Long") => {
383 error_if_any_fields()?;
384 Ok(SchemaType::Type(SchemaTypeVariant::Long))
385 }
386 Some("Boolean") => {
387 error_if_any_fields()?;
388 Ok(SchemaType::Type(SchemaTypeVariant::Boolean))
389 }
390 Some("Set") => {
391 error_if_fields(
392 &[Attributes, AdditionalAttributes, Name],
393 &[type_field_name!(Element)],
394 )?;
395
396 if let Some(element) = element {
397 Ok(SchemaType::Type(SchemaTypeVariant::Set {
398 element: Box::new(element?),
399 }))
400 } else {
401 Err(serde::de::Error::missing_field(Element.as_str()))
402 }
403 }
404 Some("Record") => {
405 error_if_fields(
406 &[Element, Name],
407 &[
408 type_field_name!(Attributes),
409 type_field_name!(AdditionalAttributes),
410 ],
411 )?;
412
413 if let Some(attributes) = attributes {
414 let additional_attributes =
415 additional_attributes.unwrap_or(Ok(partial_schema_default()));
416 Ok(SchemaType::Type(SchemaTypeVariant::Record {
417 attributes: attributes?.0,
418 additional_attributes: additional_attributes?,
419 }))
420 } else {
421 Err(serde::de::Error::missing_field(Attributes.as_str()))
422 }
423 }
424 Some("Entity") => {
425 error_if_fields(
426 &[Element, Attributes, AdditionalAttributes],
427 &[type_field_name!(Name)],
428 )?;
429
430 if let Some(name) = name {
431 Ok(SchemaType::Type(SchemaTypeVariant::Entity { name: name? }))
432 } else {
433 Err(serde::de::Error::missing_field(Name.as_str()))
434 }
435 }
436 Some("Extension") => {
437 error_if_fields(
438 &[Element, Attributes, AdditionalAttributes],
439 &[type_field_name!(Name)],
440 )?;
441
442 if let Some(name) = name {
443 Ok(SchemaType::Type(SchemaTypeVariant::Extension {
444 name: name?,
445 }))
446 } else {
447 Err(serde::de::Error::missing_field(Name.as_str()))
448 }
449 }
450 Some(type_name) => {
451 error_if_any_fields()?;
452 Ok(SchemaType::TypeDef {
453 type_name: type_name.into(),
454 })
455 }
456 None => Err(serde::de::Error::missing_field(Type.as_str())),
457 }
458 }
459}
460
461impl From<SchemaTypeVariant> for SchemaType {
462 fn from(variant: SchemaTypeVariant) -> Self {
463 Self::Type(variant)
464 }
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
468#[serde(tag = "type")]
469pub enum SchemaTypeVariant {
470 String,
471 Long,
472 Boolean,
473 Set {
474 element: Box<SchemaType>,
475 },
476 Record {
477 attributes: BTreeMap<SmolStr, TypeOfAttribute>,
478 #[serde(rename = "additionalAttributes")]
479 additional_attributes: bool,
480 },
481 Entity {
482 name: SmolStr,
483 },
484 Extension {
485 name: SmolStr,
486 },
487}
488
489pub(crate) static SCHEMA_TYPE_VARIANT_TAGS: &[&str] = &[
497 "String",
498 "Long",
499 "Boolean",
500 "Set",
501 "Record",
502 "Entity",
503 "Extension",
504];
505
506impl SchemaType {
507 pub fn is_extension(&self) -> Option<bool> {
512 match self {
513 Self::Type(SchemaTypeVariant::Extension { .. }) => Some(true),
514 Self::Type(SchemaTypeVariant::Set { element }) => element.is_extension(),
515 Self::Type(SchemaTypeVariant::Record { attributes, .. }) => attributes
516 .values()
517 .try_fold(false, |a, e| match e.ty.is_extension() {
518 Some(true) => Some(true),
519 Some(false) => Some(a),
520 None => None,
521 }),
522 Self::Type(_) => Some(false),
523 Self::TypeDef { .. } => None,
524 }
525 }
526}
527
528#[cfg(feature = "arbitrary")]
529#[allow(clippy::panic)]
531impl<'a> arbitrary::Arbitrary<'a> for SchemaType {
532 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<SchemaType> {
533 use cedar_policy_core::ast::Name;
534 use std::collections::BTreeSet;
535
536 Ok(SchemaType::Type(match u.int_in_range::<u8>(1..=8)? {
537 1 => SchemaTypeVariant::String,
538 2 => SchemaTypeVariant::Long,
539 3 => SchemaTypeVariant::Boolean,
540 4 => SchemaTypeVariant::Set {
541 element: Box::new(u.arbitrary()?),
542 },
543 5 => {
544 let attributes = {
545 let attr_names: BTreeSet<String> = u.arbitrary()?;
546 attr_names
547 .into_iter()
548 .map(|attr_name| Ok((attr_name.into(), u.arbitrary()?)))
549 .collect::<arbitrary::Result<_>>()?
550 };
551 SchemaTypeVariant::Record {
552 attributes,
553 additional_attributes: u.arbitrary()?,
554 }
555 }
556 6 => {
557 let name: Name = u.arbitrary()?;
558 SchemaTypeVariant::Entity {
559 name: name.to_string().into(),
560 }
561 }
562 7 => SchemaTypeVariant::Extension {
563 name: "ipaddr".into(),
564 },
565 8 => SchemaTypeVariant::Extension {
566 name: "decimal".into(),
567 },
568 n => panic!("bad index: {n}"),
569 }))
570 }
571 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
572 (1, None) }
574}
575
576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
591#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
592pub struct TypeOfAttribute {
593 #[serde(flatten)]
594 pub ty: SchemaType,
595 #[serde(default = "record_attribute_required_default")]
596 pub required: bool,
597}
598
599fn partial_schema_default() -> bool {
602 false
603}
604
605fn record_attribute_required_default() -> bool {
607 true
608}
609
610#[cfg(test)]
611mod test {
612 use super::*;
613
614 #[test]
615 fn test_entity_type_parser1() {
616 let user = r#"
617 {
618 "memberOfTypes" : ["UserGroup"]
619 }
620 "#;
621 let et = serde_json::from_str::<EntityType>(user).expect("Parse Error");
622 assert_eq!(et.member_of_types, vec!["UserGroup"]);
623 assert_eq!(
624 et.shape.into_inner(),
625 SchemaType::Type(SchemaTypeVariant::Record {
626 attributes: BTreeMap::new(),
627 additional_attributes: false
628 })
629 );
630 }
631
632 #[test]
633 fn test_entity_type_parser2() {
634 let src = r#"
635 { }
636 "#;
637 let et = serde_json::from_str::<EntityType>(src).expect("Parse Error");
638 assert_eq!(et.member_of_types.len(), 0);
639 assert_eq!(
640 et.shape.into_inner(),
641 SchemaType::Type(SchemaTypeVariant::Record {
642 attributes: BTreeMap::new(),
643 additional_attributes: false
644 })
645 );
646 }
647
648 #[test]
649 fn test_action_type_parser1() {
650 let src = r#"
651 {
652 "appliesTo" : {
653 "resourceTypes": ["Album"],
654 "principalTypes": ["User"]
655 },
656 "memberOf": [{"id": "readWrite"}]
657 }
658 "#;
659 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
660 let spec = ApplySpec {
661 resource_types: Some(vec!["Album".into()]),
662 principal_types: Some(vec!["User".into()]),
663 context: AttributesOrContext::default(),
664 };
665 assert_eq!(at.applies_to, Some(spec));
666 assert_eq!(
667 at.member_of,
668 Some(vec![ActionEntityUID {
669 ty: None,
670 id: "readWrite".into()
671 }])
672 );
673 }
674
675 #[test]
676 fn test_action_type_parser2() {
677 let src = r#"
678 { }
679 "#;
680 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
681 assert_eq!(at.applies_to, None);
682 assert!(at.member_of.is_none());
683 }
684
685 #[test]
686 fn test_schema_file_parser() {
687 let src = serde_json::json!(
688 {
689 "entityTypes": {
690
691 "User": {
692 "memberOfTypes": ["UserGroup"]
693 },
694 "Photo": {
695 "memberOfTypes": ["Album", "Account"]
696 },
697
698 "Album": {
699 "memberOfTypes": ["Album", "Account"]
700 },
701 "Account": { },
702 "UserGroup": { }
703 },
704
705 "actions": {
706 "readOnly": { },
707 "readWrite": { },
708 "createAlbum": {
709 "appliesTo" : {
710 "resourceTypes": ["Account", "Album"],
711 "principalTypes": ["User"]
712 },
713 "memberOf": [{"id": "readWrite"}]
714 },
715 "addPhotoToAlbum": {
716 "appliesTo" : {
717 "resourceTypes": ["Album"],
718 "principalTypes": ["User"]
719 },
720 "memberOf": [{"id": "readWrite"}]
721 },
722 "viewPhoto": {
723 "appliesTo" : {
724 "resourceTypes": ["Photo"],
725 "principalTypes": ["User"]
726 },
727 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
728 },
729 "viewComments": {
730 "appliesTo" : {
731 "resourceTypes": ["Photo"],
732 "principalTypes": ["User"]
733 },
734 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
735 }
736 }
737 });
738 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
739
740 assert_eq!(schema_file.entity_types.len(), 5);
741 assert_eq!(schema_file.actions.len(), 6);
742 }
743
744 #[test]
745 fn test_parse_namespaces() {
746 let src = r#"
747 {
748 "foo::foo::bar::baz": {
749 "entityTypes": {},
750 "actions": {}
751 }
752 }"#;
753 let schema: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
754 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
755 assert_eq!(namespace, "foo::foo::bar::baz".to_string());
756 }
757
758 #[test]
759 #[should_panic(expected = "unknown field `requiredddddd`")]
760 fn test_schema_file_with_misspelled_required() {
761 let src = serde_json::json!(
762 {
763 "entityTypes": {
764 "User": {
765 "shape": {
766 "type": "Record",
767 "attributes": {
768 "favorite": {
769 "type": "Entity",
770 "name": "Photo",
771 "requiredddddd": false
772 }
773 }
774 }
775 }
776 },
777 "actions": {}
778 });
779 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
780 println!("{:#?}", schema);
781 }
782
783 #[test]
784 #[should_panic(expected = "unknown field `nameeeeee`")]
785 fn test_schema_file_with_misspelled_field() {
786 let src = serde_json::json!(
787 {
788 "entityTypes": {
789 "User": {
790 "shape": {
791 "type": "Record",
792 "attributes": {
793 "favorite": {
794 "type": "Entity",
795 "nameeeeee": "Photo",
796 }
797 }
798 }
799 }
800 },
801 "actions": {}
802 });
803 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
804 println!("{:#?}", schema);
805 }
806
807 #[test]
808 #[should_panic(expected = "unknown field `extra`")]
809 fn test_schema_file_with_extra_field() {
810 let src = serde_json::json!(
811 {
812 "entityTypes": {
813 "User": {
814 "shape": {
815 "type": "Record",
816 "attributes": {
817 "favorite": {
818 "type": "Entity",
819 "name": "Photo",
820 "extra": "Should not exist"
821 }
822 }
823 }
824 }
825 },
826 "actions": {}
827 });
828 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
829 println!("{:#?}", schema);
830 }
831
832 #[test]
833 #[should_panic(expected = "unknown field `memberOfTypes`")]
834 fn test_schema_file_with_misplaced_field() {
835 let src = serde_json::json!(
836 {
837 "entityTypes": {
838 "User": {
839 "shape": {
840 "memberOfTypes": [],
841 "type": "Record",
842 "attributes": {
843 "favorite": {
844 "type": "Entity",
845 "name": "Photo",
846 }
847 }
848 }
849 }
850 },
851 "actions": {}
852 });
853 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
854 println!("{:#?}", schema);
855 }
856
857 #[test]
858 #[should_panic(expected = "missing field `name`")]
859 fn schema_file_with_missing_field() {
860 let src = serde_json::json!(
861 {
862 "entityTypes": {
863 "User": {
864 "shape": {
865 "type": "Record",
866 "attributes": {
867 "favorite": {
868 "type": "Entity",
869 }
870 }
871 }
872 }
873 },
874 "actions": {}
875 });
876 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
877 println!("{:#?}", schema);
878 }
879
880 #[test]
881 #[should_panic(expected = "missing field `type`")]
882 fn schema_file_with_missing_type() {
883 let src = serde_json::json!(
884 {
885 "entityTypes": {
886 "User": {
887 "shape": { }
888 }
889 },
890 "actions": {}
891 });
892 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
893 println!("{:#?}", schema);
894 }
895
896 #[test]
897 #[should_panic(expected = "unknown field `attributes`")]
898 fn schema_file_unexpected_malformed_attribute() {
899 let src = serde_json::json!(
900 {
901 "entityTypes": {
902 "User": {
903 "shape": {
904 "type": "Record",
905 "attributes": {
906 "a": {
907 "type": "Long",
908 "attributes": {
909 "b": {"foo": "bar"}
910 }
911 }
912 }
913 }
914 }
915 },
916 "actions": {}
917 });
918 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
919 println!("{:#?}", schema);
920 }
921}
922
923#[cfg(test)]
928mod test_duplicates_error {
929 use super::*;
930
931 #[test]
932 #[should_panic(expected = "invalid entry: found duplicate key")]
933 fn namespace() {
934 let src = r#"{
935 "Foo": {
936 "entityTypes" : {},
937 "actions": {}
938 },
939 "Foo": {
940 "entityTypes" : {},
941 "actions": {}
942 }
943 }"#;
944 serde_json::from_str::<SchemaFragment>(src).unwrap();
945 }
946
947 #[test]
948 #[should_panic(expected = "invalid entry: found duplicate key")]
949 fn entity_type() {
950 let src = r#"{
951 "Foo": {
952 "entityTypes" : {
953 "Bar": {},
954 "Bar": {},
955 },
956 "actions": {}
957 }
958 }"#;
959 serde_json::from_str::<SchemaFragment>(src).unwrap();
960 }
961
962 #[test]
963 #[should_panic(expected = "invalid entry: found duplicate key")]
964 fn action() {
965 let src = r#"{
966 "Foo": {
967 "entityTypes" : {},
968 "actions": {
969 "Bar": {},
970 "Bar": {}
971 }
972 }
973 }"#;
974 serde_json::from_str::<SchemaFragment>(src).unwrap();
975 }
976
977 #[test]
978 #[should_panic(expected = "invalid entry: found duplicate key")]
979 fn common_types() {
980 let src = r#"{
981 "Foo": {
982 "entityTypes" : {},
983 "actions": { },
984 "commonTypes": {
985 "Bar": {"type": "Long"},
986 "Bar": {"type": "String"}
987 }
988 }
989 }"#;
990 serde_json::from_str::<SchemaFragment>(src).unwrap();
991 }
992
993 #[test]
994 #[should_panic(expected = "invalid entry: found duplicate key")]
995 fn record_type() {
996 let src = r#"{
997 "Foo": {
998 "entityTypes" : {
999 "Bar": {
1000 "shape": {
1001 "type": "Record",
1002 "attributes": {
1003 "Baz": {"type": "Long"},
1004 "Baz": {"type": "String"}
1005 }
1006 }
1007 }
1008 },
1009 "actions": { }
1010 }
1011 }"#;
1012 serde_json::from_str::<SchemaFragment>(src).unwrap();
1013 }
1014}