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