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