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