1use std::collections::{hash_map::Entry, HashMap, HashSet};
24
25use cedar_policy_core::{
26 ast::{Entity, EntityType, EntityUID, Name},
27 entities::{Entities, EntitiesError, TCComputation},
28 extensions::Extensions,
29 transitive_closure::compute_tc,
30};
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33
34use super::NamespaceDefinition;
35use crate::{
36 err::*,
37 types::{Attributes, EntityRecordKind, OpenTag, Type},
38 SchemaFragment,
39};
40
41mod action;
42pub use action::ValidatorActionId;
43pub(crate) use action::ValidatorApplySpec;
44mod entity_type;
45pub use entity_type::ValidatorEntityType;
46mod namespace_def;
47pub(crate) use namespace_def::is_action_entity_type;
48pub use namespace_def::ValidatorNamespaceDef;
49#[cfg(test)]
50pub(crate) use namespace_def::ACTION_ENTITY_TYPE;
51
52#[derive(Eq, PartialEq, Copy, Clone, Default)]
54pub enum ActionBehavior {
55 #[default]
58 ProhibitAttributes,
59 PermitAttributes,
61}
62
63#[derive(Debug)]
64pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
65
66impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
67 type Error = SchemaError;
68
69 fn try_into(self) -> Result<ValidatorSchemaFragment> {
70 ValidatorSchemaFragment::from_schema_fragment(
71 self,
72 ActionBehavior::default(),
73 Extensions::all_available(),
74 )
75 }
76}
77
78impl ValidatorSchemaFragment {
79 pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
80 Self(namespaces.into_iter().collect())
81 }
82
83 pub fn from_schema_fragment(
84 fragment: SchemaFragment,
85 action_behavior: ActionBehavior,
86 extensions: Extensions<'_>,
87 ) -> Result<Self> {
88 Ok(Self(
89 fragment
90 .0
91 .into_iter()
92 .map(|(fragment_ns, ns_def)| {
93 ValidatorNamespaceDef::from_namespace_definition(
94 Some(fragment_ns),
95 ns_def,
96 action_behavior,
97 extensions,
98 )
99 })
100 .collect::<Result<Vec<_>>>()?,
101 ))
102 }
103
104 pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
106 self.0.iter().map(|d| d.namespace())
107 }
108}
109
110#[serde_as]
111#[derive(Clone, Debug, Serialize)]
112pub struct ValidatorSchema {
113 #[serde(rename = "entityTypes")]
115 #[serde_as(as = "Vec<(_, _)>")]
116 entity_types: HashMap<Name, ValidatorEntityType>,
117
118 #[serde(rename = "actionIds")]
120 #[serde_as(as = "Vec<(_, _)>")]
121 action_ids: HashMap<EntityUID, ValidatorActionId>,
122}
123
124impl std::str::FromStr for ValidatorSchema {
125 type Err = SchemaError;
126
127 fn from_str(s: &str) -> Result<Self> {
128 serde_json::from_str::<SchemaFragment>(s)?.try_into()
129 }
130}
131
132impl TryFrom<NamespaceDefinition> for ValidatorSchema {
133 type Error = SchemaError;
134
135 fn try_from(nsd: NamespaceDefinition) -> Result<ValidatorSchema> {
136 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
137 nsd.try_into()?
138 ])])
139 }
140}
141
142impl TryFrom<SchemaFragment> for ValidatorSchema {
143 type Error = SchemaError;
144
145 fn try_from(frag: SchemaFragment) -> Result<ValidatorSchema> {
146 ValidatorSchema::from_schema_fragments([frag.try_into()?])
147 }
148}
149
150impl ValidatorSchema {
151 pub fn empty() -> ValidatorSchema {
153 Self {
154 entity_types: HashMap::new(),
155 action_ids: HashMap::new(),
156 }
157 }
158
159 pub fn from_json_value(json: serde_json::Value, extensions: Extensions<'_>) -> Result<Self> {
162 Self::from_schema_file(
163 SchemaFragment::from_json_value(json)?,
164 ActionBehavior::default(),
165 extensions,
166 )
167 }
168
169 pub fn from_file(file: impl std::io::Read, extensions: Extensions<'_>) -> Result<Self> {
171 Self::from_schema_file(
172 SchemaFragment::from_file(file)?,
173 ActionBehavior::default(),
174 extensions,
175 )
176 }
177
178 pub fn from_schema_file(
179 schema_file: SchemaFragment,
180 action_behavior: ActionBehavior,
181 extensions: Extensions<'_>,
182 ) -> Result<ValidatorSchema> {
183 Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
184 schema_file,
185 action_behavior,
186 extensions,
187 )?])
188 }
189
190 pub fn from_schema_fragments(
192 fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
193 ) -> Result<ValidatorSchema> {
194 let mut type_defs = HashMap::new();
195 let mut entity_type_fragments = HashMap::new();
196 let mut action_fragments = HashMap::new();
197
198 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
199 for (name, ty) in ns_def.type_defs.type_defs {
205 match type_defs.entry(name) {
206 Entry::Vacant(v) => v.insert(ty),
207 Entry::Occupied(o) => {
208 return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
209 }
210 };
211 }
212
213 for (name, entity_type) in ns_def.entity_types.entity_types {
214 match entity_type_fragments.entry(name) {
215 Entry::Vacant(v) => v.insert(entity_type),
216 Entry::Occupied(o) => {
217 return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
218 }
219 };
220 }
221
222 for (action_euid, action) in ns_def.actions.actions {
223 match action_fragments.entry(action_euid) {
224 Entry::Vacant(v) => v.insert(action),
225 Entry::Occupied(o) => {
226 return Err(SchemaError::DuplicateAction(o.key().to_string()))
227 }
228 };
229 }
230 }
231
232 let mut entity_children = HashMap::new();
235 for (name, entity_type) in entity_type_fragments.iter() {
236 for parent in entity_type.parents.iter() {
237 entity_children
238 .entry(parent.clone())
239 .or_insert_with(HashSet::new)
240 .insert(name.clone());
241 }
242 }
243
244 let mut entity_types = entity_type_fragments
245 .into_iter()
246 .map(|(name, entity_type)| -> Result<_> {
247 let descendants = entity_children.remove(&name).unwrap_or_default();
257 let (attributes, open_attributes) = Self::record_attributes_or_none(
258 entity_type.attributes.resolve_type_defs(&type_defs)?,
259 )
260 .ok_or(SchemaError::ContextOrShapeNotRecord(
261 ContextOrShape::EntityTypeShape(name.clone()),
262 ))?;
263 Ok((
264 name.clone(),
265 ValidatorEntityType {
266 name,
267 descendants,
268 attributes,
269 open_attributes,
270 },
271 ))
272 })
273 .collect::<Result<HashMap<_, _>>>()?;
274
275 let mut action_children = HashMap::new();
276 for (euid, action) in action_fragments.iter() {
277 for parent in action.parents.iter() {
278 action_children
279 .entry(parent.clone())
280 .or_insert_with(HashSet::new)
281 .insert(euid.clone());
282 }
283 }
284 let mut action_ids = action_fragments
285 .into_iter()
286 .map(|(name, action)| -> Result<_> {
287 let descendants = action_children.remove(&name).unwrap_or_default();
288 let (context, open_context_attributes) =
289 Self::record_attributes_or_none(action.context.resolve_type_defs(&type_defs)?)
290 .ok_or(SchemaError::ContextOrShapeNotRecord(
291 ContextOrShape::ActionContext(name.clone()),
292 ))?;
293 Ok((
294 name.clone(),
295 ValidatorActionId {
296 name,
297 applies_to: action.applies_to,
298 descendants,
299 context: Type::record_with_attributes(
300 context.attrs,
301 open_context_attributes,
302 ),
303 attribute_types: action.attribute_types,
304 attributes: action.attributes,
305 },
306 ))
307 })
308 .collect::<Result<HashMap<_, _>>>()?;
309
310 compute_tc(&mut entity_types, false)?;
313 compute_tc(&mut action_ids, true)?;
316
317 Self::check_for_undeclared(
324 &entity_types,
325 entity_children.into_keys(),
326 &action_ids,
327 action_children.into_keys(),
328 )?;
329
330 Ok(ValidatorSchema {
331 entity_types,
332 action_ids,
333 })
334 }
335
336 fn check_for_undeclared(
341 entity_types: &HashMap<Name, ValidatorEntityType>,
342 undeclared_parent_entities: impl IntoIterator<Item = Name>,
343 action_ids: &HashMap<EntityUID, ValidatorActionId>,
344 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
345 ) -> Result<()> {
346 let mut undeclared_e = undeclared_parent_entities
351 .into_iter()
352 .map(|n| n.to_string())
353 .collect::<HashSet<_>>();
354 for entity_type in entity_types.values() {
360 for (_, attr_typ) in entity_type.attributes() {
361 Self::check_undeclared_in_type(
362 &attr_typ.attr_type,
363 entity_types,
364 &mut undeclared_e,
365 );
366 }
367 }
368
369 let undeclared_a = undeclared_parent_actions
371 .into_iter()
372 .map(|n| n.to_string())
373 .collect::<HashSet<_>>();
374 for action in action_ids.values() {
378 Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
379
380 for p_entity in action.applies_to.applicable_principal_types() {
381 match p_entity {
382 EntityType::Specified(p_entity) => {
383 if !entity_types.contains_key(&p_entity) {
384 undeclared_e.insert(p_entity.to_string());
385 }
386 }
387 EntityType::Unspecified => (),
388 }
389 }
390
391 for r_entity in action.applies_to.applicable_resource_types() {
392 match r_entity {
393 EntityType::Specified(r_entity) => {
394 if !entity_types.contains_key(&r_entity) {
395 undeclared_e.insert(r_entity.to_string());
396 }
397 }
398 EntityType::Unspecified => (),
399 }
400 }
401 }
402 if !undeclared_e.is_empty() {
403 return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
404 }
405 if !undeclared_a.is_empty() {
406 return Err(SchemaError::UndeclaredActions(undeclared_a));
407 }
408
409 Ok(())
410 }
411
412 fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
413 match ty {
414 Type::EntityOrRecord(EntityRecordKind::Record {
415 attrs,
416 open_attributes,
417 }) => Some((attrs, open_attributes)),
418 _ => None,
419 }
420 }
421
422 fn check_undeclared_in_type(
426 ty: &Type,
427 entity_types: &HashMap<Name, ValidatorEntityType>,
428 undeclared_types: &mut HashSet<String>,
429 ) {
430 match ty {
431 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
432 for name in lub.iter() {
433 if !entity_types.contains_key(name) {
434 undeclared_types.insert(name.to_string());
435 }
436 }
437 }
438
439 Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
440 for (_, attr_ty) in attrs.iter() {
441 Self::check_undeclared_in_type(
442 &attr_ty.attr_type,
443 entity_types,
444 undeclared_types,
445 );
446 }
447 }
448
449 Type::Set {
450 element_type: Some(element_type),
451 } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
452
453 _ => (),
454 }
455 }
456
457 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
459 self.action_ids.get(action_id)
460 }
461
462 pub fn get_entity_type<'a>(&'a self, entity_type_id: &Name) -> Option<&'a ValidatorEntityType> {
464 self.entity_types.get(entity_type_id)
465 }
466
467 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
469 self.action_ids.contains_key(action_id)
470 }
471
472 pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
474 is_action_entity_type(entity_type) || self.entity_types.contains_key(entity_type)
475 }
476
477 pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
482 match euid.entity_type() {
483 EntityType::Specified(ety) => self.is_known_entity_type(ety),
484 EntityType::Unspecified => true,
485 }
486 }
487
488 pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
490 self.action_ids.keys()
491 }
492
493 pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
495 self.entity_types.keys()
496 }
497
498 pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
500 self.entity_types.iter()
501 }
502
503 pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&Name> {
509 match entity.entity_type() {
510 EntityType::Specified(ety) => {
511 let mut descendants = self
512 .get_entity_type(ety)
513 .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
514 .unwrap_or_default();
515 descendants.push(ety);
516 descendants
517 }
518 EntityType::Unspecified => Vec::new(),
519 }
520 }
521
522 pub(crate) fn get_entity_types_in_set<'a>(
526 &'a self,
527 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
528 ) -> impl Iterator<Item = &Name> {
529 euids
530 .into_iter()
531 .map(|e| self.get_entity_types_in(e))
532 .flatten()
533 }
534
535 pub(crate) fn get_actions_in_set<'a>(
539 &'a self,
540 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
541 ) -> Option<Vec<&'a EntityUID>> {
542 euids
543 .into_iter()
544 .map(|e| {
545 self.get_action_id(e).map(|action| {
546 action
547 .descendants
548 .iter()
549 .chain(std::iter::once(&action.name))
550 })
551 })
552 .collect::<Option<Vec<_>>>()
553 .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
554 }
555
556 pub fn context_type(&self, action: &EntityUID) -> Option<Type> {
561 self.get_action_id(action)
564 .map(ValidatorActionId::context_type)
565 }
566
567 pub(crate) fn action_entities_iter(
570 &self,
571 ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
572 let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
578 for (action_euid, action_def) in &self.action_ids {
579 for descendant in &action_def.descendants {
580 action_ancestors
581 .entry(descendant)
582 .or_default()
583 .insert(action_euid.clone());
584 }
585 }
586 self.action_ids.iter().map(move |(action_id, action)| {
587 Entity::new_with_attr_partial_value_serialized_as_expr(
588 action_id.clone(),
589 action.attributes.clone(),
590 action_ancestors.remove(action_id).unwrap_or_default(),
591 )
592 })
593 }
594
595 pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
597 let extensions = Extensions::all_available();
598 Entities::from_entities(
599 self.action_entities_iter(),
600 None::<&cedar_policy_core::entities::NoEntitiesSchema>, TCComputation::AssumeAlreadyComputed,
602 extensions,
603 )
604 .map_err(Into::into)
605 }
606}
607
608#[derive(Debug, Clone, Deserialize)]
611#[serde(transparent)]
612pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
613
614impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
615 type Error = SchemaError;
616
617 fn try_into(self) -> Result<ValidatorSchema> {
618 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
619 ValidatorNamespaceDef::from_namespace_definition(
620 None,
621 self.0,
622 crate::ActionBehavior::PermitAttributes,
623 Extensions::all_available(),
624 )?,
625 ])])
626 }
627}
628
629#[allow(clippy::panic)]
631#[allow(clippy::indexing_slicing)]
633#[cfg(test)]
634mod test {
635 use std::{collections::BTreeMap, str::FromStr};
636
637 use crate::types::Type;
638 use crate::{SchemaType, SchemaTypeVariant};
639
640 use cedar_policy_core::ast::RestrictedExpr;
641 use cedar_policy_core::parser::err::{ParseError, ToASTError};
642 use cool_asserts::assert_matches;
643 use serde_json::json;
644
645 use super::*;
646
647 #[test]
649 fn test_from_schema_file() {
650 let src = json!(
651 {
652 "entityTypes": {
653 "User": {
654 "memberOfTypes": [ "Group" ]
655 },
656 "Group": {
657 "memberOfTypes": []
658 },
659 "Photo": {
660 "memberOfTypes": [ "Album" ]
661 },
662 "Album": {
663 "memberOfTypes": []
664 }
665 },
666 "actions": {
667 "view_photo": {
668 "appliesTo": {
669 "principalTypes": ["User", "Group"],
670 "resourceTypes": ["Photo"]
671 }
672 }
673 }
674 });
675 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
676 let schema: Result<ValidatorSchema> = schema_file.try_into();
677 assert!(schema.is_ok());
678 }
679
680 #[test]
682 fn test_from_schema_file_duplicate_entity() {
683 let src = r#"
686 {"": {
687 "entityTypes": {
688 "User": {
689 "memberOfTypes": [ "Group" ]
690 },
691 "Group": {
692 "memberOfTypes": []
693 },
694 "Photo": {
695 "memberOfTypes": [ "Album" ]
696 },
697 "Photo": {
698 "memberOfTypes": []
699 }
700 },
701 "actions": {
702 "view_photo": {
703 "memberOf": [],
704 "appliesTo": {
705 "principalTypes": ["User", "Group"],
706 "resourceTypes": ["Photo"]
707 }
708 }
709 }
710 }}"#;
711
712 match ValidatorSchema::from_str(src) {
713 Err(SchemaError::Serde(_)) => (),
714 _ => panic!("Expected serde error due to duplicate entity type."),
715 }
716 }
717
718 #[test]
720 fn test_from_schema_file_duplicate_action() {
721 let src = r#"
724 {"": {
725 "entityTypes": {
726 "User": {
727 "memberOfTypes": [ "Group" ]
728 },
729 "Group": {
730 "memberOfTypes": []
731 },
732 "Photo": {
733 "memberOfTypes": []
734 }
735 },
736 "actions": {
737 "view_photo": {
738 "memberOf": [],
739 "appliesTo": {
740 "principalTypes": ["User", "Group"],
741 "resourceTypes": ["Photo"]
742 }
743 },
744 "view_photo": { }
745 }
746 }"#;
747 match ValidatorSchema::from_str(src) {
748 Err(SchemaError::Serde(_)) => (),
749 _ => panic!("Expected serde error due to duplicate action type."),
750 }
751 }
752
753 #[test]
755 fn test_from_schema_file_undefined_entities() {
756 let src = json!(
757 {
758 "entityTypes": {
759 "User": {
760 "memberOfTypes": [ "Grop" ]
761 },
762 "Group": {
763 "memberOfTypes": []
764 },
765 "Photo": {
766 "memberOfTypes": []
767 }
768 },
769 "actions": {
770 "view_photo": {
771 "appliesTo": {
772 "principalTypes": ["Usr", "Group"],
773 "resourceTypes": ["Phoot"]
774 }
775 }
776 }
777 });
778 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
779 let schema: Result<ValidatorSchema> = schema_file.try_into();
780 match schema {
781 Ok(_) => panic!("from_schema_file should have failed"),
782 Err(SchemaError::UndeclaredEntityTypes(v)) => {
783 assert_eq!(v.len(), 3)
784 }
785 _ => panic!("Unexpected error from from_schema_file"),
786 }
787 }
788
789 #[test]
790 fn undefined_entity_namespace_member_of() {
791 let src = json!(
792 {"Foo": {
793 "entityTypes": {
794 "User": {
795 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
796 },
797 "Group": { }
798 },
799 "actions": {}
800 }});
801 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
802 let schema: Result<ValidatorSchema> = schema_file.try_into();
803 match schema {
804 Ok(_) => panic!("try_into should have failed"),
805 Err(SchemaError::UndeclaredEntityTypes(v)) => {
806 assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
807 }
808 _ => panic!("Unexpected error from try_into"),
809 }
810 }
811
812 #[test]
813 fn undefined_entity_namespace_applies_to() {
814 let src = json!(
815 {"Foo": {
816 "entityTypes": { "User": { }, "Photo": { } },
817 "actions": {
818 "view_photo": {
819 "appliesTo": {
820 "principalTypes": ["Foo::User", "Bar::User"],
821 "resourceTypes": ["Photo", "Bar::Photo"],
822 }
823 }
824 }
825 }});
826 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
827 let schema: Result<ValidatorSchema> = schema_file.try_into();
828 match schema {
829 Ok(_) => panic!("try_into should have failed"),
830 Err(SchemaError::UndeclaredEntityTypes(v)) => {
831 assert_eq!(
832 v,
833 HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
834 )
835 }
836 _ => panic!("Unexpected error from try_into"),
837 }
838 }
839
840 #[test]
842 fn test_from_schema_file_undefined_action() {
843 let src = json!(
844 {
845 "entityTypes": {
846 "User": {
847 "memberOfTypes": [ "Group" ]
848 },
849 "Group": {
850 "memberOfTypes": []
851 },
852 "Photo": {
853 "memberOfTypes": []
854 }
855 },
856 "actions": {
857 "view_photo": {
858 "memberOf": [ {"id": "photo_action"} ],
859 "appliesTo": {
860 "principalTypes": ["User", "Group"],
861 "resourceTypes": ["Photo"]
862 }
863 }
864 }
865 });
866 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
867 let schema: Result<ValidatorSchema> = schema_file.try_into();
868 match schema {
869 Ok(_) => panic!("from_schema_file should have failed"),
870 Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
871 _ => panic!("Unexpected error from from_schema_file"),
872 }
873 }
874
875 #[test]
878 fn test_from_schema_file_action_cycle1() {
879 let src = json!(
880 {
881 "entityTypes": {},
882 "actions": {
883 "view_photo": {
884 "memberOf": [ {"id": "view_photo"} ]
885 }
886 }
887 });
888 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
889 let schema: Result<ValidatorSchema> = schema_file.try_into();
890 assert_matches!(
891 schema,
892 Err(SchemaError::CycleInActionHierarchy(euid)) => {
893 assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
894 }
895 )
896 }
897
898 #[test]
901 fn test_from_schema_file_action_cycle2() {
902 let src = json!(
903 {
904 "entityTypes": {},
905 "actions": {
906 "view_photo": {
907 "memberOf": [ {"id": "edit_photo"} ]
908 },
909 "edit_photo": {
910 "memberOf": [ {"id": "delete_photo"} ]
911 },
912 "delete_photo": {
913 "memberOf": [ {"id": "view_photo"} ]
914 },
915 "other_action": {
916 "memberOf": [ {"id": "edit_photo"} ]
917 }
918 }
919 });
920 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
921 let schema: Result<ValidatorSchema> = schema_file.try_into();
922 assert_matches!(
923 schema,
924 Err(SchemaError::CycleInActionHierarchy(_)),
926 )
927 }
928
929 #[test]
930 fn namespaced_schema() {
931 let src = r#"
932 { "N::S": {
933 "entityTypes": {
934 "User": {},
935 "Photo": {}
936 },
937 "actions": {
938 "view_photo": {
939 "appliesTo": {
940 "principalTypes": ["User"],
941 "resourceTypes": ["Photo"]
942 }
943 }
944 }
945 } }
946 "#;
947 let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
948 let schema: ValidatorSchema = schema_file
949 .try_into()
950 .expect("Namespaced schema failed to convert.");
951 dbg!(&schema);
952 let user_entity_type = &"N::S::User"
953 .parse()
954 .expect("Namespaced entity type should have parsed");
955 let photo_entity_type = &"N::S::Photo"
956 .parse()
957 .expect("Namespaced entity type should have parsed");
958 assert!(
959 schema.entity_types.contains_key(user_entity_type),
960 "Expected and entity type User."
961 );
962 assert!(
963 schema.entity_types.contains_key(photo_entity_type),
964 "Expected an entity type Photo."
965 );
966 assert_eq!(
967 schema.entity_types.len(),
968 2,
969 "Expected exactly 2 entity types."
970 );
971 assert!(
972 schema.action_ids.contains_key(
973 &"N::S::Action::\"view_photo\""
974 .parse()
975 .expect("Namespaced action should have parsed")
976 ),
977 "Expected an action \"view_photo\"."
978 );
979 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
980
981 let apply_spec = &schema
982 .action_ids
983 .values()
984 .next()
985 .expect("Expected Action")
986 .applies_to;
987 assert_eq!(
988 apply_spec.applicable_principal_types().collect::<Vec<_>>(),
989 vec![&EntityType::Specified(user_entity_type.clone())]
990 );
991 assert_eq!(
992 apply_spec.applicable_resource_types().collect::<Vec<_>>(),
993 vec![&EntityType::Specified(photo_entity_type.clone())]
994 );
995 }
996
997 #[test]
998 fn cant_use_namespace_in_entity_type() {
999 let src = r#"
1000 {
1001 "entityTypes": { "NS::User": {} },
1002 "actions": {}
1003 }
1004 "#;
1005 let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
1006 assert!(
1007 matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::ParseEntityType(_))),
1008 "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
1009 }
1010
1011 #[test]
1012 fn entity_attribute_entity_type_with_namespace() {
1013 let schema_json: SchemaFragment = serde_json::from_str(
1014 r#"
1015 {"A::B": {
1016 "entityTypes": {
1017 "Foo": {
1018 "shape": {
1019 "type": "Record",
1020 "attributes": {
1021 "name": { "type": "Entity", "name": "C::D::Foo" }
1022 }
1023 }
1024 }
1025 },
1026 "actions": {}
1027 }}
1028 "#,
1029 )
1030 .expect("Expected valid schema");
1031
1032 let schema: Result<ValidatorSchema> = schema_json.try_into();
1033 match schema {
1034 Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1035 assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1036 }
1037 _ => panic!("Schema construction should have failed due to undeclared entity type."),
1038 }
1039 }
1040
1041 #[test]
1042 fn entity_attribute_entity_type_with_declared_namespace() {
1043 let schema_json: SchemaFragment = serde_json::from_str(
1044 r#"
1045 {"A::B": {
1046 "entityTypes": {
1047 "Foo": {
1048 "shape": {
1049 "type": "Record",
1050 "attributes": {
1051 "name": { "type": "Entity", "name": "A::B::Foo" }
1052 }
1053 }
1054 }
1055 },
1056 "actions": {}
1057 }}
1058 "#,
1059 )
1060 .expect("Expected valid schema");
1061
1062 let schema: ValidatorSchema = schema_json
1063 .try_into()
1064 .expect("Expected schema to construct without error.");
1065
1066 let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1067 let foo_type = schema
1068 .entity_types
1069 .get(&foo_name)
1070 .expect("Expected to find entity");
1071 let name_type = foo_type
1072 .attr("name")
1073 .expect("Expected attribute name")
1074 .attr_type
1075 .clone();
1076 let expected_name_type = Type::named_entity_reference(foo_name);
1077 assert_eq!(name_type, expected_name_type);
1078 }
1079
1080 #[test]
1081 fn cannot_declare_action_type_when_prohibited() {
1082 let schema_json: NamespaceDefinition = serde_json::from_str(
1083 r#"
1084 {
1085 "entityTypes": { "Action": {} },
1086 "actions": {}
1087 }
1088 "#,
1089 )
1090 .expect("Expected valid schema");
1091
1092 let schema: Result<ValidatorSchema> = schema_json.try_into();
1093 assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1094 }
1095
1096 #[test]
1097 fn can_declare_other_type_when_action_type_prohibited() {
1098 let schema_json: NamespaceDefinition = serde_json::from_str(
1099 r#"
1100 {
1101 "entityTypes": { "Foo": { } },
1102 "actions": {}
1103 }
1104 "#,
1105 )
1106 .expect("Expected valid schema");
1107
1108 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1109 }
1110
1111 #[test]
1112 fn cannot_declare_action_in_group_when_prohibited() {
1113 let schema_json: SchemaFragment = serde_json::from_str(
1114 r#"
1115 {"": {
1116 "entityTypes": {},
1117 "actions": {
1118 "universe": { },
1119 "view_photo": {
1120 "attributes": {"id": "universe"}
1121 },
1122 "edit_photo": {
1123 "attributes": {"id": "universe"}
1124 },
1125 "delete_photo": {
1126 "attributes": {"id": "universe"}
1127 }
1128 }
1129 }}
1130 "#,
1131 )
1132 .expect("Expected valid schema");
1133
1134 let schema = ValidatorSchemaFragment::from_schema_fragment(
1135 schema_json,
1136 ActionBehavior::ProhibitAttributes,
1137 Extensions::all_available(),
1138 );
1139 match schema {
1140 Err(SchemaError::UnsupportedFeature(UnsupportedFeature::ActionAttributes(actions))) => {
1141 assert_eq!(
1142 actions.into_iter().collect::<HashSet<_>>(),
1143 HashSet::from([
1144 "view_photo".to_string(),
1145 "edit_photo".to_string(),
1146 "delete_photo".to_string(),
1147 ])
1148 )
1149 }
1150 _ => panic!("Did not see expected error."),
1151 }
1152 }
1153
1154 #[test]
1155 fn test_entity_type_no_namespace() {
1156 let src = json!({"type": "Entity", "name": "Foo"});
1157 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1158 assert_eq!(
1159 schema_ty,
1160 SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
1161 );
1162 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1163 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1164 schema_ty,
1165 )
1166 .expect("Error converting schema type to type.")
1167 .resolve_type_defs(&HashMap::new())
1168 .unwrap();
1169 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1170 }
1171
1172 #[test]
1173 fn test_entity_type_namespace() {
1174 let src = json!({"type": "Entity", "name": "NS::Foo"});
1175 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1176 assert_eq!(
1177 schema_ty,
1178 SchemaType::Type(SchemaTypeVariant::Entity {
1179 name: "NS::Foo".into()
1180 })
1181 );
1182 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1183 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1184 schema_ty,
1185 )
1186 .expect("Error converting schema type to type.")
1187 .resolve_type_defs(&HashMap::new())
1188 .unwrap();
1189 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1190 }
1191
1192 #[test]
1193 fn test_entity_type_namespace_parse_error() {
1194 let src = json!({"type": "Entity", "name": "::Foo"});
1195 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1196 assert_eq!(
1197 schema_ty,
1198 SchemaType::Type(SchemaTypeVariant::Entity {
1199 name: "::Foo".into()
1200 })
1201 );
1202 match ValidatorNamespaceDef::try_schema_type_into_validator_type(
1203 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1204 schema_ty,
1205 ) {
1206 Err(SchemaError::ParseEntityType(_)) => (),
1207 _ => panic!("Did not see expected entity type parse error."),
1208 }
1209 }
1210
1211 #[test]
1212 fn schema_type_record_is_validator_type_record() {
1213 let src = json!({"type": "Record", "attributes": {}});
1214 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1215 assert_eq!(
1216 schema_ty,
1217 SchemaType::Type(SchemaTypeVariant::Record {
1218 attributes: BTreeMap::new(),
1219 additional_attributes: false,
1220 }),
1221 );
1222 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
1223 .expect("Error converting schema type to type.")
1224 .resolve_type_defs(&HashMap::new())
1225 .unwrap();
1226 assert_eq!(ty, Type::closed_record_with_attributes(None));
1227 }
1228
1229 #[test]
1230 fn get_namespaces() {
1231 let fragment: SchemaFragment = serde_json::from_value(json!({
1232 "Foo::Bar::Baz": {
1233 "entityTypes": {},
1234 "actions": {}
1235 },
1236 "Foo": {
1237 "entityTypes": {},
1238 "actions": {}
1239 },
1240 "Bar": {
1241 "entityTypes": {},
1242 "actions": {}
1243 },
1244 }))
1245 .unwrap();
1246
1247 let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
1248 assert_eq!(
1249 schema_fragment
1250 .0
1251 .iter()
1252 .map(|f| f.namespace())
1253 .collect::<HashSet<_>>(),
1254 HashSet::from([
1255 &Some("Foo::Bar::Baz".parse().unwrap()),
1256 &Some("Foo".parse().unwrap()),
1257 &Some("Bar".parse().unwrap())
1258 ])
1259 );
1260 }
1261
1262 #[test]
1263 fn schema_no_fragments() {
1264 let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
1265 assert!(schema.entity_types.is_empty());
1266 assert!(schema.action_ids.is_empty());
1267 }
1268
1269 #[test]
1270 fn same_action_different_namespace() {
1271 let fragment: SchemaFragment = serde_json::from_value(json!({
1272 "Foo::Bar": {
1273 "entityTypes": {},
1274 "actions": {
1275 "Baz": {}
1276 }
1277 },
1278 "Bar::Foo": {
1279 "entityTypes": {},
1280 "actions": {
1281 "Baz": { }
1282 }
1283 },
1284 "Biz": {
1285 "entityTypes": {},
1286 "actions": {
1287 "Baz": { }
1288 }
1289 }
1290 }))
1291 .unwrap();
1292
1293 let schema: ValidatorSchema = fragment.try_into().unwrap();
1294 assert!(schema
1295 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1296 .is_some());
1297 assert!(schema
1298 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1299 .is_some());
1300 assert!(schema
1301 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
1302 .is_some());
1303 }
1304
1305 #[test]
1306 fn same_type_different_namespace() {
1307 let fragment: SchemaFragment = serde_json::from_value(json!({
1308 "Foo::Bar": {
1309 "entityTypes": {"Baz" : {}},
1310 "actions": { }
1311 },
1312 "Bar::Foo": {
1313 "entityTypes": {"Baz" : {}},
1314 "actions": { }
1315 },
1316 "Biz": {
1317 "entityTypes": {"Baz" : {}},
1318 "actions": { }
1319 }
1320 }))
1321 .unwrap();
1322 let schema: ValidatorSchema = fragment.try_into().unwrap();
1323
1324 assert!(schema
1325 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
1326 .is_some());
1327 assert!(schema
1328 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
1329 .is_some());
1330 assert!(schema
1331 .get_entity_type(&"Biz::Baz".parse().unwrap())
1332 .is_some());
1333 }
1334
1335 #[test]
1336 fn member_of_different_namespace() {
1337 let fragment: SchemaFragment = serde_json::from_value(json!({
1338 "Bar": {
1339 "entityTypes": {
1340 "Baz": {
1341 "memberOfTypes": ["Foo::Buz"]
1342 }
1343 },
1344 "actions": {}
1345 },
1346 "Foo": {
1347 "entityTypes": { "Buz": {} },
1348 "actions": { }
1349 }
1350 }))
1351 .unwrap();
1352 let schema: ValidatorSchema = fragment.try_into().unwrap();
1353
1354 let buz = schema
1355 .get_entity_type(&"Foo::Buz".parse().unwrap())
1356 .unwrap();
1357 assert_eq!(
1358 buz.descendants,
1359 HashSet::from(["Bar::Baz".parse().unwrap()])
1360 );
1361 }
1362
1363 #[test]
1364 fn attribute_different_namespace() {
1365 let fragment: SchemaFragment = serde_json::from_value(json!({
1366 "Bar": {
1367 "entityTypes": {
1368 "Baz": {
1369 "shape": {
1370 "type": "Record",
1371 "attributes": {
1372 "fiz": {
1373 "type": "Entity",
1374 "name": "Foo::Buz"
1375 }
1376 }
1377 }
1378 }
1379 },
1380 "actions": {}
1381 },
1382 "Foo": {
1383 "entityTypes": { "Buz": {} },
1384 "actions": { }
1385 }
1386 }))
1387 .unwrap();
1388
1389 let schema: ValidatorSchema = fragment.try_into().unwrap();
1390 let baz = schema
1391 .get_entity_type(&"Bar::Baz".parse().unwrap())
1392 .unwrap();
1393 assert_eq!(
1394 baz.attr("fiz").unwrap().attr_type,
1395 Type::named_entity_reference_from_str("Foo::Buz"),
1396 );
1397 }
1398
1399 #[test]
1400 fn applies_to_different_namespace() {
1401 let fragment: SchemaFragment = serde_json::from_value(json!({
1402 "Foo::Bar": {
1403 "entityTypes": { },
1404 "actions": {
1405 "Baz": {
1406 "appliesTo": {
1407 "principalTypes": [ "Fiz::Buz" ],
1408 "resourceTypes": [ "Fiz::Baz" ],
1409 }
1410 }
1411 }
1412 },
1413 "Fiz": {
1414 "entityTypes": {
1415 "Buz": {},
1416 "Baz": {}
1417 },
1418 "actions": { }
1419 }
1420 }))
1421 .unwrap();
1422 let schema: ValidatorSchema = fragment.try_into().unwrap();
1423
1424 let baz = schema
1425 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1426 .unwrap();
1427 assert_eq!(
1428 baz.applies_to
1429 .applicable_principal_types()
1430 .collect::<HashSet<_>>(),
1431 HashSet::from([&EntityType::Specified("Fiz::Buz".parse().unwrap())])
1432 );
1433 assert_eq!(
1434 baz.applies_to
1435 .applicable_resource_types()
1436 .collect::<HashSet<_>>(),
1437 HashSet::from([&EntityType::Specified("Fiz::Baz".parse().unwrap())])
1438 );
1439 }
1440
1441 #[test]
1442 fn simple_defined_type() {
1443 let fragment: SchemaFragment = serde_json::from_value(json!({
1444 "": {
1445 "commonTypes": {
1446 "MyLong": {"type": "Long"}
1447 },
1448 "entityTypes": {
1449 "User": {
1450 "shape": {
1451 "type": "Record",
1452 "attributes": {
1453 "a": {"type": "MyLong"}
1454 }
1455 }
1456 }
1457 },
1458 "actions": {}
1459 }
1460 }))
1461 .unwrap();
1462 let schema: ValidatorSchema = fragment.try_into().unwrap();
1463 assert_eq!(
1464 schema.entity_types.iter().next().unwrap().1.attributes,
1465 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1466 );
1467 }
1468
1469 #[test]
1470 fn defined_record_as_attrs() {
1471 let fragment: SchemaFragment = serde_json::from_value(json!({
1472 "": {
1473 "commonTypes": {
1474 "MyRecord": {
1475 "type": "Record",
1476 "attributes": {
1477 "a": {"type": "Long"}
1478 }
1479 }
1480 },
1481 "entityTypes": {
1482 "User": { "shape": { "type": "MyRecord", } }
1483 },
1484 "actions": {}
1485 }
1486 }))
1487 .unwrap();
1488 let schema: ValidatorSchema = fragment.try_into().unwrap();
1489 assert_eq!(
1490 schema.entity_types.iter().next().unwrap().1.attributes,
1491 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1492 );
1493 }
1494
1495 #[test]
1496 fn cross_namespace_type() {
1497 let fragment: SchemaFragment = serde_json::from_value(json!({
1498 "A": {
1499 "commonTypes": {
1500 "MyLong": {"type": "Long"}
1501 },
1502 "entityTypes": { },
1503 "actions": {}
1504 },
1505 "B": {
1506 "entityTypes": {
1507 "User": {
1508 "shape": {
1509 "type": "Record",
1510 "attributes": {
1511 "a": {"type": "A::MyLong"}
1512 }
1513 }
1514 }
1515 },
1516 "actions": {}
1517 }
1518 }))
1519 .unwrap();
1520 let schema: ValidatorSchema = fragment.try_into().unwrap();
1521 assert_eq!(
1522 schema.entity_types.iter().next().unwrap().1.attributes,
1523 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1524 );
1525 }
1526
1527 #[test]
1528 fn cross_fragment_type() {
1529 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1530 "A": {
1531 "commonTypes": {
1532 "MyLong": {"type": "Long"}
1533 },
1534 "entityTypes": { },
1535 "actions": {}
1536 }
1537 }))
1538 .unwrap()
1539 .try_into()
1540 .unwrap();
1541 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1542 "A": {
1543 "entityTypes": {
1544 "User": {
1545 "shape": {
1546 "type": "Record",
1547 "attributes": {
1548 "a": {"type": "MyLong"}
1549 }
1550 }
1551 }
1552 },
1553 "actions": {}
1554 }
1555 }))
1556 .unwrap()
1557 .try_into()
1558 .unwrap();
1559 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
1560
1561 assert_eq!(
1562 schema.entity_types.iter().next().unwrap().1.attributes,
1563 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1564 );
1565 }
1566
1567 #[test]
1568 fn cross_fragment_duplicate_type() {
1569 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1570 "A": {
1571 "commonTypes": {
1572 "MyLong": {"type": "Long"}
1573 },
1574 "entityTypes": {},
1575 "actions": {}
1576 }
1577 }))
1578 .unwrap()
1579 .try_into()
1580 .unwrap();
1581 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1582 "A": {
1583 "commonTypes": {
1584 "MyLong": {"type": "Long"}
1585 },
1586 "entityTypes": {},
1587 "actions": {}
1588 }
1589 }))
1590 .unwrap()
1591 .try_into()
1592 .unwrap();
1593
1594 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]);
1595
1596 match schema {
1597 Err(SchemaError::DuplicateCommonType(s)) if s.contains("A::MyLong") => (),
1598 _ => panic!("should have errored because schema fragments have duplicate types"),
1599 };
1600 }
1601
1602 #[test]
1603 fn undeclared_type_in_attr() {
1604 let fragment: SchemaFragment = serde_json::from_value(json!({
1605 "": {
1606 "commonTypes": { },
1607 "entityTypes": {
1608 "User": {
1609 "shape": {
1610 "type": "Record",
1611 "attributes": {
1612 "a": {"type": "MyLong"}
1613 }
1614 }
1615 }
1616 },
1617 "actions": {}
1618 }
1619 }))
1620 .unwrap();
1621 match TryInto::<ValidatorSchema>::try_into(fragment) {
1622 Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1623 s => panic!(
1624 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1625 s
1626 ),
1627 }
1628 }
1629
1630 #[test]
1631 fn undeclared_type_in_type_def() {
1632 let fragment: SchemaFragment = serde_json::from_value(json!({
1633 "": {
1634 "commonTypes": {
1635 "a": { "type": "b" }
1636 },
1637 "entityTypes": { },
1638 "actions": {}
1639 }
1640 }))
1641 .unwrap();
1642 match TryInto::<ValidatorSchema>::try_into(fragment) {
1643 Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1644 s => panic!(
1645 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1646 s
1647 ),
1648 }
1649 }
1650
1651 #[test]
1652 fn shape_not_record() {
1653 let fragment: SchemaFragment = serde_json::from_value(json!({
1654 "": {
1655 "commonTypes": {
1656 "MyLong": { "type": "Long" }
1657 },
1658 "entityTypes": {
1659 "User": {
1660 "shape": { "type": "MyLong" }
1661 }
1662 },
1663 "actions": {}
1664 }
1665 }))
1666 .unwrap();
1667 match TryInto::<ValidatorSchema>::try_into(fragment) {
1668 Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
1669 s => panic!(
1670 "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
1671 s
1672 ),
1673 }
1674 }
1675
1676 #[test]
1680 fn counterexamples_from_cedar_134() {
1681 let bad1 = json!({
1683 "": {
1684 "entityTypes": {
1685 "User // comment": {
1686 "memberOfTypes": [
1687 "UserGroup"
1688 ]
1689 },
1690 "User": {
1691 "memberOfTypes": [
1692 "UserGroup"
1693 ]
1694 },
1695 "UserGroup": {}
1696 },
1697 "actions": {}
1698 }
1699 });
1700 let fragment = serde_json::from_value::<SchemaFragment>(bad1)
1701 .expect("constructing the fragment itself should succeed"); let err = ValidatorSchema::try_from(fragment)
1703 .expect_err("should error due to invalid entity type name");
1704 let expected_err = ParseError::ToAST(ToASTError::NonNormalizedString {
1705 kind: "Id",
1706 src: "User // comment".to_string(),
1707 normalized_src: "User".to_string(),
1708 })
1709 .into();
1710
1711 match err {
1712 SchemaError::ParseEntityType(parse_error) => assert_eq!(parse_error, expected_err),
1713 err => panic!("Incorrect error {err}"),
1714 }
1715
1716 let bad2 = json!({
1718 "ABC :: //comment \n XYZ ": {
1719 "entityTypes": {
1720 "User": {
1721 "memberOfTypes": []
1722 }
1723 },
1724 "actions": {}
1725 }
1726 });
1727 let fragment = serde_json::from_value::<SchemaFragment>(bad2)
1728 .expect("constructing the fragment itself should succeed"); let err = ValidatorSchema::try_from(fragment)
1730 .expect_err("should error due to invalid schema namespace");
1731 let expected_err = ParseError::ToAST(ToASTError::NonNormalizedString {
1732 kind: "Name",
1733 src: "ABC :: //comment \n XYZ ".to_string(),
1734 normalized_src: "ABC::XYZ".to_string(),
1735 })
1736 .into();
1737 match err {
1738 SchemaError::ParseNamespace(parse_error) => assert_eq!(parse_error, expected_err),
1739 err => panic!("Incorrect error {:?}", err),
1740 };
1741 }
1742
1743 #[test]
1744 fn simple_action_entity() {
1745 let src = json!(
1746 {
1747 "entityTypes": { },
1748 "actions": {
1749 "view_photo": { },
1750 }
1751 });
1752
1753 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1754 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1755 let actions = schema.action_entities().expect("Entity Construct Error");
1756
1757 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1758 let view_photo = actions.entity(&action_uid);
1759 assert_eq!(
1760 view_photo.unwrap(),
1761 &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
1762 );
1763 }
1764
1765 #[test]
1766 fn action_entity_hierarchy() {
1767 let src = json!(
1768 {
1769 "entityTypes": { },
1770 "actions": {
1771 "read": {},
1772 "view": {
1773 "memberOf": [{"id": "read"}]
1774 },
1775 "view_photo": {
1776 "memberOf": [{"id": "view"}]
1777 },
1778 }
1779 });
1780
1781 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1782 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1783 let actions = schema.action_entities().expect("Entity Construct Error");
1784
1785 let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1786 let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
1787 let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
1788
1789 let view_photo_entity = actions.entity(&view_photo_uid);
1790 assert_eq!(
1791 view_photo_entity.unwrap(),
1792 &Entity::new_with_attr_partial_value(
1793 view_photo_uid,
1794 HashMap::new(),
1795 HashSet::from([view_uid.clone(), read_uid.clone()])
1796 )
1797 );
1798
1799 let view_entity = actions.entity(&view_uid);
1800 assert_eq!(
1801 view_entity.unwrap(),
1802 &Entity::new_with_attr_partial_value(
1803 view_uid,
1804 HashMap::new(),
1805 HashSet::from([read_uid.clone()])
1806 )
1807 );
1808
1809 let read_entity = actions.entity(&read_uid);
1810 assert_eq!(
1811 read_entity.unwrap(),
1812 &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
1813 );
1814 }
1815
1816 #[test]
1817 fn action_entity_attribute() {
1818 let src = json!(
1819 {
1820 "entityTypes": { },
1821 "actions": {
1822 "view_photo": {
1823 "attributes": { "attr": "foo" }
1824 },
1825 }
1826 });
1827
1828 let schema_file: NamespaceDefinitionWithActionAttributes =
1829 serde_json::from_value(src).expect("Parse Error");
1830 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1831 let actions = schema.action_entities().expect("Entity Construct Error");
1832
1833 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1834 let view_photo = actions.entity(&action_uid);
1835 assert_eq!(
1836 view_photo.unwrap(),
1837 &Entity::new(
1838 action_uid,
1839 HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
1840 HashSet::new(),
1841 &Extensions::none(),
1842 )
1843 .unwrap(),
1844 );
1845 }
1846
1847 #[test]
1848 fn test_action_namespace_inference_multi_success() {
1849 let src = json!({
1850 "Foo" : {
1851 "entityTypes" : {},
1852 "actions" : {
1853 "read" : {}
1854 }
1855 },
1856 "ExampleCo::Personnel" : {
1857 "entityTypes" : {},
1858 "actions" : {
1859 "viewPhoto" : {
1860 "memberOf" : [
1861 {
1862 "id" : "read",
1863 "type" : "Foo::Action"
1864 }
1865 ]
1866 }
1867 }
1868 },
1869 });
1870 let schema_fragment =
1871 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1872 let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
1873 let view_photo = schema
1874 .action_entities_iter()
1875 .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
1876 .unwrap();
1877 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
1878 let read = ancestors[0];
1879 assert_eq!(read.eid().to_string(), "read");
1880 assert_eq!(read.entity_type().to_string(), "Foo::Action");
1881 }
1882
1883 #[test]
1884 fn test_action_namespace_inference_multi() {
1885 let src = json!({
1886 "ExampleCo::Personnel::Foo" : {
1887 "entityTypes" : {},
1888 "actions" : {
1889 "read" : {}
1890 }
1891 },
1892 "ExampleCo::Personnel" : {
1893 "entityTypes" : {},
1894 "actions" : {
1895 "viewPhoto" : {
1896 "memberOf" : [
1897 {
1898 "id" : "read",
1899 "type" : "Foo::Action"
1900 }
1901 ]
1902 }
1903 }
1904 },
1905 });
1906 let schema_fragment =
1907 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1908 let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
1909 schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
1910 }
1911
1912 #[test]
1913 fn test_action_namespace_inference() {
1914 let src = json!({
1915 "ExampleCo::Personnel" : {
1916 "entityTypes" : { },
1917 "actions" : {
1918 "read" : {},
1919 "viewPhoto" : {
1920 "memberOf" : [
1921 {
1922 "id" : "read",
1923 "type" : "Action"
1924 }
1925 ]
1926 }
1927 }
1928 }
1929 });
1930 let schema_fragment =
1931 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1932 let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
1933 let view_photo = schema
1934 .action_entities_iter()
1935 .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
1936 .unwrap();
1937 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
1938 let read = ancestors[0];
1939 assert_eq!(read.eid().to_string(), "read");
1940 assert_eq!(
1941 read.entity_type().to_string(),
1942 "ExampleCo::Personnel::Action"
1943 );
1944 }
1945}