1use crate::{
24 ast::{Entity, EntityType, EntityUID, InternalName, Name, UnreservedId},
25 entities::{err::EntitiesError, Entities, TCComputation},
26 extensions::Extensions,
27 parser::Loc,
28 transitive_closure::compute_tc,
29};
30use educe::Educe;
31use namespace_def::EntityTypeFragment;
32use nonempty::NonEmpty;
33use serde::Deserialize;
34#[cfg(feature = "extended-schema")]
35use smol_str::SmolStr;
36use smol_str::ToSmolStr;
37use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet};
38use std::str::FromStr;
39use std::sync::Arc;
40
41#[cfg(feature = "extended-schema")]
42use crate::validator::types::Primitive;
43
44use crate::validator::{
45 cedar_schema::SchemaWarning,
46 json_schema,
47 partition_nonempty::PartitionNonEmpty,
48 types::{Attributes, EntityRecordKind, OpenTag, RequestEnv, Type},
49 ValidationMode,
50};
51
52mod action;
53pub use action::ValidatorActionId;
54pub(crate) use action::ValidatorApplySpec;
55mod entity_type;
56pub use entity_type::{ValidatorEntityType, ValidatorEntityTypeKind};
57mod namespace_def;
58pub(crate) use namespace_def::try_jsonschema_type_into_validator_type;
59pub use namespace_def::ValidatorNamespaceDef;
60mod raw_name;
61pub use raw_name::{ConditionalName, RawName, ReferenceType};
62pub(crate) mod err;
63use err::{schema_errors::*, *};
64
65#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
67pub enum ActionBehavior {
68 #[default]
74 ProhibitAttributes,
75 PermitAttributes,
77}
78
79#[derive(Debug, Clone)]
82pub struct ValidatorSchemaFragment<N, A>(Vec<ValidatorNamespaceDef<N, A>>);
83
84impl TryInto<ValidatorSchemaFragment<ConditionalName, ConditionalName>>
85 for json_schema::Fragment<RawName>
86{
87 type Error = SchemaError;
88
89 fn try_into(self) -> Result<ValidatorSchemaFragment<ConditionalName, ConditionalName>> {
90 ValidatorSchemaFragment::from_schema_fragment(
91 self,
92 ActionBehavior::default(),
93 Extensions::all_available(),
94 )
95 }
96}
97
98impl<N, A> ValidatorSchemaFragment<N, A> {
99 pub fn from_namespaces(
101 namespaces: impl IntoIterator<Item = ValidatorNamespaceDef<N, A>>,
102 ) -> Self {
103 Self(namespaces.into_iter().collect())
104 }
105
106 pub fn namespaces(&self) -> impl Iterator<Item = Option<&InternalName>> {
110 self.0.iter().map(|d| d.namespace())
111 }
112}
113
114impl ValidatorSchemaFragment<ConditionalName, ConditionalName> {
115 pub fn from_schema_fragment(
117 fragment: json_schema::Fragment<RawName>,
118 action_behavior: ActionBehavior,
119 extensions: &Extensions<'_>,
120 ) -> Result<Self> {
121 Ok(Self(
122 fragment
123 .0
124 .into_iter()
125 .map(|(fragment_ns, ns_def)| {
126 ValidatorNamespaceDef::from_namespace_definition(
127 fragment_ns.map(Into::into),
128 ns_def,
129 action_behavior,
130 extensions,
131 )
132 })
133 .partition_nonempty()?,
134 ))
135 }
136
137 pub fn fully_qualify_type_references(
144 self,
145 all_defs: &AllDefs,
146 ) -> Result<ValidatorSchemaFragment<InternalName, EntityType>> {
147 self.0
148 .into_iter()
149 .map(|ns_def| ns_def.fully_qualify_type_references(all_defs))
150 .partition_nonempty()
151 .map(ValidatorSchemaFragment)
152 .map_err(SchemaError::join_nonempty)
153 }
154}
155
156#[derive(Clone, Debug, Educe)]
160#[educe(Eq, PartialEq)]
161pub struct LocatedType {
162 ty: Type,
163 #[cfg(feature = "extended-schema")]
164 loc: Option<Loc>,
165}
166
167impl LocatedType {
168 pub fn new(ty: Type) -> Self {
170 Self {
171 ty,
172 #[cfg(feature = "extended-schema")]
173 loc: None,
174 }
175 }
176
177 pub fn new_with_loc(ty: Type, _loc: &Option<Loc>) -> Self {
180 Self {
181 ty,
182 #[cfg(feature = "extended-schema")]
183 loc: _loc.clone(),
184 }
185 }
186
187 pub fn into_type_and_loc(self) -> (Type, Option<Loc>) {
191 (
192 self.ty,
193 #[cfg(feature = "extended-schema")]
194 self.loc,
195 #[cfg(not(feature = "extended-schema"))]
196 None,
197 )
198 }
199}
200
201#[cfg(feature = "extended-schema")]
208#[derive(Clone, Debug, Educe)]
209#[educe(Eq, PartialEq, Hash)]
210pub struct LocatedCommonType {
211 pub name: SmolStr,
213
214 #[educe(Eq(ignore))]
216 pub name_loc: Option<Loc>,
217
218 #[educe(Eq(ignore))]
220 pub type_loc: Option<Loc>,
221}
222
223#[cfg(feature = "extended-schema")]
224impl LocatedCommonType {
225 pub fn new(name: &InternalName, ty: LocatedType) -> Self {
227 Self {
228 name: name.basename().clone().into_smolstr(),
229 name_loc: name.loc().cloned(),
230 type_loc: ty.loc,
231 }
232 }
233}
234
235#[cfg(feature = "extended-schema")]
241#[derive(Clone, Debug, Educe)]
242#[educe(Eq, PartialEq, Hash)]
243pub struct LocatedNamespace {
244 pub name: SmolStr,
246 #[educe(Eq(ignore))]
248 pub name_loc: Option<Loc>,
249
250 #[educe(Eq(ignore))]
252 pub def_loc: Option<Loc>,
253}
254
255#[derive(Clone, Debug)]
260pub struct ValidatorSchema {
261 entity_types: HashMap<EntityType, ValidatorEntityType>,
263
264 action_ids: HashMap<EntityUID, ValidatorActionId>,
266
267 pub(crate) actions: HashMap<EntityUID, Arc<Entity>>,
273
274 #[cfg(feature = "extended-schema")]
275 common_types: HashSet<LocatedCommonType>,
279
280 #[cfg(feature = "extended-schema")]
281 namespaces: HashSet<LocatedNamespace>,
285}
286
287impl std::str::FromStr for ValidatorSchema {
290 type Err = CedarSchemaError;
291
292 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
293 Self::from_cedarschema_str(s, Extensions::all_available()).map(|(schema, _)| schema)
294 }
295}
296
297impl TryFrom<json_schema::NamespaceDefinition<RawName>> for ValidatorSchema {
298 type Error = SchemaError;
299
300 fn try_from(nsd: json_schema::NamespaceDefinition<RawName>) -> Result<ValidatorSchema> {
301 ValidatorSchema::from_schema_fragments(
302 [ValidatorSchemaFragment::from_namespaces([nsd.try_into()?])],
303 Extensions::all_available(),
304 )
305 }
306}
307
308impl TryFrom<json_schema::Fragment<RawName>> for ValidatorSchema {
309 type Error = SchemaError;
310
311 fn try_from(frag: json_schema::Fragment<RawName>) -> Result<ValidatorSchema> {
312 ValidatorSchema::from_schema_fragments([frag.try_into()?], Extensions::all_available())
313 }
314}
315
316impl ValidatorSchema {
317 pub fn new(
319 entity_types: impl IntoIterator<Item = ValidatorEntityType>,
320 action_ids: impl IntoIterator<Item = ValidatorActionId>,
321 ) -> Self {
322 let entity_types = entity_types
323 .into_iter()
324 .map(|ety| (ety.name().clone(), ety))
325 .collect();
326 let action_ids = action_ids
327 .into_iter()
328 .map(|id| (id.name().clone(), id))
329 .collect();
330 Self::new_from_maps(
331 entity_types,
332 action_ids,
333 #[cfg(feature = "extended-schema")]
334 HashSet::new(),
335 #[cfg(feature = "extended-schema")]
336 HashSet::new(),
337 )
338 }
339
340 fn new_from_maps(
344 entity_types: HashMap<EntityType, ValidatorEntityType>,
345 action_ids: HashMap<EntityUID, ValidatorActionId>,
346 #[cfg(feature = "extended-schema")] common_types: HashSet<LocatedCommonType>,
347 #[cfg(feature = "extended-schema")] namespaces: HashSet<LocatedNamespace>,
348 ) -> Self {
349 let actions = Self::action_entities_iter(&action_ids)
350 .map(|e| (e.uid().clone(), Arc::new(e)))
351 .collect();
352 Self {
353 entity_types,
354 action_ids,
355 actions,
356 #[cfg(feature = "extended-schema")]
357 common_types,
358 #[cfg(feature = "extended-schema")]
359 namespaces,
360 }
361 }
362
363 #[cfg(feature = "extended-schema")]
365 pub fn common_types(&self) -> impl Iterator<Item = &LocatedCommonType> {
366 self.common_types.iter()
367 }
368
369 #[cfg(feature = "extended-schema")]
371 pub fn namespaces(&self) -> impl Iterator<Item = &LocatedNamespace> {
372 self.namespaces.iter()
373 }
374
375 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
377 self.action_ids
378 .values()
379 .flat_map(ValidatorActionId::principals)
380 }
381
382 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
384 self.action_ids
385 .values()
386 .flat_map(ValidatorActionId::resources)
387 }
388
389 pub fn principals_for_action(
395 &self,
396 action: &EntityUID,
397 ) -> Option<impl Iterator<Item = &EntityType>> {
398 self.action_ids
399 .get(action)
400 .map(ValidatorActionId::principals)
401 }
402
403 pub fn resources_for_action(
409 &self,
410 action: &EntityUID,
411 ) -> Option<impl Iterator<Item = &EntityType>> {
412 self.action_ids
413 .get(action)
414 .map(ValidatorActionId::resources)
415 }
416
417 pub fn actions_for_principal_and_resource<'a: 'b, 'b>(
421 &'a self,
422 principal_type: &'b EntityType,
423 resource_type: &'b EntityType,
424 ) -> impl Iterator<Item = &'a EntityUID> + 'b {
425 self.action_ids()
426 .filter(|action| {
427 action.is_applicable_principal_type(principal_type)
428 && action.is_applicable_resource_type(resource_type)
429 })
430 .map(|action| action.name())
431 }
432
433 pub fn unlinked_request_envs(
435 &self,
436 mode: ValidationMode,
437 ) -> impl Iterator<Item = RequestEnv<'_>> + '_ {
438 self.action_ids()
441 .flat_map(|action| {
442 action.applies_to_principals().flat_map(|principal| {
443 action
444 .applies_to_resources()
445 .map(|resource| RequestEnv::DeclaredAction {
446 principal,
447 action: &action.name,
448 resource,
449 context: &action.context,
450 principal_slot: None,
451 resource_slot: None,
452 })
453 })
454 })
455 .chain(if mode.is_partial() {
456 Some(RequestEnv::UndeclaredAction)
461 } else {
462 None
463 })
464 }
465
466 pub fn ancestors<'a>(
472 &'a self,
473 ty: &'a EntityType,
474 ) -> Option<impl Iterator<Item = &'a EntityType> + 'a> {
475 if self.entity_types.contains_key(ty) {
476 Some(self.entity_types.values().filter_map(|ety| {
477 if ety.descendants.contains(ty) {
478 Some(&ety.name)
479 } else {
480 None
481 }
482 }))
483 } else {
484 None
485 }
486 }
487
488 pub fn action_groups(&self) -> impl Iterator<Item = &EntityUID> {
490 self.action_ids.values().filter_map(|action| {
491 if action.descendants.is_empty() {
492 None
493 } else {
494 Some(&action.name)
495 }
496 })
497 }
498
499 pub fn actions(&self) -> impl Iterator<Item = &EntityUID> {
501 self.action_ids.keys()
502 }
503
504 pub fn empty() -> ValidatorSchema {
507 Self {
508 entity_types: HashMap::new(),
509 action_ids: HashMap::new(),
510 actions: HashMap::new(),
511 #[cfg(feature = "extended-schema")]
512 common_types: HashSet::new(),
513 #[cfg(feature = "extended-schema")]
514 namespaces: HashSet::new(),
515 }
516 }
517
518 pub fn from_json_value(json: serde_json::Value, extensions: &Extensions<'_>) -> Result<Self> {
521 Self::from_schema_frag(
522 json_schema::Fragment::<RawName>::from_json_value(json)?,
523 ActionBehavior::default(),
524 extensions,
525 )
526 }
527
528 pub fn from_json_str(json: &str, extensions: &Extensions<'_>) -> Result<Self> {
531 Self::from_schema_frag(
532 json_schema::Fragment::<RawName>::from_json_str(json)?,
533 ActionBehavior::default(),
534 extensions,
535 )
536 }
537
538 pub fn from_json_file(file: impl std::io::Read, extensions: &Extensions<'_>) -> Result<Self> {
541 Self::from_schema_frag(
542 json_schema::Fragment::<RawName>::from_json_file(file)?,
543 ActionBehavior::default(),
544 extensions,
545 )
546 }
547
548 pub fn from_cedarschema_file<'a>(
551 r: impl std::io::Read,
552 extensions: &'a Extensions<'a>,
553 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
554 {
555 let (fragment, warnings) = json_schema::Fragment::from_cedarschema_file(r, extensions)?;
556 let schema_and_warnings =
557 Self::from_schema_frag(fragment, ActionBehavior::default(), extensions)
558 .map(|schema| (schema, warnings))?;
559 Ok(schema_and_warnings)
560 }
561
562 pub fn from_cedarschema_str<'a>(
565 src: &str,
566 extensions: &Extensions<'a>,
567 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
568 {
569 let (fragment, warnings) = json_schema::Fragment::from_cedarschema_str(src, extensions)?;
570 let schema_and_warnings =
571 Self::from_schema_frag(fragment, ActionBehavior::default(), extensions)
572 .map(|schema| (schema, warnings))?;
573 Ok(schema_and_warnings)
574 }
575
576 pub(crate) fn from_schema_frag(
578 schema_file: json_schema::Fragment<RawName>,
579 action_behavior: ActionBehavior,
580 extensions: &Extensions<'_>,
581 ) -> Result<ValidatorSchema> {
582 Self::from_schema_fragments(
583 [ValidatorSchemaFragment::from_schema_fragment(
584 schema_file,
585 action_behavior,
586 extensions,
587 )?],
588 extensions,
589 )
590 }
591
592 pub fn from_schema_fragments(
594 fragments: impl IntoIterator<Item = ValidatorSchemaFragment<ConditionalName, ConditionalName>>,
595 extensions: &Extensions<'_>,
596 ) -> Result<ValidatorSchema> {
597 let mut fragments = fragments
598 .into_iter()
599 .chain(std::iter::once(cedar_fragment(extensions)))
602 .collect::<Vec<_>>();
603
604 #[cfg(feature = "extended-schema")]
606 let validator_namespaces = fragments
607 .clone()
608 .into_iter()
609 .flat_map(|f| f.0.into_iter().map(|n| (n.namespace().cloned(), n.loc)))
610 .filter_map(|n| match n {
611 (Some(name), loc) => Some((name, loc)),
612 (None, _) => None,
613 })
614 .map(|n| LocatedNamespace {
615 name: n.0.to_smolstr(),
616 name_loc: n.0.loc().cloned(),
617 def_loc: n.1,
618 })
619 .collect::<HashSet<_>>();
620
621 let mut all_defs = AllDefs::new(|| fragments.iter());
624
625 all_defs.rfc_70_shadowing_checks()?;
634
635 for tyname in primitive_types::<Name>()
643 .map(|(id, _)| Name::unqualified_name(id))
644 .chain(extensions.ext_types().cloned())
645 {
646 if !all_defs.is_defined_as_entity(tyname.as_ref())
647 && !all_defs.is_defined_as_common(tyname.as_ref())
648 {
649 assert!(
650 tyname.is_unqualified(),
651 "expected all primitive and extension type names to be unqualified"
652 );
653 fragments.push(single_alias_in_empty_namespace(
654 tyname.basename().clone(),
655 tyname.as_ref().qualify_with(Some(&InternalName::__cedar())),
656 None, ));
658 all_defs.mark_as_defined_as_common_type(tyname.into());
659 }
660 }
661
662 let action_types: HashSet<InternalName> = all_defs
666 .action_defs
667 .iter()
668 .map(|action_def| action_def.entity_type().as_ref().as_ref().clone())
669 .collect();
670 for action_type in action_types {
671 all_defs.mark_as_defined_as_entity_type(action_type);
672 }
673
674 let fragments: Vec<_> = fragments
682 .into_iter()
683 .map(|frag| frag.fully_qualify_type_references(&all_defs))
684 .partition_nonempty()?;
685
686 let mut common_types = HashMap::new();
692 let mut entity_type_fragments: HashMap<EntityType, _> = HashMap::new();
693 let mut action_fragments = HashMap::new();
694 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
695 for (name, ty) in ns_def.common_types.defs {
696 match common_types.entry(name) {
697 Entry::Vacant(v) => v.insert(ty),
698 Entry::Occupied(o) => {
699 return Err(DuplicateCommonTypeError {
700 ty: o.key().clone(),
701 }
702 .into());
703 }
704 };
705 }
706
707 for (name, entity_type) in ns_def.entity_types.defs {
708 match entity_type_fragments.entry(name) {
709 Entry::Vacant(v) => v.insert(entity_type),
710 Entry::Occupied(o) => {
711 return Err(DuplicateEntityTypeError {
712 ty: o.key().clone(),
713 }
714 .into())
715 }
716 };
717 }
718
719 for (action_euid, action) in ns_def.actions.actions {
720 match action_fragments.entry(action_euid) {
721 Entry::Vacant(v) => v.insert(action),
722 Entry::Occupied(o) => {
723 return Err(DuplicateActionError(o.key().to_smolstr()).into())
724 }
725 };
726 }
727 }
728
729 let resolver = CommonTypeResolver::new(&common_types);
730 let common_types: HashMap<&InternalName, LocatedType> = resolver.resolve(extensions)?;
731
732 let mut entity_children: HashMap<EntityType, HashSet<EntityType>> = HashMap::new();
735 for (name, entity_type) in entity_type_fragments.iter() {
736 for parent in entity_type.parents() {
737 entity_children
738 .entry(internal_name_to_entity_type(parent.clone())?)
739 .or_default()
740 .insert(name.clone());
741 }
742 }
743 let mut entity_types = entity_type_fragments
744 .into_iter()
745 .map(|(name, entity_type)| -> Result<_> {
746 let descendants = entity_children.remove(&name).unwrap_or_default();
756
757 match entity_type {
758 EntityTypeFragment::Enum(choices) => Ok((
759 name.clone(),
760 ValidatorEntityType::new_enum(
761 name.clone(),
762 descendants,
763 choices,
764 name.loc().cloned(),
765 ),
766 )),
767 EntityTypeFragment::Standard {
768 attributes,
769 parents: _,
770 tags,
771 } => {
772 let (attributes, open_attributes) = {
773 let attr_loc = attributes.0.loc().cloned();
774 let unresolved = try_jsonschema_type_into_validator_type(
775 attributes.0,
776 extensions,
777 attr_loc,
778 )?;
779 Self::record_attributes_or_none(
780 unresolved.resolve_common_type_refs(&common_types)?,
781 )
782 .ok_or_else(|| {
783 ContextOrShapeNotRecordError {
784 ctx_or_shape: ContextOrShape::EntityTypeShape(name.clone()),
785 }
786 })?
787 };
788 let tags = tags
789 .map(|tags| {
790 let tags_loc = tags.loc().cloned();
791 try_jsonschema_type_into_validator_type(tags, extensions, tags_loc)
792 })
793 .transpose()?
794 .map(|unresolved| unresolved.resolve_common_type_refs(&common_types))
795 .transpose()?;
796
797 Ok((
798 name.with_loc(name.loc()),
799 ValidatorEntityType::new_standard(
800 name.clone(),
801 descendants,
802 attributes,
803 open_attributes,
804 tags.map(|t| t.ty),
805 name.loc().cloned(),
806 ),
807 ))
808 }
809 }
810 })
811 .partition_nonempty()?;
812
813 let mut action_children = HashMap::new();
814 for (euid, action) in action_fragments.iter() {
815 for parent in action.parents.iter() {
816 action_children
817 .entry(parent.clone().try_into()?)
818 .or_insert_with(HashSet::new)
819 .insert(euid.clone());
820 }
821 }
822 let mut action_ids = action_fragments
823 .into_iter()
824 .map(|(name, action)| -> Result<_> {
825 let descendants = action_children.remove(&name).unwrap_or_default();
826 let (context, open_context_attributes) = {
827 let context_loc = action.context.loc().cloned();
828 let unresolved = try_jsonschema_type_into_validator_type(
829 action.context,
830 extensions,
831 context_loc,
832 )?;
833 Self::record_attributes_or_none(
834 unresolved.resolve_common_type_refs(&common_types)?,
835 )
836 .ok_or_else(|| ContextOrShapeNotRecordError {
837 ctx_or_shape: ContextOrShape::ActionContext(name.clone()),
838 })?
839 };
840 Ok((
841 name.clone(),
842 ValidatorActionId {
843 name,
844 applies_to: action.applies_to,
845 descendants,
846 context: Type::record_with_attributes(context, open_context_attributes),
847 attribute_types: action.attribute_types,
848 attributes: action.attributes,
849 loc: action.loc,
850 },
851 ))
852 })
853 .partition_nonempty()?;
854
855 compute_tc(&mut entity_types, false)
858 .map_err(|e| EntityTypeTransitiveClosureError::from(Box::new(e)))?;
859 compute_tc(&mut action_ids, true)?;
862
863 #[cfg(feature = "extended-schema")]
864 let common_type_validators = common_types
865 .clone()
866 .into_iter()
867 .filter(|ct| {
868 let ct_name = ct.0.clone();
870 ct_name.loc().is_some() && !Primitive::is_primitive(ct_name.basename().as_ref())
871 })
872 .map(|ct| LocatedCommonType::new(ct.0, ct.1))
873 .collect();
874
875 Self::check_for_undeclared(
882 &all_defs,
883 &entity_types,
884 entity_children.into_keys(),
885 &action_ids,
886 action_children.into_keys(),
887 common_types.into_values(),
888 )?;
889 Ok(ValidatorSchema::new_from_maps(
890 entity_types,
891 action_ids,
892 #[cfg(feature = "extended-schema")]
893 common_type_validators,
894 #[cfg(feature = "extended-schema")]
895 validator_namespaces,
896 ))
897 }
898
899 fn check_for_undeclared(
904 all_defs: &AllDefs,
905 entity_types: &HashMap<EntityType, ValidatorEntityType>,
906 undeclared_parent_entities: impl IntoIterator<Item = EntityType>,
907 action_ids: &HashMap<EntityUID, ValidatorActionId>,
908 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
909 common_types: impl IntoIterator<Item = LocatedType>,
910 ) -> Result<()> {
911 let mut undeclared_e = undeclared_parent_entities
916 .into_iter()
917 .collect::<BTreeSet<EntityType>>();
918 for entity_type in entity_types.values() {
924 for (_, attr_typ) in entity_type.attributes().iter() {
925 Self::check_undeclared_in_type(&attr_typ.attr_type, all_defs, &mut undeclared_e);
926 }
927 }
928
929 for common_type in common_types {
931 Self::check_undeclared_in_type(&common_type.ty, all_defs, &mut undeclared_e);
932 }
933
934 let undeclared_a = undeclared_parent_actions.into_iter();
936 for action in action_ids.values() {
940 Self::check_undeclared_in_type(&action.context, all_defs, &mut undeclared_e);
941
942 for p_entity in action.applies_to_principals() {
943 if !entity_types.contains_key(p_entity) {
944 undeclared_e.insert(p_entity.clone());
945 }
946 }
947
948 for r_entity in action.applies_to_resources() {
949 if !entity_types.contains_key(r_entity) {
950 undeclared_e.insert(r_entity.clone());
951 }
952 }
953 }
954 if let Some(types) = NonEmpty::collect(undeclared_e) {
955 return Err(UndeclaredEntityTypesError { types }.into());
956 }
957 if let Some(euids) = NonEmpty::collect(undeclared_a) {
958 return Err(ActionInvariantViolationError { euids }.into());
961 }
962
963 Ok(())
964 }
965
966 fn record_attributes_or_none(ty: LocatedType) -> Option<(Attributes, OpenTag)> {
967 match ty.ty {
968 Type::EntityOrRecord(EntityRecordKind::Record {
969 attrs,
970 open_attributes,
971 }) => Some((attrs, open_attributes)),
972 _ => None,
973 }
974 }
975
976 fn check_undeclared_in_type(
980 ty: &Type,
981 all_defs: &AllDefs,
982 undeclared_types: &mut BTreeSet<EntityType>,
983 ) {
984 match ty {
985 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
986 for name in lub.iter() {
987 if !all_defs.is_defined_as_entity(name.as_ref().as_ref()) {
988 undeclared_types.insert(name.clone());
989 }
990 }
991 }
992
993 Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
994 for (_, attr_ty) in attrs.iter() {
995 Self::check_undeclared_in_type(&attr_ty.attr_type, all_defs, undeclared_types);
996 }
997 }
998
999 Type::Set {
1000 element_type: Some(element_type),
1001 } => Self::check_undeclared_in_type(element_type, all_defs, undeclared_types),
1002
1003 _ => (),
1004 }
1005 }
1006
1007 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
1009 self.action_ids.get(action_id)
1010 }
1011
1012 pub fn get_entity_type<'a>(
1014 &'a self,
1015 entity_type_id: &EntityType,
1016 ) -> Option<&'a ValidatorEntityType> {
1017 self.entity_types.get(entity_type_id)
1018 }
1019
1020 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
1022 self.action_ids.contains_key(action_id)
1023 }
1024
1025 pub(crate) fn is_known_entity_type(&self, entity_type: &EntityType) -> bool {
1027 entity_type.is_action() || self.entity_types.contains_key(entity_type)
1028 }
1029
1030 pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
1032 self.is_known_entity_type(euid.entity_type())
1033 }
1034
1035 pub fn action_ids(&self) -> impl Iterator<Item = &ValidatorActionId> {
1037 self.action_ids.values()
1038 }
1039
1040 pub fn entity_type_names(&self) -> impl Iterator<Item = &EntityType> {
1042 self.entity_types.keys()
1043 }
1044
1045 pub fn entity_types(&self) -> impl Iterator<Item = &ValidatorEntityType> {
1047 self.entity_types.values()
1048 }
1049
1050 pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&'a EntityType> {
1056 let mut descendants = self
1057 .get_entity_type(entity.entity_type())
1058 .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
1059 .unwrap_or_default();
1060 descendants.push(entity.entity_type());
1061 descendants
1062 }
1063
1064 pub(crate) fn get_entity_types_in_set<'a>(
1068 &'a self,
1069 euids: impl IntoIterator<Item = &'a EntityUID>,
1070 ) -> impl Iterator<Item = &'a EntityType> {
1071 euids.into_iter().flat_map(|e| self.get_entity_types_in(e))
1072 }
1073
1074 pub(crate) fn get_actions_in_set<'a>(
1078 &'a self,
1079 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
1080 ) -> Option<Vec<&'a EntityUID>> {
1081 euids
1082 .into_iter()
1083 .map(|e| {
1084 self.get_action_id(e).map(|action| {
1085 action
1086 .descendants
1087 .iter()
1088 .chain(std::iter::once(&action.name))
1089 })
1090 })
1091 .collect::<Option<Vec<_>>>()
1092 .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
1093 }
1094
1095 pub fn context_type(&self, action: &EntityUID) -> Option<&Type> {
1100 self.get_action_id(action)
1103 .map(ValidatorActionId::context_type)
1104 }
1105
1106 pub(crate) fn action_entities_iter(
1109 action_ids: &HashMap<EntityUID, ValidatorActionId>,
1110 ) -> impl Iterator<Item = crate::ast::Entity> + '_ {
1111 let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
1117 for (action_euid, action_def) in action_ids {
1118 for descendant in &action_def.descendants {
1119 action_ancestors
1120 .entry(descendant)
1121 .or_default()
1122 .insert(action_euid.clone());
1123 }
1124 }
1125 action_ids.iter().map(move |(action_id, action)| {
1126 Entity::new_with_attr_partial_value(
1127 action_id.clone(),
1128 action.attributes.clone(),
1129 HashSet::new(),
1130 action_ancestors.remove(action_id).unwrap_or_default(),
1131 [], )
1133 })
1134 }
1135
1136 pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
1138 let extensions = Extensions::all_available();
1139 Entities::from_entities(
1140 self.actions.values().map(|entity| entity.as_ref().clone()),
1141 None::<&crate::entities::NoEntitiesSchema>, TCComputation::AssumeAlreadyComputed,
1143 extensions,
1144 )
1145 }
1146}
1147
1148#[derive(Debug, Clone, Deserialize)]
1151#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1152#[serde(transparent)]
1153#[allow(
1154 dead_code,
1155 reason = "Not actually dead, but linter mistakenly thinks this code is dead"
1156)]
1157pub(crate) struct NamespaceDefinitionWithActionAttributes<N>(
1158 pub(crate) json_schema::NamespaceDefinition<N>,
1159);
1160
1161impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes<RawName> {
1162 type Error = SchemaError;
1163
1164 fn try_into(self) -> Result<ValidatorSchema> {
1165 ValidatorSchema::from_schema_fragments(
1166 [ValidatorSchemaFragment::from_namespaces([
1167 ValidatorNamespaceDef::from_namespace_definition(
1168 None,
1169 self.0,
1170 crate::validator::ActionBehavior::PermitAttributes,
1171 Extensions::all_available(),
1172 )?,
1173 ])],
1174 Extensions::all_available(),
1175 )
1176 }
1177}
1178
1179fn cedar_fragment(
1182 extensions: &Extensions<'_>,
1183) -> ValidatorSchemaFragment<ConditionalName, ConditionalName> {
1184 #[allow(clippy::unwrap_used)]
1186 let mut common_types = HashMap::from_iter(primitive_types());
1187 for ext_type in extensions.ext_types() {
1188 assert!(
1189 ext_type.is_unqualified(),
1190 "expected extension type names to be unqualified"
1191 );
1192 let ext_type = ext_type.basename().clone();
1193 common_types.insert(
1194 ext_type.clone(),
1195 json_schema::Type::Type {
1196 ty: json_schema::TypeVariant::Extension { name: ext_type },
1197 loc: None,
1198 },
1199 );
1200 }
1201
1202 #[allow(clippy::unwrap_used)]
1204 ValidatorSchemaFragment(vec![ValidatorNamespaceDef::from_common_type_defs(
1205 Some(InternalName::__cedar()),
1206 common_types,
1207 )
1208 .unwrap()])
1209}
1210
1211fn single_alias_in_empty_namespace(
1219 id: UnreservedId,
1220 def: InternalName,
1221 loc: Option<Loc>,
1222) -> ValidatorSchemaFragment<ConditionalName, ConditionalName> {
1223 ValidatorSchemaFragment(vec![ValidatorNamespaceDef::from_common_type_def(
1224 None,
1225 (
1226 id,
1227 json_schema::Type::Type {
1228 ty: json_schema::TypeVariant::EntityOrCommon {
1229 type_name: ConditionalName::unconditional(def, ReferenceType::CommonOrEntity),
1230 },
1231 loc,
1232 },
1233 ),
1234 )])
1235}
1236
1237fn primitive_types<N>() -> impl Iterator<Item = (UnreservedId, json_schema::Type<N>)> {
1240 #[allow(clippy::unwrap_used)]
1242 [
1243 (
1244 UnreservedId::from_str("Bool").unwrap(),
1245 json_schema::Type::Type {
1246 ty: json_schema::TypeVariant::Boolean,
1247 loc: None,
1248 },
1249 ),
1250 (
1251 UnreservedId::from_str("Long").unwrap(),
1252 json_schema::Type::Type {
1253 ty: json_schema::TypeVariant::Long,
1254 loc: None,
1255 },
1256 ),
1257 (
1258 UnreservedId::from_str("String").unwrap(),
1259 json_schema::Type::Type {
1260 ty: json_schema::TypeVariant::String,
1261 loc: None,
1262 },
1263 ),
1264 ]
1265 .into_iter()
1266}
1267
1268fn internal_name_to_entity_type(
1273 name: InternalName,
1274) -> std::result::Result<EntityType, crate::ast::ReservedNameError> {
1275 Name::try_from(name).map(Into::into)
1276}
1277
1278#[derive(Debug)]
1281pub struct AllDefs {
1282 entity_defs: HashSet<InternalName>,
1284 common_defs: HashSet<InternalName>,
1286 action_defs: HashSet<EntityUID>,
1288}
1289
1290impl AllDefs {
1291 pub fn new<'a, N: 'a, A: 'a, I>(fragments: impl Fn() -> I) -> Self
1294 where
1295 I: Iterator<Item = &'a ValidatorSchemaFragment<N, A>>,
1296 {
1297 Self {
1298 entity_defs: fragments()
1299 .flat_map(|f| f.0.iter())
1300 .flat_map(|ns_def| ns_def.all_declared_entity_type_names().cloned())
1301 .collect(),
1302 common_defs: fragments()
1303 .flat_map(|f| f.0.iter())
1304 .flat_map(|ns_def| ns_def.all_declared_common_type_names().cloned())
1305 .collect(),
1306 action_defs: fragments()
1307 .flat_map(|f| f.0.iter())
1308 .flat_map(|ns_def| ns_def.all_declared_action_names().cloned())
1309 .collect(),
1310 }
1311 }
1312
1313 pub fn single_fragment<N, A>(fragment: &ValidatorSchemaFragment<N, A>) -> Self {
1318 Self::new(|| std::iter::once(fragment))
1319 }
1320
1321 pub fn is_defined_as_entity(&self, name: &InternalName) -> bool {
1324 self.entity_defs.contains(name)
1325 }
1326
1327 pub fn is_defined_as_common(&self, name: &InternalName) -> bool {
1330 self.common_defs.contains(name)
1331 }
1332
1333 pub fn is_defined_as_action(&self, euid: &EntityUID) -> bool {
1336 self.action_defs.contains(euid)
1337 }
1338
1339 pub fn mark_as_defined_as_entity_type(&mut self, name: InternalName) {
1341 self.entity_defs.insert(name);
1342 }
1343
1344 pub fn mark_as_defined_as_common_type(&mut self, name: InternalName) {
1346 self.common_defs.insert(name);
1347 }
1348
1349 pub fn rfc_70_shadowing_checks(&self) -> Result<()> {
1358 for unqualified_name in self
1359 .entity_and_common_names()
1360 .filter(|name| name.is_unqualified())
1361 {
1362 if let Some(name) = self.entity_and_common_names().find(|name| {
1364 !name.is_unqualified() && !name.is_reserved() && name.basename() == unqualified_name.basename()
1367 }) {
1368 return Err(TypeShadowingError {
1369 shadowed_def: unqualified_name.clone(),
1370 shadowing_def: name.clone(),
1371 }
1372 .into());
1373 }
1374 }
1375 for unqualified_action in self
1376 .action_defs
1377 .iter()
1378 .filter(|euid| euid.entity_type().as_ref().is_unqualified())
1379 {
1380 if let Some(action) = self.action_defs.iter().find(|euid| {
1382 !euid.entity_type().as_ref().is_unqualified() && euid.eid() == unqualified_action.eid()
1385 }) {
1386 return Err(ActionShadowingError {
1387 shadowed_def: unqualified_action.clone(),
1388 shadowing_def: action.clone(),
1389 }
1390 .into());
1391 }
1392 }
1393 Ok(())
1394 }
1395
1396 fn entity_and_common_names(&self) -> impl Iterator<Item = &InternalName> {
1399 self.entity_defs.iter().chain(self.common_defs.iter())
1400 }
1401}
1402
1403#[cfg(test)]
1404impl AllDefs {
1405 pub(crate) fn from_entity_defs(names: impl IntoIterator<Item = InternalName>) -> Self {
1409 Self {
1410 entity_defs: names.into_iter().collect(),
1411 common_defs: HashSet::new(),
1412 action_defs: HashSet::new(),
1413 }
1414 }
1415}
1416
1417#[derive(Debug)]
1428struct CommonTypeResolver<'a> {
1429 defs: &'a HashMap<InternalName, json_schema::Type<InternalName>>,
1438 graph: HashMap<&'a InternalName, HashSet<&'a InternalName>>,
1446}
1447
1448impl<'a> CommonTypeResolver<'a> {
1449 fn new(defs: &'a HashMap<InternalName, json_schema::Type<InternalName>>) -> Self {
1460 let mut graph = HashMap::new();
1461 for (name, ty) in defs {
1462 graph.insert(name, HashSet::from_iter(ty.common_type_references()));
1463 }
1464 Self { defs, graph }
1465 }
1466
1467 fn topo_sort(&self) -> std::result::Result<Vec<&'a InternalName>, InternalName> {
1478 let mut indegrees: HashMap<&InternalName, usize> = HashMap::new();
1482 for (ty_name, deps) in self.graph.iter() {
1483 indegrees.entry(ty_name).or_insert(0);
1485 for dep in deps {
1486 match indegrees.entry(dep) {
1487 std::collections::hash_map::Entry::Occupied(mut o) => {
1488 o.insert(o.get() + 1);
1489 }
1490 std::collections::hash_map::Entry::Vacant(v) => {
1491 v.insert(1);
1492 }
1493 }
1494 }
1495 }
1496
1497 let mut work_set: HashSet<&'a InternalName> = HashSet::new();
1499 let mut res: Vec<&'a InternalName> = Vec::new();
1500
1501 for (name, degree) in indegrees.iter() {
1503 let name = *name;
1504 if *degree == 0 {
1505 work_set.insert(name);
1506 if self.graph.contains_key(name) {
1508 res.push(name);
1509 }
1510 }
1511 }
1512
1513 while let Some(name) = work_set.iter().next().copied() {
1515 work_set.remove(name);
1516 if let Some(deps) = self.graph.get(name) {
1517 for dep in deps {
1518 if let Some(degree) = indegrees.get_mut(dep) {
1519 *degree -= 1;
1529 if *degree == 0 {
1530 work_set.insert(dep);
1531 if self.graph.contains_key(dep) {
1532 res.push(dep);
1533 }
1534 }
1535 }
1536 }
1537 }
1538 }
1539
1540 let mut set: HashSet<&InternalName> = HashSet::from_iter(self.graph.keys().copied());
1543 for name in res.iter() {
1544 set.remove(name);
1545 }
1546
1547 if let Some(cycle) = set.into_iter().next() {
1548 Err(cycle.clone())
1549 } else {
1550 res.reverse();
1553 Ok(res)
1554 }
1555 }
1556
1557 fn resolve_type(
1562 resolve_table: &HashMap<&InternalName, json_schema::Type<InternalName>>,
1563 ty: json_schema::Type<InternalName>,
1564 ) -> Result<json_schema::Type<InternalName>> {
1565 match ty {
1566 json_schema::Type::CommonTypeRef { type_name, .. } => resolve_table
1567 .get(&type_name)
1568 .ok_or_else(|| CommonTypeInvariantViolationError { name: type_name }.into())
1569 .cloned(),
1570 json_schema::Type::Type {
1571 ty: json_schema::TypeVariant::EntityOrCommon { type_name },
1572 loc,
1573 } => match resolve_table.get(&type_name) {
1574 Some(def) => Ok(def.clone().with_loc(loc)),
1575
1576 None => Ok(json_schema::Type::Type {
1577 ty: json_schema::TypeVariant::Entity { name: type_name },
1578 loc,
1579 }),
1580 },
1581 json_schema::Type::Type {
1582 ty: json_schema::TypeVariant::Set { element },
1583 loc,
1584 } => Ok(json_schema::Type::Type {
1585 ty: json_schema::TypeVariant::Set {
1586 element: Box::new(Self::resolve_type(resolve_table, *element)?),
1587 },
1588 loc,
1589 }),
1590 json_schema::Type::Type {
1591 ty:
1592 json_schema::TypeVariant::Record(json_schema::RecordType {
1593 attributes,
1594 additional_attributes,
1595 }),
1596 loc,
1597 } => Ok(json_schema::Type::Type {
1598 ty: json_schema::TypeVariant::Record(json_schema::RecordType {
1599 attributes: BTreeMap::from_iter(
1600 attributes
1601 .into_iter()
1602 .map(|(attr, attr_ty)| -> Result<_> {
1603 Ok((
1604 attr,
1605 json_schema::TypeOfAttribute {
1606 required: attr_ty.required,
1607 ty: Self::resolve_type(resolve_table, attr_ty.ty)?,
1608 annotations: attr_ty.annotations,
1609 #[cfg(feature = "extended-schema")]
1610 loc: attr_ty.loc,
1611 },
1612 ))
1613 })
1614 .partition_nonempty::<Vec<_>>()?,
1615 ),
1616 additional_attributes,
1617 }),
1618 loc,
1619 }),
1620 _ => Ok(ty),
1621 }
1622 }
1623
1624 fn resolve(
1627 &self,
1628 extensions: &Extensions<'_>,
1629 ) -> Result<HashMap<&'a InternalName, LocatedType>> {
1630 let sorted_names = self.topo_sort().map_err(|n| {
1631 SchemaError::CycleInCommonTypeReferences(CycleInCommonTypeReferencesError { ty: n })
1632 })?;
1633
1634 let mut resolve_table: HashMap<&InternalName, json_schema::Type<InternalName>> =
1635 HashMap::new();
1636 let mut tys: HashMap<&'a InternalName, LocatedType> = HashMap::new();
1637
1638 for &name in sorted_names.iter() {
1639 #[allow(clippy::unwrap_used)]
1641 let ty = self.defs.get(name).unwrap();
1642 let substituted_ty = Self::resolve_type(&resolve_table, ty.clone())?;
1643 resolve_table.insert(name, substituted_ty.clone());
1644 let substituted_ty_loc = substituted_ty.loc().cloned();
1645 let validator_type = try_jsonschema_type_into_validator_type(
1646 substituted_ty,
1647 extensions,
1648 substituted_ty_loc,
1649 )?;
1650 let validator_type = validator_type.resolve_common_type_refs(&HashMap::new())?;
1651
1652 tys.insert(name, validator_type);
1653 }
1654
1655 Ok(tys)
1656 }
1657}
1658
1659#[allow(clippy::panic)]
1661#[allow(clippy::indexing_slicing)]
1663#[cfg(test)]
1664pub(crate) mod test {
1665 use std::{
1666 collections::{BTreeMap, HashSet},
1667 str::FromStr,
1668 };
1669
1670 use crate::validator::json_schema;
1671 use crate::validator::types::Type;
1672
1673 use crate::ast::RestrictedExpr;
1674 use crate::test_utils::{expect_err, ExpectedErrorMessageBuilder};
1675 use cool_asserts::assert_matches;
1676
1677 use serde_json::json;
1678
1679 use super::*;
1680
1681 pub(crate) mod utils {
1682 use super::{CedarSchemaError, SchemaError, ValidatorEntityType, ValidatorSchema};
1683 use crate::extensions::Extensions;
1684
1685 pub fn collect_warnings<A, B, E>(
1690 r: std::result::Result<(A, impl Iterator<Item = B>), E>,
1691 ) -> std::result::Result<(A, Vec<B>), E> {
1692 r.map(|(a, iter)| (a, iter.collect()))
1693 }
1694
1695 #[track_caller]
1699 pub fn assert_entity_type_exists<'s>(
1700 schema: &'s ValidatorSchema,
1701 etype: &str,
1702 ) -> &'s ValidatorEntityType {
1703 schema.get_entity_type(&etype.parse().unwrap()).unwrap()
1704 }
1705
1706 #[track_caller]
1707 pub fn assert_valid_cedar_schema(src: &str) -> ValidatorSchema {
1708 match ValidatorSchema::from_cedarschema_str(src, Extensions::all_available()) {
1709 Ok((schema, _)) => schema,
1710 Err(e) => panic!("{:?}", miette::Report::new(e)),
1711 }
1712 }
1713
1714 #[track_caller]
1715 pub fn assert_invalid_cedar_schema(src: &str) {
1716 match ValidatorSchema::from_cedarschema_str(src, Extensions::all_available()) {
1717 Ok(_) => panic!("{src} should be an invalid schema"),
1718 Err(CedarSchemaError::Parsing(_)) => {}
1719 Err(e) => panic!("unexpected error: {:?}", miette::Report::new(e)),
1720 }
1721 }
1722
1723 #[track_caller]
1724 pub fn assert_valid_json_schema(json: serde_json::Value) -> ValidatorSchema {
1725 match ValidatorSchema::from_json_value(json, Extensions::all_available()) {
1726 Ok(schema) => schema,
1727 Err(e) => panic!("{:?}", miette::Report::new(e)),
1728 }
1729 }
1730
1731 #[track_caller]
1732 pub fn assert_invalid_json_schema(json: &serde_json::Value) {
1733 match ValidatorSchema::from_json_value(json.clone(), Extensions::all_available()) {
1734 Ok(_) => panic!("{json} should be an invalid schema"),
1735 Err(SchemaError::JsonDeserialization(_)) => {}
1736 Err(e) => panic!("unexpected error: {:?}", miette::Report::new(e)),
1737 }
1738 }
1739 }
1740
1741 use utils::*;
1742
1743 #[test]
1745 fn test_from_schema_file() {
1746 let src = json!(
1747 {
1748 "entityTypes": {
1749 "User": {
1750 "memberOfTypes": [ "Group" ]
1751 },
1752 "Group": {
1753 "memberOfTypes": []
1754 },
1755 "Photo": {
1756 "memberOfTypes": [ "Album" ]
1757 },
1758 "Album": {
1759 "memberOfTypes": []
1760 }
1761 },
1762 "actions": {
1763 "view_photo": {
1764 "appliesTo": {
1765 "principalTypes": ["User", "Group"],
1766 "resourceTypes": ["Photo"]
1767 }
1768 }
1769 }
1770 });
1771 let schema_file: json_schema::NamespaceDefinition<RawName> =
1772 serde_json::from_value(src).unwrap();
1773 let schema: Result<ValidatorSchema> = schema_file.try_into();
1774 assert!(schema.is_ok());
1775 }
1776
1777 #[test]
1779 fn test_from_schema_file_duplicate_entity() {
1780 let src = r#"
1783 {"": {
1784 "entityTypes": {
1785 "User": {
1786 "memberOfTypes": [ "Group" ]
1787 },
1788 "Group": {
1789 "memberOfTypes": []
1790 },
1791 "Photo": {
1792 "memberOfTypes": [ "Album" ]
1793 },
1794 "Photo": {
1795 "memberOfTypes": []
1796 }
1797 },
1798 "actions": {
1799 "view_photo": {
1800 "memberOf": [],
1801 "appliesTo": {
1802 "principalTypes": ["User", "Group"],
1803 "resourceTypes": ["Photo"]
1804 }
1805 }
1806 }
1807 }}"#;
1808
1809 match ValidatorSchema::from_json_str(src, Extensions::all_available()) {
1810 Err(SchemaError::JsonDeserialization(_)) => (),
1811 _ => panic!("Expected JSON deserialization error due to duplicate entity type."),
1812 }
1813 }
1814
1815 #[test]
1817 fn test_from_schema_file_duplicate_action() {
1818 let src = r#"
1821 {"": {
1822 "entityTypes": {
1823 "User": {
1824 "memberOfTypes": [ "Group" ]
1825 },
1826 "Group": {
1827 "memberOfTypes": []
1828 },
1829 "Photo": {
1830 "memberOfTypes": []
1831 }
1832 },
1833 "actions": {
1834 "view_photo": {
1835 "memberOf": [],
1836 "appliesTo": {
1837 "principalTypes": ["User", "Group"],
1838 "resourceTypes": ["Photo"]
1839 }
1840 },
1841 "view_photo": { }
1842 }
1843 }"#;
1844 match ValidatorSchema::from_json_str(src, Extensions::all_available()) {
1845 Err(SchemaError::JsonDeserialization(_)) => (),
1846 _ => panic!("Expected JSON deserialization error due to duplicate action type."),
1847 }
1848 }
1849
1850 #[test]
1851 fn test_from_schema_file_missing_parent_action() {
1852 let src = json!({
1853 "": {
1854 "entityTypes": {
1855 "Test": {}
1856 },
1857 "actions": {
1858 "doTests": {
1859 "memberOf": [
1860 { "type": "Action", "id": "test1" },
1861 { "type": "Action", "id": "test2" }
1862 ]
1863 }
1864 }
1865 }
1866 });
1867 match ValidatorSchema::from_json_value(src, Extensions::all_available()) {
1868 Err(SchemaError::ActionNotDefined(missing)) => {
1869 assert_eq!(missing.0.len(), 2);
1870 }
1871 _ => panic!("Expected ActionNotDefined due to unknown actions in memberOf."),
1872 }
1873 }
1874
1875 #[test]
1876 fn test_from_schema_file_undefined_types_in_common() {
1877 let src = json!({
1878 "": {
1879 "commonTypes": {
1880 "My1": {"type": "What"},
1881 "My2": {"type": "Ev"},
1882 "My3": {"type": "Er"}
1883 },
1884 "entityTypes": {
1885 "Test": {}
1886 },
1887 "actions": {},
1888 }
1889 });
1890 match ValidatorSchema::from_json_value(src, Extensions::all_available()) {
1891 Err(SchemaError::TypeNotDefined(missing)) => {
1892 assert_eq!(missing.undefined_types.len(), 3);
1893 }
1894 x => {
1895 panic!("Expected TypeNotDefined due to unknown types in commonTypes, found: {x:?}")
1896 }
1897 }
1898 }
1899
1900 #[test]
1901 fn test_from_schema_file_undefined_entities_in_one_action() {
1902 let src = json!({
1903 "": {
1904 "entityTypes": {
1905 "Test": {}
1906 },
1907 "actions": {
1908 "doTests": {
1909 "appliesTo": {
1910 "principalTypes": ["Usr", "Group"],
1911 "resourceTypes": ["Phoot"]
1912 }
1913 }
1914 }
1915 }
1916 });
1917 match ValidatorSchema::from_json_value(src, Extensions::all_available()) {
1918 Err(SchemaError::TypeNotDefined(missing)) => {
1919 assert_eq!(missing.undefined_types.len(), 3);
1920 }
1921 x => {
1922 panic!("Expected TypeNotDefined due to unknown entities in appliesTo, found: {x:?}")
1923 }
1924 }
1925 }
1926
1927 #[test]
1929 fn test_from_schema_file_undefined_entities() {
1930 let src = json!(
1931 {
1932 "entityTypes": {
1933 "User": {
1934 "memberOfTypes": [ "Grop" ]
1935 },
1936 "Group": {
1937 "memberOfTypes": []
1938 },
1939 "Photo": {
1940 "memberOfTypes": []
1941 }
1942 },
1943 "actions": {
1944 "view_photo": {
1945 "appliesTo": {
1946 "principalTypes": ["Usr", "Group"],
1947 "resourceTypes": ["Phoot"]
1948 }
1949 }
1950 }
1951 });
1952 let schema_file: json_schema::NamespaceDefinition<RawName> =
1953 serde_json::from_value(src.clone()).unwrap();
1954 let schema: Result<ValidatorSchema> = schema_file.try_into();
1955 assert_matches!(schema, Err(e) => {
1956 expect_err(
1957 &src,
1958 &miette::Report::new(e),
1959 &ExpectedErrorMessageBuilder::error(r#"failed to resolve types: Grop, Usr, Phoot"#)
1960 .help("`Grop` has not been declared as an entity type")
1961 .exactly_one_underline("Grop")
1962 .build());
1963 });
1964 }
1965
1966 #[test]
1967 fn undefined_entity_namespace_member_of() {
1968 let src = json!(
1969 {"Foo": {
1970 "entityTypes": {
1971 "User": {
1972 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1973 },
1974 "Group": { }
1975 },
1976 "actions": {}
1977 }});
1978 let schema_file = json_schema::Fragment::from_json_value(src.clone()).unwrap();
1979 let schema: Result<ValidatorSchema> = schema_file.try_into();
1980 assert_matches!(schema, Err(e) => {
1981 expect_err(
1982 &src,
1983 &miette::Report::new(e),
1984 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: Bar::Group"#)
1985 .help("`Bar::Group` has not been declared as an entity type")
1986 .exactly_one_underline("Bar::Group")
1987 .build());
1988 });
1989 }
1990
1991 #[test]
1992 fn undefined_entity_namespace_applies_to() {
1993 let src = json!(
1994 {"Foo": {
1995 "entityTypes": { "User": { }, "Photo": { } },
1996 "actions": {
1997 "view_photo": {
1998 "appliesTo": {
1999 "principalTypes": ["Foo::User", "Bar::User"],
2000 "resourceTypes": ["Photo", "Bar::Photo"],
2001 }
2002 }
2003 }
2004 }});
2005 let schema_file = json_schema::Fragment::from_json_value(src.clone()).unwrap();
2006 let schema: Result<ValidatorSchema> = schema_file.try_into();
2007 assert_matches!(schema, Err(e) => {
2008 expect_err(
2009 &src,
2010 &miette::Report::new(e),
2011 &ExpectedErrorMessageBuilder::error(r#"failed to resolve types: Bar::User, Bar::Photo"#)
2012 .help("`Bar::User` has not been declared as an entity type")
2013 .exactly_one_underline("Bar::User")
2014 .build());
2015 });
2016 }
2017
2018 #[test]
2020 fn test_from_schema_file_undefined_action() {
2021 let src = json!(
2022 {
2023 "entityTypes": {
2024 "User": {
2025 "memberOfTypes": [ "Group" ]
2026 },
2027 "Group": {
2028 "memberOfTypes": []
2029 },
2030 "Photo": {
2031 "memberOfTypes": []
2032 }
2033 },
2034 "actions": {
2035 "view_photo": {
2036 "memberOf": [ {"id": "photo_action"} ],
2037 "appliesTo": {
2038 "principalTypes": ["User", "Group"],
2039 "resourceTypes": ["Photo"]
2040 }
2041 }
2042 }
2043 });
2044 let schema_file: json_schema::NamespaceDefinition<RawName> =
2045 serde_json::from_value(src.clone()).unwrap();
2046 let schema: Result<ValidatorSchema> = schema_file.try_into();
2047 assert_matches!(schema, Err(e) => {
2048 expect_err(
2049 &src,
2050 &miette::Report::new(e),
2051 &ExpectedErrorMessageBuilder::error(r#"undeclared action: Action::"photo_action""#)
2052 .help("any actions appearing as parents need to be declared as actions")
2053 .build());
2054 });
2055 }
2056
2057 #[test]
2060 fn test_from_schema_file_action_cycle1() {
2061 let src = json!(
2062 {
2063 "entityTypes": {},
2064 "actions": {
2065 "view_photo": {
2066 "memberOf": [ {"id": "view_photo"} ]
2067 }
2068 }
2069 });
2070 let schema_file: json_schema::NamespaceDefinition<RawName> =
2071 serde_json::from_value(src).unwrap();
2072 let schema: Result<ValidatorSchema> = schema_file.try_into();
2073 assert_matches!(
2074 schema,
2075 Err(SchemaError::CycleInActionHierarchy(CycleInActionHierarchyError { uid: euid })) => {
2076 assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
2077 }
2078 )
2079 }
2080
2081 #[test]
2084 fn test_from_schema_file_action_cycle2() {
2085 let src = json!(
2086 {
2087 "entityTypes": {},
2088 "actions": {
2089 "view_photo": {
2090 "memberOf": [ {"id": "edit_photo"} ]
2091 },
2092 "edit_photo": {
2093 "memberOf": [ {"id": "delete_photo"} ]
2094 },
2095 "delete_photo": {
2096 "memberOf": [ {"id": "view_photo"} ]
2097 },
2098 "other_action": {
2099 "memberOf": [ {"id": "edit_photo"} ]
2100 }
2101 }
2102 });
2103 let schema_file: json_schema::NamespaceDefinition<RawName> =
2104 serde_json::from_value(src).unwrap();
2105 let schema: Result<ValidatorSchema> = schema_file.try_into();
2106 assert_matches!(
2107 schema,
2108 Err(SchemaError::CycleInActionHierarchy(_)),
2110 )
2111 }
2112
2113 #[test]
2114 fn namespaced_schema() {
2115 let src = r#"
2116 { "N::S": {
2117 "entityTypes": {
2118 "User": {},
2119 "Photo": {}
2120 },
2121 "actions": {
2122 "view_photo": {
2123 "appliesTo": {
2124 "principalTypes": ["User"],
2125 "resourceTypes": ["Photo"]
2126 }
2127 }
2128 }
2129 } }
2130 "#;
2131 let schema_file = json_schema::Fragment::from_json_str(src).unwrap();
2132 let schema: ValidatorSchema = schema_file
2133 .try_into()
2134 .expect("Namespaced schema failed to convert.");
2135 dbg!(&schema);
2136 let user_entity_type = &"N::S::User"
2137 .parse()
2138 .expect("Namespaced entity type should have parsed");
2139 let photo_entity_type = &"N::S::Photo"
2140 .parse()
2141 .expect("Namespaced entity type should have parsed");
2142 assert!(
2143 schema.entity_types.contains_key(user_entity_type),
2144 "Expected and entity type User."
2145 );
2146 assert!(
2147 schema.entity_types.contains_key(photo_entity_type),
2148 "Expected an entity type Photo."
2149 );
2150 assert_eq!(
2151 schema.entity_types.len(),
2152 2,
2153 "Expected exactly 2 entity types."
2154 );
2155 assert!(
2156 schema.action_ids.contains_key(
2157 &"N::S::Action::\"view_photo\""
2158 .parse()
2159 .expect("Namespaced action should have parsed")
2160 ),
2161 "Expected an action \"view_photo\"."
2162 );
2163 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
2164
2165 let action = &schema.action_ids.values().next().expect("Expected Action");
2166 assert_eq!(
2167 action.applies_to_principals().collect::<Vec<_>>(),
2168 vec![user_entity_type]
2169 );
2170 assert_eq!(
2171 action.applies_to_resources().collect::<Vec<_>>(),
2172 vec![photo_entity_type]
2173 );
2174 }
2175
2176 #[test]
2177 fn cant_use_namespace_in_entity_type() {
2178 let src = r#"
2179 {
2180 "entityTypes": { "NS::User": {} },
2181 "actions": {}
2182 }
2183 "#;
2184 assert_matches!(
2185 serde_json::from_str::<json_schema::NamespaceDefinition<RawName>>(src),
2186 Err(_)
2187 );
2188 }
2189
2190 #[test]
2191 fn entity_attribute_entity_type_with_namespace() {
2192 let src = json!(
2193 {"A::B": {
2194 "entityTypes": {
2195 "Foo": {
2196 "shape": {
2197 "type": "Record",
2198 "attributes": {
2199 "name": { "type": "Entity", "name": "C::D::Foo" }
2200 }
2201 }
2202 }
2203 },
2204 "actions": {}
2205 }});
2206 let schema_json = json_schema::Fragment::from_json_value(src.clone()).unwrap();
2207 let schema: Result<ValidatorSchema> = schema_json.try_into();
2208 assert_matches!(schema, Err(e) => {
2209 expect_err(
2210 &src,
2211 &miette::Report::new(e),
2212 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: C::D::Foo"#)
2213 .help("`C::D::Foo` has not been declared as an entity type")
2214 .exactly_one_underline("C::D::Foo")
2215 .build());
2216 });
2217 }
2218
2219 #[test]
2220 fn entity_attribute_entity_type_with_declared_namespace() {
2221 let schema_json = json_schema::Fragment::from_json_str(
2222 r#"
2223 {"A::B": {
2224 "entityTypes": {
2225 "Foo": {
2226 "shape": {
2227 "type": "Record",
2228 "attributes": {
2229 "name": { "type": "Entity", "name": "A::B::Foo" }
2230 }
2231 }
2232 }
2233 },
2234 "actions": {}
2235 }}
2236 "#,
2237 )
2238 .unwrap();
2239
2240 let schema: ValidatorSchema = schema_json
2241 .try_into()
2242 .expect("Expected schema to construct without error.");
2243
2244 let foo_name: EntityType = "A::B::Foo".parse().expect("Expected entity type name");
2245 let foo_type = schema
2246 .entity_types
2247 .get(&foo_name)
2248 .expect("Expected to find entity");
2249 let name_type = foo_type
2250 .attr("name")
2251 .expect("Expected attribute name")
2252 .attr_type
2253 .clone();
2254 assert_eq!(name_type, Type::named_entity_reference(foo_name));
2255 }
2256
2257 #[test]
2258 fn cannot_declare_action_type_when_prohibited() {
2259 let schema_json: json_schema::NamespaceDefinition<RawName> = serde_json::from_str(
2260 r#"
2261 {
2262 "entityTypes": { "Action": {} },
2263 "actions": {}
2264 }
2265 "#,
2266 )
2267 .unwrap();
2268 let schema: Result<ValidatorSchema> = schema_json.try_into();
2269 assert!(matches!(
2270 schema,
2271 Err(SchemaError::ActionEntityTypeDeclared(_))
2272 ));
2273 }
2274
2275 #[test]
2276 fn can_declare_other_type_when_action_type_prohibited() {
2277 let schema_json: json_schema::NamespaceDefinition<RawName> = serde_json::from_str(
2278 r#"
2279 {
2280 "entityTypes": { "Foo": { } },
2281 "actions": {}
2282 }
2283 "#,
2284 )
2285 .unwrap();
2286
2287 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
2288 }
2289
2290 #[test]
2291 fn cannot_declare_action_in_group_when_prohibited() {
2292 let schema_json = json_schema::Fragment::from_json_str(
2293 r#"
2294 {"": {
2295 "entityTypes": {},
2296 "actions": {
2297 "universe": { },
2298 "view_photo": {
2299 "attributes": {"id": "universe"}
2300 },
2301 "edit_photo": {
2302 "attributes": {"id": "universe"}
2303 },
2304 "delete_photo": {
2305 "attributes": {"id": "universe"}
2306 }
2307 }
2308 }}
2309 "#,
2310 )
2311 .unwrap();
2312
2313 let schema = ValidatorSchemaFragment::from_schema_fragment(
2314 schema_json,
2315 ActionBehavior::ProhibitAttributes,
2316 Extensions::all_available(),
2317 );
2318 match schema {
2319 Err(e) => {
2320 expect_err(
2321 "",
2322 &miette::Report::new(e),
2323 &ExpectedErrorMessageBuilder::error("unsupported feature used in schema")
2324 .source(r#"action declared with attributes: [delete_photo, edit_photo, view_photo]"#)
2325 .build()
2326 )
2327 }
2328 _ => panic!("Did not see expected error."),
2329 }
2330 }
2331
2332 #[test]
2333 fn test_entity_type_no_namespace() {
2334 let src = json!({"type": "Entity", "name": "Foo"});
2335 let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
2336 assert_eq!(
2337 schema_ty,
2338 json_schema::Type::Type {
2339 ty: json_schema::TypeVariant::Entity {
2340 name: "Foo".parse().unwrap()
2341 },
2342 loc: None
2343 },
2344 );
2345 let schema_ty = schema_ty.conditionally_qualify_type_references(Some(
2346 &InternalName::parse_unqualified_name("NS").unwrap(),
2347 ));
2348 let all_defs = AllDefs::from_entity_defs([
2349 InternalName::from_str("NS::Foo").unwrap(),
2350 InternalName::from_str("Bar").unwrap(),
2351 ]);
2352 let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
2353 let ty: LocatedType =
2354 try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available(), None)
2355 .expect("Error converting schema type to type.")
2356 .resolve_common_type_refs(&HashMap::new())
2357 .unwrap();
2358 assert_eq!(ty.ty, Type::named_entity_reference_from_str("NS::Foo"));
2359 }
2360
2361 #[test]
2362 fn test_entity_type_namespace() {
2363 let src = json!({"type": "Entity", "name": "NS::Foo"});
2364 let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
2365 assert_eq!(
2366 schema_ty,
2367 json_schema::Type::Type {
2368 ty: json_schema::TypeVariant::Entity {
2369 name: "NS::Foo".parse().unwrap()
2370 },
2371 loc: None
2372 },
2373 );
2374 let schema_ty = schema_ty.conditionally_qualify_type_references(Some(
2375 &InternalName::parse_unqualified_name("NS").unwrap(),
2376 ));
2377 let all_defs = AllDefs::from_entity_defs([
2378 InternalName::from_str("NS::Foo").unwrap(),
2379 InternalName::from_str("Foo").unwrap(),
2380 ]);
2381 let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
2382 let ty: LocatedType =
2383 try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available(), None)
2384 .expect("Error converting schema type to type.")
2385 .resolve_common_type_refs(&HashMap::new())
2386 .unwrap();
2387 assert_eq!(ty.ty, Type::named_entity_reference_from_str("NS::Foo"));
2388 }
2389
2390 #[test]
2391 fn test_entity_type_namespace_parse_error() {
2392 let src = json!({"type": "Entity", "name": "::Foo"});
2393 assert_matches!(
2394 serde_json::from_value::<json_schema::Type<RawName>>(src),
2395 Err(_)
2396 );
2397 }
2398
2399 #[test]
2400 fn schema_type_record_is_validator_type_record() {
2401 let src = json!({"type": "Record", "attributes": {}});
2402 let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
2403 assert_eq!(
2404 schema_ty,
2405 json_schema::Type::Type {
2406 ty: json_schema::TypeVariant::Record(json_schema::RecordType {
2407 attributes: BTreeMap::new(),
2408 additional_attributes: false,
2409 }),
2410 loc: None
2411 },
2412 );
2413 let schema_ty = schema_ty.conditionally_qualify_type_references(None);
2414 let all_defs = AllDefs::from_entity_defs([InternalName::from_str("Foo").unwrap()]);
2415 let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
2416 let ty: LocatedType =
2417 try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available(), None)
2418 .expect("Error converting schema type to type.")
2419 .resolve_common_type_refs(&HashMap::new())
2420 .unwrap();
2421 assert_eq!(ty.ty, Type::closed_record_with_attributes(None));
2422 }
2423
2424 #[test]
2425 fn get_namespaces() {
2426 let fragment = json_schema::Fragment::from_json_value(json!({
2427 "Foo::Bar::Baz": {
2428 "entityTypes": {},
2429 "actions": {}
2430 },
2431 "Foo": {
2432 "entityTypes": {},
2433 "actions": {}
2434 },
2435 "Bar": {
2436 "entityTypes": {},
2437 "actions": {}
2438 },
2439 }))
2440 .unwrap();
2441
2442 let schema_fragment: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2443 fragment.try_into().unwrap();
2444 assert_eq!(
2445 schema_fragment
2446 .0
2447 .iter()
2448 .map(|f| f.namespace())
2449 .collect::<HashSet<_>>(),
2450 HashSet::from([
2451 Some(&"Foo::Bar::Baz".parse().unwrap()),
2452 Some(&"Foo".parse().unwrap()),
2453 Some(&"Bar".parse().unwrap())
2454 ])
2455 );
2456 }
2457
2458 #[test]
2459 fn schema_no_fragments() {
2460 let schema =
2461 ValidatorSchema::from_schema_fragments([], Extensions::all_available()).unwrap();
2462 assert!(schema.entity_types.is_empty());
2463 assert!(schema.action_ids.is_empty());
2464 }
2465
2466 #[test]
2467 fn same_action_different_namespace() {
2468 let fragment = json_schema::Fragment::from_json_value(json!({
2469 "Foo::Bar": {
2470 "entityTypes": {},
2471 "actions": {
2472 "Baz": {}
2473 }
2474 },
2475 "Bar::Foo": {
2476 "entityTypes": {},
2477 "actions": {
2478 "Baz": { }
2479 }
2480 },
2481 "Biz": {
2482 "entityTypes": {},
2483 "actions": {
2484 "Baz": { }
2485 }
2486 }
2487 }))
2488 .unwrap();
2489
2490 let schema: ValidatorSchema = fragment.try_into().unwrap();
2491 assert!(schema
2492 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2493 .is_some());
2494 assert!(schema
2495 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
2496 .is_some());
2497 assert!(schema
2498 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2499 .is_some());
2500 }
2501
2502 #[test]
2503 fn same_type_different_namespace() {
2504 let fragment = json_schema::Fragment::from_json_value(json!({
2505 "Foo::Bar": {
2506 "entityTypes": {"Baz" : {}},
2507 "actions": { }
2508 },
2509 "Bar::Foo": {
2510 "entityTypes": {"Baz" : {}},
2511 "actions": { }
2512 },
2513 "Biz": {
2514 "entityTypes": {"Baz" : {}},
2515 "actions": { }
2516 }
2517 }))
2518 .unwrap();
2519 let schema: ValidatorSchema = fragment.try_into().unwrap();
2520
2521 assert_entity_type_exists(&schema, "Foo::Bar::Baz");
2522 assert_entity_type_exists(&schema, "Bar::Foo::Baz");
2523 assert_entity_type_exists(&schema, "Biz::Baz");
2524 }
2525
2526 #[test]
2527 fn member_of_different_namespace() {
2528 let fragment = json_schema::Fragment::from_json_value(json!({
2529 "Bar": {
2530 "entityTypes": {
2531 "Baz": {
2532 "memberOfTypes": ["Foo::Buz"]
2533 }
2534 },
2535 "actions": {}
2536 },
2537 "Foo": {
2538 "entityTypes": { "Buz": {} },
2539 "actions": { }
2540 }
2541 }))
2542 .unwrap();
2543 let schema: ValidatorSchema = fragment.try_into().unwrap();
2544
2545 let buz = assert_entity_type_exists(&schema, "Foo::Buz");
2546 assert_eq!(
2547 buz.descendants,
2548 HashSet::from(["Bar::Baz".parse().unwrap()])
2549 );
2550 }
2551
2552 #[test]
2553 fn attribute_different_namespace() {
2554 let fragment = json_schema::Fragment::from_json_value(json!({
2555 "Bar": {
2556 "entityTypes": {
2557 "Baz": {
2558 "shape": {
2559 "type": "Record",
2560 "attributes": {
2561 "fiz": {
2562 "type": "Entity",
2563 "name": "Foo::Buz"
2564 }
2565 }
2566 }
2567 }
2568 },
2569 "actions": {}
2570 },
2571 "Foo": {
2572 "entityTypes": { "Buz": {} },
2573 "actions": { }
2574 }
2575 }))
2576 .unwrap();
2577
2578 let schema: ValidatorSchema = fragment.try_into().unwrap();
2579 let baz = assert_entity_type_exists(&schema, "Bar::Baz");
2580 assert_eq!(
2581 baz.attr("fiz").unwrap().attr_type,
2582 Type::named_entity_reference_from_str("Foo::Buz"),
2583 );
2584 }
2585
2586 #[test]
2587 fn applies_to_different_namespace() {
2588 let fragment = json_schema::Fragment::from_json_value(json!({
2589 "Foo::Bar": {
2590 "entityTypes": { },
2591 "actions": {
2592 "Baz": {
2593 "appliesTo": {
2594 "principalTypes": [ "Fiz::Buz" ],
2595 "resourceTypes": [ "Fiz::Baz" ],
2596 }
2597 }
2598 }
2599 },
2600 "Fiz": {
2601 "entityTypes": {
2602 "Buz": {},
2603 "Baz": {}
2604 },
2605 "actions": { }
2606 }
2607 }))
2608 .unwrap();
2609 let schema: ValidatorSchema = fragment.try_into().unwrap();
2610
2611 let baz = schema
2612 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2613 .unwrap();
2614 assert_eq!(
2615 baz.applies_to
2616 .applicable_principal_types()
2617 .collect::<HashSet<_>>(),
2618 HashSet::from([&("Fiz::Buz".parse().unwrap())])
2619 );
2620 assert_eq!(
2621 baz.applies_to
2622 .applicable_resource_types()
2623 .collect::<HashSet<_>>(),
2624 HashSet::from([&("Fiz::Baz".parse().unwrap())])
2625 );
2626 }
2627
2628 #[test]
2629 fn simple_defined_type() {
2630 let fragment = json_schema::Fragment::from_json_value(json!({
2631 "": {
2632 "commonTypes": {
2633 "MyLong": {"type": "Long"}
2634 },
2635 "entityTypes": {
2636 "User": {
2637 "shape": {
2638 "type": "Record",
2639 "attributes": {
2640 "a": {"type": "MyLong"}
2641 }
2642 }
2643 }
2644 },
2645 "actions": {}
2646 }
2647 }))
2648 .unwrap();
2649 let schema: ValidatorSchema = fragment.try_into().unwrap();
2650 assert_eq!(
2651 schema.entity_types.iter().next().unwrap().1.attributes(),
2652 &Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2653 );
2654 }
2655
2656 #[test]
2657 fn defined_record_as_attrs() {
2658 let fragment = json_schema::Fragment::from_json_value(json!({
2659 "": {
2660 "commonTypes": {
2661 "MyRecord": {
2662 "type": "Record",
2663 "attributes": {
2664 "a": {"type": "Long"}
2665 }
2666 }
2667 },
2668 "entityTypes": {
2669 "User": { "shape": { "type": "MyRecord", } }
2670 },
2671 "actions": {}
2672 }
2673 }))
2674 .unwrap();
2675 let schema: ValidatorSchema = fragment.try_into().unwrap();
2676 assert_eq!(
2677 schema.entity_types.iter().next().unwrap().1.attributes(),
2678 &Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2679 );
2680 }
2681
2682 #[test]
2683 fn cross_namespace_type() {
2684 let fragment = json_schema::Fragment::from_json_value(json!({
2685 "A": {
2686 "commonTypes": {
2687 "MyLong": {"type": "Long"}
2688 },
2689 "entityTypes": { },
2690 "actions": {}
2691 },
2692 "B": {
2693 "entityTypes": {
2694 "User": {
2695 "shape": {
2696 "type": "Record",
2697 "attributes": {
2698 "a": {"type": "A::MyLong"}
2699 }
2700 }
2701 }
2702 },
2703 "actions": {}
2704 }
2705 }))
2706 .unwrap();
2707 let schema: ValidatorSchema = fragment.try_into().unwrap();
2708 assert_eq!(
2709 schema.entity_types.iter().next().unwrap().1.attributes(),
2710 &Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2711 );
2712 }
2713
2714 #[test]
2715 fn cross_fragment_type() {
2716 let fragment1: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2717 json_schema::Fragment::from_json_value(json!({
2718 "A": {
2719 "commonTypes": {
2720 "MyLong": {"type": "Long"}
2721 },
2722 "entityTypes": { },
2723 "actions": {}
2724 }
2725 }))
2726 .unwrap()
2727 .try_into()
2728 .unwrap();
2729 let fragment2: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2730 json_schema::Fragment::from_json_value(json!({
2731 "A": {
2732 "entityTypes": {
2733 "User": {
2734 "shape": {
2735 "type": "Record",
2736 "attributes": {
2737 "a": {"type": "MyLong"}
2738 }
2739 }
2740 }
2741 },
2742 "actions": {}
2743 }
2744 }))
2745 .unwrap()
2746 .try_into()
2747 .unwrap();
2748 let schema = ValidatorSchema::from_schema_fragments(
2749 [fragment1, fragment2],
2750 Extensions::all_available(),
2751 )
2752 .unwrap();
2753
2754 assert_eq!(
2755 schema.entity_types.iter().next().unwrap().1.attributes(),
2756 &Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2757 );
2758 }
2759
2760 #[test]
2761 fn cross_fragment_duplicate_type() {
2762 let fragment1: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2763 json_schema::Fragment::from_json_value(json!({
2764 "A": {
2765 "commonTypes": {
2766 "MyLong": {"type": "Long"}
2767 },
2768 "entityTypes": {},
2769 "actions": {}
2770 }
2771 }))
2772 .unwrap()
2773 .try_into()
2774 .unwrap();
2775 let fragment2: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2776 json_schema::Fragment::from_json_value(json!({
2777 "A": {
2778 "commonTypes": {
2779 "MyLong": {"type": "Long"}
2780 },
2781 "entityTypes": {},
2782 "actions": {}
2783 }
2784 }))
2785 .unwrap()
2786 .try_into()
2787 .unwrap();
2788
2789 let schema = ValidatorSchema::from_schema_fragments(
2790 [fragment1, fragment2],
2791 Extensions::all_available(),
2792 );
2793
2794 assert_matches!(schema, Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError { ty })) => {
2796 assert_eq!(ty, "A::MyLong".parse().unwrap());
2797 });
2798 }
2799
2800 #[test]
2801 fn undeclared_type_in_attr() {
2802 let fragment = json_schema::Fragment::from_json_value(json!({
2803 "": {
2804 "commonTypes": { },
2805 "entityTypes": {
2806 "User": {
2807 "shape": {
2808 "type": "Record",
2809 "attributes": {
2810 "a": {"type": "MyLong"}
2811 }
2812 }
2813 }
2814 },
2815 "actions": {}
2816 }
2817 }))
2818 .unwrap();
2819 assert_matches!(
2820 TryInto::<ValidatorSchema>::try_into(fragment),
2821 Err(SchemaError::TypeNotDefined(_))
2822 );
2823 }
2824
2825 #[test]
2826 fn undeclared_type_in_common_types() {
2827 let fragment = json_schema::Fragment::from_json_value(json!({
2828 "": {
2829 "commonTypes": {
2830 "a": { "type": "b" }
2831 },
2832 "entityTypes": { },
2833 "actions": {}
2834 }
2835 }))
2836 .unwrap();
2837 assert_matches!(
2838 TryInto::<ValidatorSchema>::try_into(fragment),
2839 Err(SchemaError::TypeNotDefined(_))
2840 );
2841 }
2842
2843 #[test]
2844 fn shape_not_record() {
2845 let fragment = json_schema::Fragment::from_json_value(json!({
2846 "": {
2847 "commonTypes": {
2848 "MyLong": { "type": "Long" }
2849 },
2850 "entityTypes": {
2851 "User": {
2852 "shape": { "type": "MyLong" }
2853 }
2854 },
2855 "actions": {}
2856 }
2857 }))
2858 .unwrap();
2859 assert_matches!(
2860 TryInto::<ValidatorSchema>::try_into(fragment),
2861 Err(SchemaError::ContextOrShapeNotRecord(_))
2862 );
2863 }
2864
2865 #[test]
2869 fn counterexamples_from_cedar_134() {
2870 let bad1 = json!({
2872 "": {
2873 "entityTypes": {
2874 "User // comment": {
2875 "memberOfTypes": [
2876 "UserGroup"
2877 ]
2878 },
2879 "User": {
2880 "memberOfTypes": [
2881 "UserGroup"
2882 ]
2883 },
2884 "UserGroup": {}
2885 },
2886 "actions": {}
2887 }
2888 });
2889 assert_matches!(json_schema::Fragment::from_json_value(bad1), Err(_));
2890
2891 let bad2 = json!({
2893 "ABC :: //comment \n XYZ ": {
2894 "entityTypes": {
2895 "User": {
2896 "memberOfTypes": []
2897 }
2898 },
2899 "actions": {}
2900 }
2901 });
2902 assert_matches!(json_schema::Fragment::from_json_value(bad2), Err(_));
2903 }
2904
2905 #[test]
2906 fn simple_action_entity() {
2907 let src = json!(
2908 {
2909 "entityTypes": { },
2910 "actions": {
2911 "view_photo": { },
2912 }
2913 });
2914
2915 let schema_file: json_schema::NamespaceDefinition<RawName> =
2916 serde_json::from_value(src).unwrap();
2917 let schema: ValidatorSchema = schema_file.try_into().unwrap();
2918 let actions = schema.action_entities().expect("Entity Construct Error");
2919
2920 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2921 let view_photo = actions.entity(&action_uid);
2922 assert_eq!(
2923 view_photo.unwrap(),
2924 &Entity::new_with_attr_partial_value(
2925 action_uid,
2926 [],
2927 HashSet::new(),
2928 HashSet::new(),
2929 []
2930 )
2931 );
2932 }
2933
2934 #[test]
2935 fn action_entity_hierarchy() {
2936 let src = json!(
2937 {
2938 "entityTypes": { },
2939 "actions": {
2940 "read": {},
2941 "view": {
2942 "memberOf": [{"id": "read"}]
2943 },
2944 "view_photo": {
2945 "memberOf": [{"id": "view"}]
2946 },
2947 }
2948 });
2949
2950 let schema_file: json_schema::NamespaceDefinition<RawName> =
2951 serde_json::from_value(src).unwrap();
2952 let schema: ValidatorSchema = schema_file.try_into().unwrap();
2953 let actions = schema.action_entities().expect("Entity Construct Error");
2954
2955 let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2956 let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2957 let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2958
2959 let view_photo_entity = actions.entity(&view_photo_uid);
2960 assert_eq!(
2961 view_photo_entity.unwrap(),
2962 &Entity::new_with_attr_partial_value(
2963 view_photo_uid,
2964 [],
2965 HashSet::new(),
2966 HashSet::from([view_uid, read_uid.clone()]),
2967 [],
2968 )
2969 );
2970
2971 let read_entity = actions.entity(&read_uid);
2972 assert_eq!(
2973 read_entity.unwrap(),
2974 &Entity::new_with_attr_partial_value(read_uid, [], HashSet::new(), HashSet::new(), [])
2975 );
2976 }
2977
2978 #[test]
2979 fn action_entity_attribute() {
2980 let src = json!(
2981 {
2982 "entityTypes": { },
2983 "actions": {
2984 "view_photo": {
2985 "attributes": { "attr": "foo" }
2986 },
2987 }
2988 });
2989
2990 let schema_file: NamespaceDefinitionWithActionAttributes<RawName> =
2991 serde_json::from_value(src).unwrap();
2992 let schema: ValidatorSchema = schema_file.try_into().unwrap();
2993 let actions = schema.action_entities().expect("Entity Construct Error");
2994
2995 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2996 let view_photo = actions.entity(&action_uid);
2997 assert_eq!(
2998 view_photo.unwrap(),
2999 &Entity::new(
3000 action_uid,
3001 [("attr".into(), RestrictedExpr::val("foo"))],
3002 HashSet::new(),
3003 HashSet::new(),
3004 [],
3005 Extensions::none(),
3006 )
3007 .unwrap(),
3008 );
3009 }
3010
3011 #[test]
3012 fn test_action_namespace_inference_multi_success() {
3013 let src = json!({
3014 "Foo" : {
3015 "entityTypes" : {},
3016 "actions" : {
3017 "read" : {}
3018 }
3019 },
3020 "ExampleCo::Personnel" : {
3021 "entityTypes" : {},
3022 "actions" : {
3023 "viewPhoto" : {
3024 "memberOf" : [
3025 {
3026 "id" : "read",
3027 "type" : "Foo::Action"
3028 }
3029 ]
3030 }
3031 }
3032 },
3033 });
3034 let schema_fragment =
3035 json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
3036 let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
3037 let view_photo = ValidatorSchema::action_entities_iter(&schema.action_ids)
3038 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
3039 .unwrap();
3040 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
3041 let read = ancestors[0];
3042 let read_eid: &str = read.eid().as_ref();
3043 assert_eq!(read_eid, "read");
3044 assert_eq!(read.entity_type().to_string(), "Foo::Action");
3045 }
3046
3047 #[test]
3048 fn test_action_namespace_inference_multi() {
3049 let src = json!({
3050 "ExampleCo::Personnel::Foo" : {
3051 "entityTypes" : {},
3052 "actions" : {
3053 "read" : {}
3054 }
3055 },
3056 "ExampleCo::Personnel" : {
3057 "entityTypes" : {},
3058 "actions" : {
3059 "viewPhoto" : {
3060 "memberOf" : [
3061 {
3062 "id" : "read",
3063 "type" : "Foo::Action"
3064 }
3065 ]
3066 }
3067 }
3068 },
3069 });
3070 let schema_fragment =
3071 json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
3072 let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
3073 schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
3074 }
3075
3076 #[test]
3077 fn test_action_namespace_inference() {
3078 let src = json!({
3079 "ExampleCo::Personnel" : {
3080 "entityTypes" : { },
3081 "actions" : {
3082 "read" : {},
3083 "viewPhoto" : {
3084 "memberOf" : [
3085 {
3086 "id" : "read",
3087 "type" : "Action"
3088 }
3089 ]
3090 }
3091 }
3092 }
3093 });
3094 let schema_fragment =
3095 json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
3096 let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
3097 let view_photo = ValidatorSchema::action_entities_iter(&schema.action_ids)
3098 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
3099 .unwrap();
3100 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
3101 let read = ancestors[0];
3102 let read_eid: &str = read.eid().as_ref();
3103 assert_eq!(read_eid, "read");
3104 assert_eq!(
3105 read.entity_type().to_string(),
3106 "ExampleCo::Personnel::Action"
3107 );
3108 }
3109
3110 #[test]
3111 fn fallback_to_empty_namespace() {
3112 let src = json!(
3113 {
3114 "Demo": {
3115 "entityTypes": {
3116 "User": {
3117 "memberOfTypes": [],
3118 "shape": {
3119 "type": "Record",
3120 "attributes": {
3121 "id": { "type": "id" },
3122 }
3123 }
3124 }
3125 },
3126 "actions": {}
3127 },
3128 "": {
3129 "commonTypes": {
3130 "id": {
3131 "type": "String"
3132 },
3133 },
3134 "entityTypes": {},
3135 "actions": {}
3136 }
3137 }
3138 );
3139 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available()).unwrap();
3140 let mut attributes = assert_entity_type_exists(&schema, "Demo::User")
3141 .attributes()
3142 .iter();
3143 let (attr_name, attr_ty) = attributes.next().unwrap();
3144 assert_eq!(attr_name, "id");
3145 assert_eq!(&attr_ty.attr_type, &Type::primitive_string());
3146 assert_matches!(attributes.next(), None);
3147 }
3148
3149 #[test]
3150 fn qualified_undeclared_common_types2() {
3151 let src = json!(
3152 {
3153 "Demo": {
3154 "entityTypes": {
3155 "User": {
3156 "memberOfTypes": [],
3157 "shape": {
3158 "type": "Record",
3159 "attributes": {
3160 "id": { "type": "Demo::id" },
3161 }
3162 }
3163 }
3164 },
3165 "actions": {}
3166 },
3167 "": {
3168 "commonTypes": {
3169 "id": {
3170 "type": "String"
3171 },
3172 },
3173 "entityTypes": {},
3174 "actions": {}
3175 }
3176 }
3177 );
3178 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3179 assert_matches!(schema, Err(e) => {
3180 expect_err(
3181 &src,
3182 &miette::Report::new(e),
3183 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: Demo::id"#)
3184 .help("`Demo::id` has not been declared as a common type")
3185 .exactly_one_underline("Demo::id")
3186 .build());
3187 });
3188 }
3189
3190 #[test]
3191 fn undeclared_entity_type_in_common_type() {
3192 let src = json!(
3193 {
3194 "": {
3195 "commonTypes": {
3196 "id": {
3197 "type": "Entity",
3198 "name": "undeclared"
3199 },
3200 },
3201 "entityTypes": {},
3202 "actions": {}
3203 }
3204 }
3205 );
3206 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3207 assert_matches!(schema, Err(e) => {
3208 expect_err(
3209 &src,
3210 &miette::Report::new(e),
3211 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
3212 .help("`undeclared` has not been declared as an entity type")
3213 .exactly_one_underline("undeclared")
3214 .build());
3215 });
3216 }
3217
3218 #[test]
3219 fn undeclared_entity_type_in_common_type_record() {
3220 let src = json!(
3221 {
3222 "": {
3223 "commonTypes": {
3224 "id": {
3225 "type": "Record",
3226 "attributes": {
3227 "first": {
3228 "type": "Entity",
3229 "name": "undeclared"
3230 }
3231 }
3232 },
3233 },
3234 "entityTypes": {},
3235 "actions": {}
3236 }
3237 }
3238 );
3239 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3240 assert_matches!(schema, Err(e) => {
3241 expect_err(
3242 &src,
3243 &miette::Report::new(e),
3244 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
3245 .help("`undeclared` has not been declared as an entity type")
3246 .exactly_one_underline("undeclared")
3247 .build());
3248 });
3249 }
3250
3251 #[test]
3252 fn undeclared_entity_type_in_common_type_set() {
3253 let src = json!(
3254 {
3255 "": {
3256 "commonTypes": {
3257 "id": {
3258 "type": "Set",
3259 "element": {
3260 "type": "Entity",
3261 "name": "undeclared"
3262 }
3263 },
3264 },
3265 "entityTypes": {},
3266 "actions": {}
3267 }
3268 }
3269 );
3270 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3271 assert_matches!(schema, Err(e) => {
3272 expect_err(
3273 &src,
3274 &miette::Report::new(e),
3275 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
3276 .help("`undeclared` has not been declared as an entity type")
3277 .exactly_one_underline("undeclared")
3278 .build());
3279 });
3280 }
3281
3282 #[test]
3283 fn unknown_extension_type() {
3284 let src: serde_json::Value = json!({
3285 "": {
3286 "commonTypes": { },
3287 "entityTypes": {
3288 "User": {
3289 "shape": {
3290 "type": "Record",
3291 "attributes": {
3292 "a": {
3293 "type": "Extension",
3294 "name": "ip",
3295 }
3296 }
3297 }
3298 }
3299 },
3300 "actions": {}
3301 }
3302 });
3303 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3304 assert_matches!(schema, Err(e) => {
3305 expect_err(
3306 &src,
3307 &miette::Report::new(e),
3308 &ExpectedErrorMessageBuilder::error("unknown extension type `ip`")
3309 .help("did you mean `ipaddr`?")
3310 .build());
3311 });
3312
3313 let src: serde_json::Value = json!({
3314 "": {
3315 "commonTypes": { },
3316 "entityTypes": {
3317 "User": {},
3318 "Folder" :{}
3319 },
3320 "actions": {
3321 "A": {
3322 "appliesTo": {
3323 "principalTypes" : ["User"],
3324 "resourceTypes" : ["Folder"],
3325 "context": {
3326 "type": "Record",
3327 "attributes": {
3328 "a": {
3329 "type": "Extension",
3330 "name": "deciml",
3331 }
3332 }
3333 }
3334 }
3335 }
3336 }
3337 }
3338 });
3339 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3340 assert_matches!(schema, Err(e) => {
3341 expect_err(
3342 &src,
3343 &miette::Report::new(e),
3344 &ExpectedErrorMessageBuilder::error("unknown extension type `deciml`")
3345 .help("did you mean `decimal`?")
3346 .build());
3347 });
3348
3349 let src: serde_json::Value = json!({
3350 "": {
3351 "commonTypes": {
3352 "ty": {
3353 "type": "Record",
3354 "attributes": {
3355 "a": {
3356 "type": "Extension",
3357 "name": "i",
3358 }
3359 }
3360 }
3361 },
3362 "entityTypes": { },
3363 "actions": { },
3364 }
3365 });
3366 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3367 assert_matches!(schema, Err(e) => {
3368 expect_err(
3369 &src,
3370 &miette::Report::new(e),
3371 &ExpectedErrorMessageBuilder::error("unknown extension type `i`")
3372 .help("did you mean `ipaddr`?")
3373 .build());
3374 });
3375
3376 {
3377 let src: serde_json::Value = json!({
3378 "": {
3379 "commonTypes": {
3380 "ty": {
3381 "type": "Record",
3382 "attributes": {
3383 "a": {
3384 "type": "Extension",
3385 "name": "partial_evaluation",
3386 }
3387 }
3388 }
3389 },
3390 "entityTypes": { },
3391 "actions": { },
3392 }
3393 });
3394 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3395 assert_matches!(schema, Err(e) => {
3396 expect_err(
3397 &src,
3398 &miette::Report::new(e),
3399 &ExpectedErrorMessageBuilder::error("unknown extension type `partial_evaluation`")
3400 .help("did you mean `duration`?")
3401 .build());
3402 });
3403 }
3404 }
3405
3406 #[track_caller]
3407 fn assert_invalid_json_schema(src: serde_json::Value) {
3408 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
3409 assert_matches!(schema, Err(SchemaError::JsonDeserialization(e)) if e.to_smolstr().contains("this is reserved and cannot be the basename of a common-type declaration"));
3410 }
3411
3412 #[test]
3414 fn test_common_type_name_conflicts() {
3415 let src: serde_json::Value = json!({
3417 "": {
3418 "commonTypes": {
3419 "Record": {
3420 "type": "Record",
3421 "attributes": {
3422 "a": {
3423 "type": "Long",
3424 }
3425 }
3426 }
3427 },
3428 "entityTypes": {
3429 "b": {
3430 "shape" : {
3431 "type" : "Record",
3432 "attributes" : {
3433 "c" : {
3434 "type" : "Record"
3435 }
3436 }
3437 }
3438 }
3439 },
3440 "actions": { },
3441 }
3442 });
3443 assert_invalid_json_schema(src);
3444
3445 let src: serde_json::Value = json!({
3446 "NS": {
3447 "commonTypes": {
3448 "Record": {
3449 "type": "Record",
3450 "attributes": {
3451 "a": {
3452 "type": "Long",
3453 }
3454 }
3455 }
3456 },
3457 "entityTypes": {
3458 "b": {
3459 "shape" : {
3460 "type" : "Record",
3461 "attributes" : {
3462 "c" : {
3463 "type" : "Record"
3464 }
3465 }
3466 }
3467 }
3468 },
3469 "actions": { },
3470 }
3471 });
3472 assert_invalid_json_schema(src);
3473
3474 let src: serde_json::Value = json!({
3476 "": {
3477 "commonTypes": {
3478 "Extension": {
3479 "type": "Record",
3480 "attributes": {
3481 "a": {
3482 "type": "Long",
3483 }
3484 }
3485 }
3486 },
3487 "entityTypes": {
3488 "b": {
3489 "shape" : {
3490 "type" : "Record",
3491 "attributes" : {
3492 "c" : {
3493 "type" : "Extension"
3494 }
3495 }
3496 }
3497 }
3498 },
3499 "actions": { },
3500 }
3501 });
3502 assert_invalid_json_schema(src);
3503
3504 let src: serde_json::Value = json!({
3505 "NS": {
3506 "commonTypes": {
3507 "Extension": {
3508 "type": "Record",
3509 "attributes": {
3510 "a": {
3511 "type": "Long",
3512 }
3513 }
3514 }
3515 },
3516 "entityTypes": {
3517 "b": {
3518 "shape" : {
3519 "type" : "Record",
3520 "attributes" : {
3521 "c" : {
3522 "type" : "Extension"
3523 }
3524 }
3525 }
3526 }
3527 },
3528 "actions": { },
3529 }
3530 });
3531 assert_invalid_json_schema(src);
3532
3533 let src: serde_json::Value = json!({
3535 "": {
3536 "commonTypes": {
3537 "Entity": {
3538 "type": "Record",
3539 "attributes": {
3540 "a": {
3541 "type": "Long",
3542 }
3543 }
3544 }
3545 },
3546 "entityTypes": {
3547 "b": {
3548 "shape" : {
3549 "type" : "Record",
3550 "attributes" : {
3551 "c" : {
3552 "type" : "Entity"
3553 }
3554 }
3555 }
3556 }
3557 },
3558 "actions": { },
3559 }
3560 });
3561 assert_invalid_json_schema(src);
3562
3563 let src: serde_json::Value = json!({
3564 "NS": {
3565 "commonTypes": {
3566 "Entity": {
3567 "type": "Record",
3568 "attributes": {
3569 "a": {
3570 "type": "Long",
3571 }
3572 }
3573 }
3574 },
3575 "entityTypes": {
3576 "b": {
3577 "shape" : {
3578 "type" : "Record",
3579 "attributes" : {
3580 "c" : {
3581 "type" : "Entity"
3582 }
3583 }
3584 }
3585 }
3586 },
3587 "actions": { },
3588 }
3589 });
3590 assert_invalid_json_schema(src);
3591
3592 let src: serde_json::Value = json!({
3594 "": {
3595 "commonTypes": {
3596 "Set": {
3597 "type": "Record",
3598 "attributes": {
3599 "a": {
3600 "type": "Long",
3601 }
3602 }
3603 }
3604 },
3605 "entityTypes": {
3606 "b": {
3607 "shape" : {
3608 "type" : "Record",
3609 "attributes" : {
3610 "c" : {
3611 "type" : "Set"
3612 }
3613 }
3614 }
3615 }
3616 },
3617 "actions": { },
3618 }
3619 });
3620 assert_invalid_json_schema(src);
3621
3622 let src: serde_json::Value = json!({
3623 "NS": {
3624 "commonTypes": {
3625 "Set": {
3626 "type": "Record",
3627 "attributes": {
3628 "a": {
3629 "type": "Long",
3630 }
3631 }
3632 }
3633 },
3634 "entityTypes": {
3635 "b": {
3636 "shape" : {
3637 "type" : "Record",
3638 "attributes" : {
3639 "c" : {
3640 "type" : "Set"
3641 }
3642 }
3643 }
3644 }
3645 },
3646 "actions": { },
3647 }
3648 });
3649 assert_invalid_json_schema(src);
3650
3651 let src: serde_json::Value = json!({
3653 "": {
3654 "commonTypes": {
3655 "Long": {
3656 "type": "Record",
3657 "attributes": {
3658 "a": {
3659 "type": "Long",
3660 }
3661 }
3662 }
3663 },
3664 "entityTypes": {
3665 "b": {
3666 "shape" : {
3667 "type" : "Record",
3668 "attributes" : {
3669 "c" : {
3670 "type" : "Long"
3671 }
3672 }
3673 }
3674 }
3675 },
3676 "actions": { },
3677 }
3678 });
3679 assert_invalid_json_schema(src);
3680
3681 let src: serde_json::Value = json!({
3682 "NS": {
3683 "commonTypes": {
3684 "Long": {
3685 "type": "Record",
3686 "attributes": {
3687 "a": {
3688 "type": "Long",
3689 }
3690 }
3691 }
3692 },
3693 "entityTypes": {
3694 "b": {
3695 "shape" : {
3696 "type" : "Record",
3697 "attributes" : {
3698 "c" : {
3699 "type" : "Long"
3700 }
3701 }
3702 }
3703 }
3704 },
3705 "actions": { },
3706 }
3707 });
3708 assert_invalid_json_schema(src);
3709
3710 let src: serde_json::Value = json!({
3712 "": {
3713 "commonTypes": {
3714 "Boolean": {
3715 "type": "Record",
3716 "attributes": {
3717 "a": {
3718 "type": "Long",
3719 }
3720 }
3721 }
3722 },
3723 "entityTypes": {
3724 "b": {
3725 "shape" : {
3726 "type" : "Record",
3727 "attributes" : {
3728 "c" : {
3729 "type" : "Boolean"
3730 }
3731 }
3732 }
3733 }
3734 },
3735 "actions": { },
3736 }
3737 });
3738 assert_invalid_json_schema(src);
3739
3740 let src: serde_json::Value = json!({
3741 "NS": {
3742 "commonTypes": {
3743 "Boolean": {
3744 "type": "Record",
3745 "attributes": {
3746 "a": {
3747 "type": "Long",
3748 }
3749 }
3750 }
3751 },
3752 "entityTypes": {
3753 "b": {
3754 "shape" : {
3755 "type" : "Record",
3756 "attributes" : {
3757 "c" : {
3758 "type" : "Boolean"
3759 }
3760 }
3761 }
3762 }
3763 },
3764 "actions": { },
3765 }
3766 });
3767 assert_invalid_json_schema(src);
3768
3769 let src: serde_json::Value = json!({
3771 "": {
3772 "commonTypes": {
3773 "String": {
3774 "type": "Record",
3775 "attributes": {
3776 "a": {
3777 "type": "Long",
3778 }
3779 }
3780 }
3781 },
3782 "entityTypes": {
3783 "b": {
3784 "shape" : {
3785 "type" : "Record",
3786 "attributes" : {
3787 "c" : {
3788 "type" : "String"
3789 }
3790 }
3791 }
3792 }
3793 },
3794 "actions": { },
3795 }
3796 });
3797 assert_invalid_json_schema(src);
3798
3799 let src: serde_json::Value = json!({
3800 "NS": {
3801 "commonTypes": {
3802 "String": {
3803 "type": "Record",
3804 "attributes": {
3805 "a": {
3806 "type": "Long",
3807 }
3808 }
3809 }
3810 },
3811 "entityTypes": {
3812 "b": {
3813 "shape" : {
3814 "type" : "Record",
3815 "attributes" : {
3816 "c" : {
3817 "type" : "String"
3818 }
3819 }
3820 }
3821 }
3822 },
3823 "actions": { },
3824 }
3825 });
3826 assert_invalid_json_schema(src);
3827
3828 let src: serde_json::Value = json!({
3832 "": {
3833 "commonTypes": {
3834 "Record": {
3835 "type": "Set",
3836 "element": {
3837 "type": "Long"
3838 }
3839 }
3840 },
3841 "entityTypes": {
3842 "b": {
3843 "shape" :
3844 {
3845 "type": "Record",
3846 "attributes" : {
3847 "c" : {
3848 "type" : "String"
3849 }
3850 }
3851 }
3852 }
3853 },
3854 "actions": { },
3855 }
3856 });
3857 assert_invalid_json_schema(src);
3858 }
3859
3860 #[test]
3861 fn reserved_namespace() {
3862 let src: serde_json::Value = json!({
3863 "__cedar": {
3864 "commonTypes": { },
3865 "entityTypes": { },
3866 "actions": { },
3867 }
3868 });
3869 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
3870 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3871
3872 let src: serde_json::Value = json!({
3873 "__cedar::A": {
3874 "commonTypes": { },
3875 "entityTypes": { },
3876 "actions": { },
3877 }
3878 });
3879 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
3880 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3881
3882 let src: serde_json::Value = json!({
3883 "": {
3884 "commonTypes": {
3885 "__cedar": {
3886 "type": "String",
3887 }
3888 },
3889 "entityTypes": { },
3890 "actions": { },
3891 }
3892 });
3893 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
3894 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3895
3896 let src: serde_json::Value = json!({
3897 "A": {
3898 "commonTypes": {
3899 "__cedar": {
3900 "type": "String",
3901 }
3902 },
3903 "entityTypes": { },
3904 "actions": { },
3905 }
3906 });
3907 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
3908 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3909
3910 let src: serde_json::Value = json!({
3911 "": {
3912 "commonTypes": {
3913 "A": {
3914 "type": "__cedar",
3915 }
3916 },
3917 "entityTypes": { },
3918 "actions": { },
3919 }
3920 });
3921 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3922 assert_matches!(schema, Err(e) => {
3923 expect_err(
3924 &src,
3925 &miette::Report::new(e),
3926 &ExpectedErrorMessageBuilder::error("failed to resolve type: __cedar")
3927 .help("`__cedar` has not been declared as a common type")
3928 .exactly_one_underline("__cedar")
3929 .build(),
3930 );
3931 });
3932 }
3933
3934 #[test]
3935 fn attr_named_tags() {
3936 let src = r#"
3937 entity E { tags: Set<{key: String, value: Set<String>}> };
3938 "#;
3939 assert_valid_cedar_schema(src);
3940 }
3941}
3942
3943#[cfg(test)]
3944mod test_579; #[cfg(test)]
3947#[allow(clippy::cognitive_complexity)]
3948mod test_rfc70 {
3949 use super::test::utils::*;
3950 use super::ValidatorSchema;
3951 use crate::validator::types::Type;
3952 use crate::{
3953 extensions::Extensions,
3954 test_utils::{expect_err, ExpectedErrorMessageBuilder},
3955 };
3956 use cool_asserts::assert_matches;
3957 use serde_json::json;
3958
3959 #[test]
3961 fn common_common_conflict() {
3962 let src = "
3963 type T = String;
3964 namespace NS {
3965 type T = String;
3966 entity User { t: T };
3967 }
3968 ";
3969 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3970 expect_err(
3971 src,
3972 &miette::Report::new(e),
3973 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3974 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3975 .exactly_one_underline("type T = String;")
3976 .build(),
3977 );
3978 });
3979
3980 let src_json = json!({
3981 "": {
3982 "commonTypes": {
3983 "T": { "type": "String" },
3984 },
3985 "entityTypes": {},
3986 "actions": {},
3987 },
3988 "NS": {
3989 "commonTypes": {
3990 "T": { "type": "String" },
3991 },
3992 "entityTypes": {
3993 "User": {
3994 "shape": {
3995 "type": "Record",
3996 "attributes": {
3997 "t": { "type": "T" },
3998 },
3999 }
4000 }
4001 },
4002 "actions": {},
4003 }
4004 });
4005 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
4006 expect_err(
4007 &src_json,
4008 &miette::Report::new(e),
4009 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4010 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4011 .build(),
4012 );
4013 });
4014 }
4015
4016 #[test]
4018 fn entity_entity_conflict() {
4019 let src = "
4020 entity T in T { foo: String };
4021 namespace NS {
4022 entity T { bar: String };
4023 entity User { t: T };
4024 }
4025 ";
4026 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
4027 expect_err(
4028 src,
4029 &miette::Report::new(e),
4030 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4031 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4032 .exactly_one_underline("entity T { bar: String };")
4033 .build(),
4034 );
4035 });
4036
4037 let src = "
4039 entity T { foo: String };
4040 namespace NS {
4041 entity T { bar: String };
4042 }
4043 ";
4044 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
4045 expect_err(
4046 src,
4047 &miette::Report::new(e),
4048 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4049 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4050 .exactly_one_underline("entity T { bar: String };")
4051 .build(),
4052 );
4053 });
4054
4055 let src_json = json!({
4056 "": {
4057 "entityTypes": {
4058 "T": {
4059 "memberOfTypes": ["T"],
4060 "shape": {
4061 "type": "Record",
4062 "attributes": {
4063 "foo": { "type": "String" },
4064 },
4065 }
4066 }
4067 },
4068 "actions": {},
4069 },
4070 "NS": {
4071 "entityTypes": {
4072 "T": {
4073 "shape": {
4074 "type": "Record",
4075 "attributes": {
4076 "bar": { "type": "String" },
4077 },
4078 }
4079 },
4080 "User": {
4081 "shape": {
4082 "type": "Record",
4083 "attributes": {
4084 "t": { "type": "Entity", "name": "T" },
4085 },
4086 }
4087 },
4088 },
4089 "actions": {},
4090 }
4091 });
4092 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
4093 expect_err(
4094 &src_json,
4095 &miette::Report::new(e),
4096 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4097 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4098 .build(),
4099 );
4100 });
4101 }
4102
4103 #[test]
4106 fn common_entity_conflict() {
4107 let src = "
4108 entity T in T { foo: String };
4109 namespace NS {
4110 type T = String;
4111 entity User { t: T };
4112 }
4113 ";
4114 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
4115 expect_err(
4116 src,
4117 &miette::Report::new(e),
4118 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4119 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4120 .exactly_one_underline("type T = String;")
4121 .build(),
4122 );
4123 });
4124
4125 let src_json = json!({
4126 "": {
4127 "entityTypes": {
4128 "T": {
4129 "memberOfTypes": ["T"],
4130 "shape": {
4131 "type": "Record",
4132 "attributes": {
4133 "foo": { "type": "String" },
4134 },
4135 }
4136 }
4137 },
4138 "actions": {},
4139 },
4140 "NS": {
4141 "commonTypes": {
4142 "T": { "type": "String" },
4143 },
4144 "entityTypes": {
4145 "User": {
4146 "shape": {
4147 "type": "Record",
4148 "attributes": {
4149 "t": { "type": "T" },
4150 }
4151 }
4152 }
4153 },
4154 "actions": {},
4155 }
4156 });
4157 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
4158 expect_err(
4159 &src_json,
4160 &miette::Report::new(e),
4161 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4162 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4163 .build(),
4164 );
4165 });
4166 }
4167
4168 #[test]
4171 fn entity_common_conflict() {
4172 let src = "
4173 type T = String;
4174 namespace NS {
4175 entity T in T { foo: String };
4176 entity User { t: T };
4177 }
4178 ";
4179 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
4180 expect_err(
4181 src,
4182 &miette::Report::new(e),
4183 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4184 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4185 .exactly_one_underline("entity T in T { foo: String };")
4186 .build(),
4187 );
4188 });
4189
4190 let src_json = json!({
4191 "": {
4192 "commonTypes": {
4193 "T": { "type": "String" },
4194 },
4195 "entityTypes": {},
4196 "actions": {},
4197 },
4198 "NS": {
4199 "entityTypes": {
4200 "T": {
4201 "memberOfTypes": ["T"],
4202 "shape": {
4203 "type": "Record",
4204 "attributes": {
4205 "foo": { "type": "String" },
4206 },
4207 }
4208 },
4209 "User": {
4210 "shape": {
4211 "type": "Record",
4212 "attributes": {
4213 "t": { "type": "T" },
4214 }
4215 }
4216 }
4217 },
4218 "actions": {},
4219 }
4220 });
4221 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
4222 expect_err(
4223 &src_json,
4224 &miette::Report::new(e),
4225 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
4226 .help("try renaming one of the definitions, or moving `T` to a different namespace")
4227 .build(),
4228 );
4229 });
4230 }
4231
4232 #[test]
4234 fn action_action_conflict() {
4235 let src = "
4236 action A;
4237 namespace NS {
4238 action A;
4239 }
4240 ";
4241 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
4242 let assertion = ExpectedErrorMessageBuilder::error("definition of `NS::Action::\"A\"` illegally shadows the existing definition of `Action::\"A\"`")
4243 .help("try renaming one of the actions, or moving `Action::\"A\"` to a different namespace");
4244 #[cfg(feature = "extended-schema")]
4245 let assertion = assertion.exactly_one_underline("A");
4246
4247 expect_err(
4248 src,
4249 &miette::Report::new(e),
4250 &assertion.build()
4251 );
4252 });
4253
4254 let src_json = json!({
4255 "": {
4256 "entityTypes": {},
4257 "actions": {
4258 "A": {},
4259 },
4260 },
4261 "NS": {
4262 "entityTypes": {},
4263 "actions": {
4264 "A": {},
4265 },
4266 }
4267 });
4268 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
4269 expect_err(
4270 &src_json,
4271 &miette::Report::new(e),
4272 &ExpectedErrorMessageBuilder::error("definition of `NS::Action::\"A\"` illegally shadows the existing definition of `Action::\"A\"`")
4273 .help("try renaming one of the actions, or moving `Action::\"A\"` to a different namespace")
4274 .build(),
4275 );
4276 });
4277 }
4278
4279 #[test]
4281 fn action_common_conflict() {
4282 let src = "
4283 action A;
4284 action B; // same name as a common type in same (empty) namespace
4285 action C; // same name as a common type in different (nonempty) namespace
4286 type B = String;
4287 type E = String;
4288 namespace NS1 {
4289 type C = String;
4290 entity User { b: B, c: C, e: E };
4291 }
4292 namespace NS2 {
4293 type D = String;
4294 action D; // same name as a common type in same (nonempty) namespace
4295 action E; // same name as a common type in different (empty) namespace
4296 entity User { b: B, d: D, e: E };
4297 }
4298 ";
4299 assert_valid_cedar_schema(src);
4300
4301 let src_json = json!({
4302 "": {
4303 "commonTypes": {
4304 "B": { "type": "String" },
4305 "E": { "type": "String" },
4306 },
4307 "entityTypes": {},
4308 "actions": {
4309 "A": {},
4310 "B": {},
4311 "C": {},
4312 },
4313 },
4314 "NS1": {
4315 "commonTypes": {
4316 "C": { "type": "String" },
4317 },
4318 "entityTypes": {
4319 "User": {
4320 "shape": {
4321 "type": "Record",
4322 "attributes": {
4323 "b": { "type": "B" },
4324 "c": { "type": "C" },
4325 "e": { "type": "E" },
4326 }
4327 }
4328 },
4329 },
4330 "actions": {}
4331 },
4332 "NS2": {
4333 "commonTypes": {
4334 "D": { "type": "String" },
4335 },
4336 "entityTypes": {
4337 "User": {
4338 "shape": {
4339 "type": "Record",
4340 "attributes": {
4341 "b": { "type": "B" },
4342 "d": { "type": "D" },
4343 "e": { "type": "E" },
4344 }
4345 }
4346 }
4347 },
4348 "actions": {
4349 "D": {},
4350 "E": {},
4351 }
4352 }
4353 });
4354 assert_valid_json_schema(src_json);
4355 }
4356
4357 #[test]
4359 fn action_entity_conflict() {
4360 let src = "
4361 action A;
4362 action B; // same name as an entity type in same (empty) namespace
4363 action C; // same name as an entity type in different (nonempty) namespace
4364 entity B;
4365 entity E;
4366 namespace NS1 {
4367 entity C;
4368 entity User { b: B, c: C, e: E };
4369 }
4370 namespace NS2 {
4371 entity D;
4372 action D; // same name as an entity type in same (nonempty) namespace
4373 action E; // same name as an entity type in different (empty) namespace
4374 entity User { b: B, d: D, e: E };
4375 }
4376 ";
4377 assert_valid_cedar_schema(src);
4378
4379 let src_json = json!({
4380 "": {
4381 "entityTypes": {
4382 "B": {},
4383 "E": {},
4384 },
4385 "actions": {
4386 "A": {},
4387 "B": {},
4388 "C": {},
4389 },
4390 },
4391 "NS1": {
4392 "entityTypes": {
4393 "C": {},
4394 "User": {
4395 "shape": {
4396 "type": "Record",
4397 "attributes": {
4398 "b": { "type": "Entity", "name": "B" },
4399 "c": { "type": "Entity", "name": "C" },
4400 "e": { "type": "Entity", "name": "E" },
4401 }
4402 }
4403 },
4404 },
4405 "actions": {}
4406 },
4407 "NS2": {
4408 "entityTypes": {
4409 "D": {},
4410 "User": {
4411 "shape": {
4412 "type": "Record",
4413 "attributes": {
4414 "b": { "type": "Entity", "name": "B" },
4415 "d": { "type": "Entity", "name": "D" },
4416 "e": { "type": "Entity", "name": "E" },
4417 }
4418 }
4419 }
4420 },
4421 "actions": {
4422 "D": {},
4423 "E": {},
4424 }
4425 }
4426 });
4427 assert_valid_json_schema(src_json);
4428 }
4429
4430 #[test]
4436 fn common_shadowing_entity_same_namespace() {
4437 let src = "
4438 entity T;
4439 type T = Bool; // works in the empty namespace
4440 namespace NS {
4441 entity E;
4442 type E = Bool; // works in a nonempty namespace
4443 }
4444 ";
4445 assert_valid_cedar_schema(src);
4446
4447 let src_json = json!({
4448 "": {
4449 "commonTypes": {
4450 "T": { "type": "Entity", "name": "T" },
4451 },
4452 "entityTypes": {
4453 "T": {},
4454 },
4455 "actions": {}
4456 },
4457 "NS1": {
4458 "commonTypes": {
4459 "E": { "type": "Entity", "name": "E" },
4460 },
4461 "entityTypes": {
4462 "E": {},
4463 },
4464 "actions": {}
4465 },
4466 "NS2": {
4467 "commonTypes": {
4468 "E": { "type": "String" },
4469 },
4470 "entityTypes": {
4471 "E": {},
4472 },
4473 "actions": {}
4474 }
4475 });
4476 assert_valid_json_schema(src_json);
4477 }
4478
4479 #[test]
4482 fn common_shadowing_primitive() {
4483 let src = "
4484 type String = Long;
4485 entity E {
4486 a: String,
4487 b: __cedar::String,
4488 c: Long,
4489 d: __cedar::Long,
4490 };
4491 namespace NS {
4492 type Bool = Long;
4493 entity F {
4494 a: Bool,
4495 b: __cedar::Bool,
4496 c: Long,
4497 d: __cedar::Long,
4498 };
4499 }
4500 ";
4501 assert_invalid_cedar_schema(src);
4502 let src = "
4503 type _String = Long;
4504 entity E {
4505 a: _String,
4506 b: __cedar::String,
4507 c: Long,
4508 d: __cedar::Long,
4509 };
4510 namespace NS {
4511 type _Bool = Long;
4512 entity F {
4513 a: _Bool,
4514 b: __cedar::Bool,
4515 c: Long,
4516 d: __cedar::Long,
4517 };
4518 }
4519 ";
4520 let schema = assert_valid_cedar_schema(src);
4521 let e = assert_entity_type_exists(&schema, "E");
4522 assert_matches!(e.attr("a"), Some(atype) => {
4523 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4525 assert_matches!(e.attr("b"), Some(atype) => {
4526 assert_eq!(&atype.attr_type, &Type::primitive_string());
4527 });
4528 assert_matches!(e.attr("c"), Some(atype) => {
4529 assert_eq!(&atype.attr_type, &Type::primitive_long());
4530 });
4531 assert_matches!(e.attr("d"), Some(atype) => {
4532 assert_eq!(&atype.attr_type, &Type::primitive_long());
4533 });
4534 let f = assert_entity_type_exists(&schema, "NS::F");
4535 assert_matches!(f.attr("a"), Some(atype) => {
4536 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4538 assert_matches!(f.attr("b"), Some(atype) => {
4539 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4540 });
4541 assert_matches!(f.attr("c"), Some(atype) => {
4542 assert_eq!(&atype.attr_type, &Type::primitive_long());
4543 });
4544 assert_matches!(f.attr("d"), Some(atype) => {
4545 assert_eq!(&atype.attr_type, &Type::primitive_long());
4546 });
4547
4548 let src_json = json!({
4549 "": {
4550 "commonTypes": {
4551 "String": { "type": "Long" },
4552 },
4553 "entityTypes": {
4554 "E": {
4555 "shape": {
4556 "type": "Record",
4557 "attributes": {
4558 "a": { "type": "String" },
4559 "b": { "type": "__cedar::String" },
4560 "c": { "type": "Long" },
4561 "d": { "type": "__cedar::Long" },
4562 }
4563 }
4564 },
4565 },
4566 "actions": {}
4567 },
4568 "NS": {
4569 "commonTypes": {
4570 "Bool": { "type": "Long" },
4571 },
4572 "entityTypes": {
4573 "F": {
4574 "shape": {
4575 "type": "Record",
4576 "attributes": {
4577 "a": { "type": "Bool" },
4578 "b": { "type": "__cedar::Bool" },
4579 "c": { "type": "Long" },
4580 "d": { "type": "__cedar::Long" },
4581 }
4582 }
4583 },
4584 },
4585 "actions": {}
4586 }
4587 });
4588 assert_invalid_json_schema(&src_json);
4589 let src_json = json!({
4590 "": {
4591 "commonTypes": {
4592 "_String": { "type": "Long" },
4593 },
4594 "entityTypes": {
4595 "E": {
4596 "shape": {
4597 "type": "Record",
4598 "attributes": {
4599 "a": { "type": "_String" },
4600 "b": { "type": "__cedar::String" },
4601 "c": { "type": "Long" },
4602 "d": { "type": "__cedar::Long" },
4603 }
4604 }
4605 },
4606 },
4607 "actions": {}
4608 },
4609 "NS": {
4610 "commonTypes": {
4611 "_Bool": { "type": "Long" },
4612 },
4613 "entityTypes": {
4614 "F": {
4615 "shape": {
4616 "type": "Record",
4617 "attributes": {
4618 "a": { "type": "_Bool" },
4619 "b": { "type": "__cedar::Bool" },
4620 "c": { "type": "Long" },
4621 "d": { "type": "__cedar::Long" },
4622 }
4623 }
4624 },
4625 },
4626 "actions": {}
4627 }
4628 });
4629 let schema = assert_valid_json_schema(src_json);
4630 let e = assert_entity_type_exists(&schema, "E");
4631 assert_matches!(e.attr("a"), Some(atype) => {
4632 assert_eq!(&atype.attr_type, &Type::primitive_long());
4633 });
4634 assert_matches!(e.attr("b"), Some(atype) => {
4635 assert_eq!(&atype.attr_type, &Type::primitive_string());
4636 });
4637 assert_matches!(e.attr("c"), Some(atype) => {
4638 assert_eq!(&atype.attr_type, &Type::primitive_long());
4639 });
4640 assert_matches!(e.attr("d"), Some(atype) => {
4641 assert_eq!(&atype.attr_type, &Type::primitive_long());
4642 });
4643 let f = assert_entity_type_exists(&schema, "NS::F");
4644 assert_matches!(f.attr("a"), Some(atype) => {
4645 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4647 assert_matches!(f.attr("b"), Some(atype) => {
4648 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4649 });
4650 assert_matches!(f.attr("c"), Some(atype) => {
4651 assert_eq!(&atype.attr_type, &Type::primitive_long());
4652 });
4653 assert_matches!(f.attr("d"), Some(atype) => {
4654 assert_eq!(&atype.attr_type, &Type::primitive_long());
4655 });
4656 }
4657
4658 #[test]
4661 fn common_shadowing_extension() {
4662 let src = "
4663 type ipaddr = Long;
4664 entity E {
4665 a: ipaddr,
4666 b: __cedar::ipaddr,
4667 c: Long,
4668 d: __cedar::Long,
4669 };
4670 namespace NS {
4671 type decimal = Long;
4672 entity F {
4673 a: decimal,
4674 b: __cedar::decimal,
4675 c: Long,
4676 d: __cedar::Long,
4677 };
4678 }
4679 ";
4680 let schema = assert_valid_cedar_schema(src);
4681 let e = assert_entity_type_exists(&schema, "E");
4682 assert_matches!(e.attr("a"), Some(atype) => {
4683 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4685 assert_matches!(e.attr("b"), Some(atype) => {
4686 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4687 });
4688 assert_matches!(e.attr("c"), Some(atype) => {
4689 assert_eq!(&atype.attr_type, &Type::primitive_long());
4690 });
4691 assert_matches!(e.attr("d"), Some(atype) => {
4692 assert_eq!(&atype.attr_type, &Type::primitive_long());
4693 });
4694 let f = assert_entity_type_exists(&schema, "NS::F");
4695 assert_matches!(f.attr("a"), Some(atype) => {
4696 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4698 assert_matches!(f.attr("b"), Some(atype) => {
4699 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4700 });
4701 assert_matches!(f.attr("c"), Some(atype) => {
4702 assert_eq!(&atype.attr_type, &Type::primitive_long());
4703 });
4704 assert_matches!(f.attr("d"), Some(atype) => {
4705 assert_eq!(&atype.attr_type, &Type::primitive_long());
4706 });
4707
4708 let src_json = json!({
4709 "": {
4710 "commonTypes": {
4711 "ipaddr": { "type": "Long" },
4712 },
4713 "entityTypes": {
4714 "E": {
4715 "shape": {
4716 "type": "Record",
4717 "attributes": {
4718 "a": { "type": "ipaddr" },
4719 "b": { "type": "__cedar::ipaddr" },
4720 "c": { "type": "Long" },
4721 "d": { "type": "__cedar::Long" },
4722 }
4723 }
4724 },
4725 },
4726 "actions": {}
4727 },
4728 "NS": {
4729 "commonTypes": {
4730 "decimal": { "type": "Long" },
4731 },
4732 "entityTypes": {
4733 "F": {
4734 "shape": {
4735 "type": "Record",
4736 "attributes": {
4737 "a": { "type": "decimal" },
4738 "b": { "type": "__cedar::decimal" },
4739 "c": { "type": "Long" },
4740 "d": { "type": "__cedar::Long" },
4741 }
4742 }
4743 },
4744 },
4745 "actions": {}
4746 }
4747 });
4748 let schema = assert_valid_json_schema(src_json);
4749 let e = assert_entity_type_exists(&schema, "E");
4750 assert_matches!(e.attr("a"), Some(atype) => {
4751 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4753 assert_matches!(e.attr("b"), Some(atype) => {
4754 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4755 });
4756 assert_matches!(e.attr("c"), Some(atype) => {
4757 assert_eq!(&atype.attr_type, &Type::primitive_long());
4758 });
4759 assert_matches!(e.attr("d"), Some(atype) => {
4760 assert_eq!(&atype.attr_type, &Type::primitive_long());
4761 });
4762 let f = assert_entity_type_exists(&schema, "NS::F");
4763 assert_matches!(f.attr("a"), Some(atype) => {
4764 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4766 assert_matches!(f.attr("b"), Some(atype) => {
4767 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4768 });
4769 assert_matches!(f.attr("c"), Some(atype) => {
4770 assert_eq!(&atype.attr_type, &Type::primitive_long());
4771 });
4772 assert_matches!(f.attr("d"), Some(atype) => {
4773 assert_eq!(&atype.attr_type, &Type::primitive_long());
4774 });
4775 }
4776
4777 #[test]
4780 fn entity_shadowing_primitive() {
4781 let src = "
4782 entity String;
4783 entity E {
4784 a: String,
4785 b: __cedar::String,
4786 };
4787 namespace NS {
4788 entity Bool;
4789 entity F {
4790 a: Bool,
4791 b: __cedar::Bool,
4792 };
4793 }
4794 ";
4795 let schema = assert_valid_cedar_schema(src);
4796 let e = assert_entity_type_exists(&schema, "E");
4797 assert_matches!(e.attr("a"), Some(atype) => {
4798 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("String"));
4799 });
4800 assert_matches!(e.attr("b"), Some(atype) => {
4801 assert_eq!(&atype.attr_type, &Type::primitive_string());
4802 });
4803 let f = assert_entity_type_exists(&schema, "NS::F");
4804 assert_matches!(f.attr("a"), Some(atype) => {
4805 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::Bool")); });
4807 assert_matches!(f.attr("b"), Some(atype) => {
4808 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4809 });
4810
4811 let src_json = json!({
4812 "": {
4813 "entityTypes": {
4814 "String": {},
4815 "E": {
4816 "shape": {
4817 "type": "Record",
4818 "attributes": {
4819 "a": { "type": "Entity", "name": "String" },
4820 "b": { "type": "__cedar::String" },
4821 }
4822 }
4823 },
4824 },
4825 "actions": {}
4826 },
4827 "NS": {
4828 "entityTypes": {
4829 "Bool": {},
4830 "F": {
4831 "shape": {
4832 "type": "Record",
4833 "attributes": {
4834 "a": { "type": "Entity", "name": "Bool" },
4835 "b": { "type": "__cedar::Bool" },
4836 }
4837 }
4838 },
4839 },
4840 "actions": {}
4841 }
4842 });
4843 let schema = assert_valid_json_schema(src_json);
4844 let e = assert_entity_type_exists(&schema, "E");
4845 assert_matches!(e.attr("a"), Some(atype) => {
4846 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("String"));
4847 });
4848 assert_matches!(e.attr("b"), Some(atype) => {
4849 assert_eq!(&atype.attr_type, &Type::primitive_string());
4850 });
4851 let f = assert_entity_type_exists(&schema, "NS::F");
4852 assert_matches!(f.attr("a"), Some(atype) => {
4853 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::Bool"));
4854 });
4855 assert_matches!(f.attr("b"), Some(atype) => {
4856 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4857 });
4858 }
4859
4860 #[test]
4863 fn entity_shadowing_extension() {
4864 let src = "
4865 entity ipaddr;
4866 entity E {
4867 a: ipaddr,
4868 b: __cedar::ipaddr,
4869 };
4870 namespace NS {
4871 entity decimal;
4872 entity F {
4873 a: decimal,
4874 b: __cedar::decimal,
4875 };
4876 }
4877 ";
4878 let schema = assert_valid_cedar_schema(src);
4879 let e = assert_entity_type_exists(&schema, "E");
4880 assert_matches!(e.attr("a"), Some(atype) => {
4881 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("ipaddr"));
4882 });
4883 assert_matches!(e.attr("b"), Some(atype) => {
4884 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4885 });
4886 let f = assert_entity_type_exists(&schema, "NS::F");
4887 assert_matches!(f.attr("a"), Some(atype) => {
4888 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::decimal"));
4889 });
4890 assert_matches!(f.attr("b"), Some(atype) => {
4891 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4892 });
4893
4894 let src_json = json!({
4895 "": {
4896 "entityTypes": {
4897 "ipaddr": {},
4898 "E": {
4899 "shape": {
4900 "type": "Record",
4901 "attributes": {
4902 "a": { "type": "Entity", "name": "ipaddr" },
4903 "b": { "type": "__cedar::ipaddr" },
4904 }
4905 }
4906 },
4907 },
4908 "actions": {}
4909 },
4910 "NS": {
4911 "entityTypes": {
4912 "decimal": {},
4913 "F": {
4914 "shape": {
4915 "type": "Record",
4916 "attributes": {
4917 "a": { "type": "Entity", "name": "decimal" },
4918 "b": { "type": "__cedar::decimal" },
4919 }
4920 }
4921 },
4922 },
4923 "actions": {}
4924 }
4925 });
4926 let schema = assert_valid_json_schema(src_json);
4927 let e = assert_entity_type_exists(&schema, "E");
4928 assert_matches!(e.attr("a"), Some(atype) => {
4929 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("ipaddr"));
4930 });
4931 assert_matches!(e.attr("b"), Some(atype) => {
4932 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4933 });
4934 let f = assert_entity_type_exists(&schema, "NS::F");
4935 assert_matches!(f.attr("a"), Some(atype) => {
4936 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::decimal"));
4937 });
4938 assert_matches!(f.attr("b"), Some(atype) => {
4939 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4940 });
4941 }
4942}
4943
4944#[cfg(test)]
4946#[allow(clippy::cognitive_complexity)]
4947mod entity_tags {
4948 use super::{test::utils::*, *};
4949 use crate::{
4950 extensions::Extensions,
4951 test_utils::{expect_err, ExpectedErrorMessageBuilder},
4952 };
4953 use cool_asserts::assert_matches;
4954 use serde_json::json;
4955
4956 use crate::validator::types::Primitive;
4957
4958 #[test]
4959 fn cedar_syntax_tags() {
4960 let src = "
4962 entity User = {
4963 jobLevel: Long,
4964 } tags Set<String>;
4965 entity Document = {
4966 owner: User,
4967 } tags Set<String>;
4968 ";
4969 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Ok((schema, warnings)) => {
4970 assert!(warnings.is_empty());
4971 let user = assert_entity_type_exists(&schema, "User");
4972 assert_matches!(user.tag_type(), Some(Type::Set { element_type: Some(el_ty) }) => {
4973 assert_matches!(&**el_ty, Type::Primitive { primitive_type: Primitive::String });
4974 });
4975 let doc = assert_entity_type_exists(&schema, "Document");
4976 assert_matches!(doc.tag_type(), Some(Type::Set { element_type: Some(el_ty) }) => {
4977 assert_matches!(&**el_ty, Type::Primitive { primitive_type: Primitive::String });
4978 });
4979 });
4980 }
4981
4982 #[test]
4983 fn json_syntax_tags() {
4984 let json = json!({"": {
4986 "entityTypes": {
4987 "User" : {
4988 "shape" : {
4989 "type" : "Record",
4990 "attributes" : {
4991 "jobLevel" : {
4992 "type" : "Long"
4993 },
4994 }
4995 },
4996 "tags" : {
4997 "type" : "Set",
4998 "element": { "type": "String" }
4999 }
5000 },
5001 "Document" : {
5002 "shape" : {
5003 "type" : "Record",
5004 "attributes" : {
5005 "owner" : {
5006 "type" : "Entity",
5007 "name" : "User"
5008 },
5009 }
5010 },
5011 "tags" : {
5012 "type" : "Set",
5013 "element": { "type": "String" }
5014 }
5015 }
5016 },
5017 "actions": {}
5018 }});
5019 assert_matches!(ValidatorSchema::from_json_value(json, Extensions::all_available()), Ok(schema) => {
5020 let user = assert_entity_type_exists(&schema, "User");
5021 assert_matches!(user.tag_type(), Some(Type::Set { element_type: Some(el_ty) }) => {
5022 assert_matches!(&**el_ty, Type::Primitive { primitive_type: Primitive::String });
5023 });
5024 let doc = assert_entity_type_exists(&schema, "Document");
5025 assert_matches!(doc.tag_type(), Some(Type::Set { element_type: Some(el_ty) }) => {
5026 assert_matches!(&**el_ty, Type::Primitive { primitive_type: Primitive::String });
5027 });
5028 });
5029 }
5030
5031 #[test]
5032 fn other_tag_types() {
5033 let src = "
5034 entity E;
5035 type Blah = {
5036 foo: Long,
5037 bar: Set<E>,
5038 };
5039 entity Foo1 in E {
5040 bool: Bool,
5041 } tags Bool;
5042 entity Foo2 in E {
5043 bool: Bool,
5044 } tags { bool: Bool };
5045 entity Foo3 in E tags E;
5046 entity Foo4 in E tags Set<E>;
5047 entity Foo5 in E tags { a: String, b: Long };
5048 entity Foo6 in E tags Blah;
5049 entity Foo7 in E tags Set<Set<{a: Blah}>>;
5050 entity Foo8 in E tags Foo7;
5051 ";
5052 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Ok((schema, warnings)) => {
5053 assert!(warnings.is_empty());
5054 let e = assert_entity_type_exists(&schema, "E");
5055 assert_matches!(e.tag_type(), None);
5056 let foo1 = assert_entity_type_exists(&schema, "Foo1");
5057 assert_matches!(foo1.tag_type(), Some(Type::Primitive { primitive_type: Primitive::Bool }));
5058 let foo2 = assert_entity_type_exists(&schema, "Foo2");
5059 assert_matches!(foo2.tag_type(), Some(Type::EntityOrRecord(EntityRecordKind::Record { .. })));
5060 let foo3 = assert_entity_type_exists(&schema, "Foo3");
5061 assert_matches!(foo3.tag_type(), Some(Type::EntityOrRecord(EntityRecordKind::Entity(_))));
5062 let foo4 = assert_entity_type_exists(&schema, "Foo4");
5063 assert_matches!(foo4.tag_type(), Some(Type::Set { element_type }) => assert_matches!(element_type.as_deref(), Some(Type::EntityOrRecord(EntityRecordKind::Entity(_)))));
5064 let foo5 = assert_entity_type_exists(&schema, "Foo5");
5065 assert_matches!(foo5.tag_type(), Some(Type::EntityOrRecord(EntityRecordKind::Record { .. })));
5066 let foo6 = assert_entity_type_exists(&schema, "Foo6");
5067 assert_matches!(foo6.tag_type(), Some(Type::EntityOrRecord(EntityRecordKind::Record { .. })));
5068 let foo7 = assert_entity_type_exists(&schema, "Foo7");
5069 assert_matches!(foo7.tag_type(), Some(Type::Set { element_type }) => assert_matches!(element_type.as_deref(), Some(Type::Set { element_type }) => assert_matches!(element_type.as_deref(), Some(Type::EntityOrRecord(EntityRecordKind::Record { .. })))));
5070 let foo8 = assert_entity_type_exists(&schema, "Foo8");
5071 assert_matches!(foo8.tag_type(), Some(Type::EntityOrRecord(EntityRecordKind::Entity(_))));
5072 });
5073 }
5074
5075 #[test]
5076 fn invalid_tags() {
5077 let src = "entity E tags Undef;";
5078 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
5079 expect_err(
5080 src,
5081 &miette::Report::new(e),
5082 &ExpectedErrorMessageBuilder::error("failed to resolve type: Undef")
5083 .help("`Undef` has not been declared as a common or entity type")
5084 .exactly_one_underline("Undef")
5085 .build(),
5086 );
5087 });
5088 }
5089}
5090
5091#[cfg(test)]
5092mod test_resolver {
5093 use std::collections::HashMap;
5094
5095 use crate::{ast::InternalName, extensions::Extensions};
5096 use cool_asserts::assert_matches;
5097
5098 use super::{AllDefs, CommonTypeResolver, LocatedType};
5099 use crate::validator::{
5100 err::SchemaError, json_schema, types::Type, ConditionalName, ValidatorSchemaFragment,
5101 };
5102
5103 fn resolve(
5104 schema_json: serde_json::Value,
5105 ) -> Result<HashMap<InternalName, LocatedType>, SchemaError> {
5106 let sfrag = json_schema::Fragment::from_json_value(schema_json).unwrap();
5107 let schema: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
5108 sfrag.try_into().unwrap();
5109 let all_defs = AllDefs::single_fragment(&schema);
5110 let schema = schema.fully_qualify_type_references(&all_defs).unwrap();
5111 let mut defs = HashMap::new();
5112 for def in schema.0 {
5113 defs.extend(def.common_types.defs.into_iter());
5114 }
5115 let resolver = CommonTypeResolver::new(&defs);
5116 resolver
5117 .resolve(Extensions::all_available())
5118 .map(|map| map.into_iter().map(|(k, v)| (k.clone(), v)).collect())
5119 }
5120
5121 #[test]
5122 fn test_simple() {
5123 let schema = serde_json::json!(
5124 {
5125 "": {
5126 "entityTypes": {},
5127 "actions": {},
5128 "commonTypes": {
5129 "a" : {
5130 "type": "b"
5131 },
5132 "b": {
5133 "type": "Boolean"
5134 }
5135 }
5136 }
5137 }
5138 );
5139 let res = resolve(schema).unwrap();
5140 assert_eq!(
5141 res,
5142 HashMap::from_iter([
5143 (
5144 "a".parse().unwrap(),
5145 LocatedType::new(Type::primitive_boolean())
5146 ),
5147 (
5148 "b".parse().unwrap(),
5149 LocatedType::new(Type::primitive_boolean())
5150 )
5151 ])
5152 );
5153
5154 let schema = serde_json::json!(
5155 {
5156 "": {
5157 "entityTypes": {},
5158 "actions": {},
5159 "commonTypes": {
5160 "a" : {
5161 "type": "b"
5162 },
5163 "b": {
5164 "type": "c"
5165 },
5166 "c": {
5167 "type": "Boolean"
5168 }
5169 }
5170 }
5171 }
5172 );
5173 let res = resolve(schema).unwrap();
5174 assert_eq!(
5175 res,
5176 HashMap::from_iter([
5177 (
5178 "a".parse().unwrap(),
5179 LocatedType::new(Type::primitive_boolean())
5180 ),
5181 (
5182 "b".parse().unwrap(),
5183 LocatedType::new(Type::primitive_boolean())
5184 ),
5185 (
5186 "c".parse().unwrap(),
5187 LocatedType::new(Type::primitive_boolean())
5188 )
5189 ])
5190 );
5191 }
5192
5193 #[test]
5194 fn test_set() {
5195 let schema = serde_json::json!(
5196 {
5197 "": {
5198 "entityTypes": {},
5199 "actions": {},
5200 "commonTypes": {
5201 "a" : {
5202 "type": "Set",
5203 "element": {
5204 "type": "b"
5205 }
5206 },
5207 "b": {
5208 "type": "Boolean"
5209 }
5210 }
5211 }
5212 }
5213 );
5214 let res = resolve(schema).unwrap();
5215 assert_eq!(
5216 res,
5217 HashMap::from_iter([
5218 (
5219 "a".parse().unwrap(),
5220 LocatedType::new(Type::set(Type::primitive_boolean()))
5221 ),
5222 (
5223 "b".parse().unwrap(),
5224 LocatedType::new(Type::primitive_boolean())
5225 )
5226 ])
5227 );
5228 }
5229
5230 #[test]
5231 fn test_record() {
5232 let schema = serde_json::json!(
5233 {
5234 "": {
5235 "entityTypes": {},
5236 "actions": {},
5237 "commonTypes": {
5238 "a" : {
5239 "type": "Record",
5240 "attributes": {
5241 "foo": {
5242 "type": "b"
5243 }
5244 }
5245 },
5246 "b": {
5247 "type": "Boolean"
5248 }
5249 }
5250 }
5251 }
5252 );
5253 let res = resolve(schema).unwrap();
5254 assert_eq!(
5255 res,
5256 HashMap::from_iter([
5257 (
5258 "a".parse().unwrap(),
5259 LocatedType::new(Type::record_with_required_attributes(
5260 [("foo".into(), Type::primitive_boolean())],
5261 crate::validator::types::OpenTag::ClosedAttributes
5262 ))
5263 ),
5264 (
5265 "b".parse().unwrap(),
5266 LocatedType::new(Type::primitive_boolean())
5267 )
5268 ])
5269 );
5270 }
5271
5272 #[test]
5273 fn test_names() {
5274 let schema = serde_json::json!(
5275 {
5276 "A": {
5277 "entityTypes": {},
5278 "actions": {},
5279 "commonTypes": {
5280 "a" : {
5281 "type": "B::a"
5282 }
5283 }
5284 },
5285 "B": {
5286 "entityTypes": {},
5287 "actions": {},
5288 "commonTypes": {
5289 "a" : {
5290 "type": "Boolean"
5291 }
5292 }
5293 }
5294 }
5295 );
5296 let res = resolve(schema).unwrap();
5297 assert_eq!(
5298 res,
5299 HashMap::from_iter([
5300 (
5301 "A::a".parse().unwrap(),
5302 LocatedType::new(Type::primitive_boolean())
5303 ),
5304 (
5305 "B::a".parse().unwrap(),
5306 LocatedType::new(Type::primitive_boolean())
5307 )
5308 ])
5309 );
5310 }
5311
5312 #[test]
5313 fn test_cycles() {
5314 let schema = serde_json::json!(
5316 {
5317 "": {
5318 "entityTypes": {},
5319 "actions": {},
5320 "commonTypes": {
5321 "a" : {
5322 "type": "a"
5323 }
5324 }
5325 }
5326 }
5327 );
5328 let res = resolve(schema);
5329 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
5330
5331 let schema = serde_json::json!(
5333 {
5334 "": {
5335 "entityTypes": {},
5336 "actions": {},
5337 "commonTypes": {
5338 "a" : {
5339 "type": "b"
5340 },
5341 "b" : {
5342 "type": "a"
5343 }
5344 }
5345 }
5346 }
5347 );
5348 let res = resolve(schema);
5349 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
5350
5351 let schema = serde_json::json!(
5353 {
5354 "": {
5355 "entityTypes": {},
5356 "actions": {},
5357 "commonTypes": {
5358 "a" : {
5359 "type": "b"
5360 },
5361 "b" : {
5362 "type": "c"
5363 },
5364 "c" : {
5365 "type": "a"
5366 }
5367 }
5368 }
5369 }
5370 );
5371 let res = resolve(schema);
5372 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
5373
5374 let schema = serde_json::json!(
5376 {
5377 "A": {
5378 "entityTypes": {},
5379 "actions": {},
5380 "commonTypes": {
5381 "a" : {
5382 "type": "B::a"
5383 }
5384 }
5385 },
5386 "B": {
5387 "entityTypes": {},
5388 "actions": {},
5389 "commonTypes": {
5390 "a" : {
5391 "type": "A::a"
5392 }
5393 }
5394 }
5395 }
5396 );
5397 let res = resolve(schema);
5398 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
5399
5400 let schema = serde_json::json!(
5402 {
5403 "A": {
5404 "entityTypes": {},
5405 "actions": {},
5406 "commonTypes": {
5407 "a" : {
5408 "type": "B::a"
5409 }
5410 }
5411 },
5412 "B": {
5413 "entityTypes": {},
5414 "actions": {},
5415 "commonTypes": {
5416 "a" : {
5417 "type": "C::a"
5418 }
5419 }
5420 },
5421 "C": {
5422 "entityTypes": {},
5423 "actions": {},
5424 "commonTypes": {
5425 "a" : {
5426 "type": "A::a"
5427 }
5428 }
5429 }
5430 }
5431 );
5432 let res = resolve(schema);
5433 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
5434
5435 let schema = serde_json::json!(
5437 {
5438 "A": {
5439 "entityTypes": {},
5440 "actions": {},
5441 "commonTypes": {
5442 "a" : {
5443 "type": "B::a"
5444 }
5445 }
5446 },
5447 "B": {
5448 "entityTypes": {},
5449 "actions": {},
5450 "commonTypes": {
5451 "a" : {
5452 "type": "c"
5453 },
5454 "c": {
5455 "type": "A::a"
5456 }
5457 }
5458 }
5459 }
5460 );
5461 let res = resolve(schema);
5462 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
5463 }
5464}
5465
5466#[cfg(test)]
5467mod test_access {
5468 use super::*;
5469
5470 fn schema() -> ValidatorSchema {
5471 let src = r#"
5472 type Task = {
5473 "id": Long,
5474 "name": String,
5475 "state": String,
5476};
5477
5478type Tasks = Set<Task>;
5479entity List in [Application] = {
5480 "editors": Team,
5481 "name": String,
5482 "owner": User,
5483 "readers": Team,
5484 "tasks": Tasks,
5485};
5486entity Application;
5487entity User in [Team, Application] = {
5488 "joblevel": Long,
5489 "location": String,
5490};
5491
5492entity CoolList;
5493
5494entity Team in [Team, Application];
5495
5496action Read, Write, Create;
5497
5498action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5499 principal: [User],
5500 resource : [List]
5501};
5502
5503action GetList in Read appliesTo {
5504 principal : [User],
5505 resource : [List, CoolList]
5506};
5507
5508action GetLists in Read appliesTo {
5509 principal : [User],
5510 resource : [Application]
5511};
5512
5513action CreateList in Create appliesTo {
5514 principal : [User],
5515 resource : [Application]
5516};
5517
5518 "#;
5519
5520 src.parse().unwrap()
5521 }
5522
5523 #[test]
5524 fn principals() {
5525 let schema = schema();
5526 let principals = schema.principals().collect::<HashSet<_>>();
5527 assert_eq!(principals.len(), 1);
5528 let user: EntityType = "User".parse().unwrap();
5529 assert!(principals.contains(&user));
5530 let principals = schema.principals().collect::<Vec<_>>();
5531 assert!(principals.len() > 1);
5532 assert!(principals.iter().all(|ety| **ety == user));
5533 }
5534
5535 #[test]
5536 fn empty_schema_principals_and_resources() {
5537 let empty: ValidatorSchema = "".parse().unwrap();
5538 assert!(empty.principals().next().is_none());
5539 assert!(empty.resources().next().is_none());
5540 }
5541
5542 #[test]
5543 fn resources() {
5544 let schema = schema();
5545 let resources = schema.resources().cloned().collect::<HashSet<_>>();
5546 let expected: HashSet<EntityType> = HashSet::from([
5547 "List".parse().unwrap(),
5548 "Application".parse().unwrap(),
5549 "CoolList".parse().unwrap(),
5550 ]);
5551 assert_eq!(resources, expected);
5552 }
5553
5554 #[test]
5555 fn principals_for_action() {
5556 let schema = schema();
5557 let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
5558 let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
5559 let got = schema
5560 .principals_for_action(&delete_list)
5561 .unwrap()
5562 .cloned()
5563 .collect::<Vec<_>>();
5564 assert_eq!(got, vec!["User".parse().unwrap()]);
5565 assert!(schema.principals_for_action(&delete_user).is_none());
5566 }
5567
5568 #[test]
5569 fn resources_for_action() {
5570 let schema = schema();
5571 let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
5572 let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
5573 let create_list: EntityUID = r#"Action::"CreateList""#.parse().unwrap();
5574 let get_list: EntityUID = r#"Action::"GetList""#.parse().unwrap();
5575 let got = schema
5576 .resources_for_action(&delete_list)
5577 .unwrap()
5578 .cloned()
5579 .collect::<Vec<_>>();
5580 assert_eq!(got, vec!["List".parse().unwrap()]);
5581 let got = schema
5582 .resources_for_action(&create_list)
5583 .unwrap()
5584 .cloned()
5585 .collect::<Vec<_>>();
5586 assert_eq!(got, vec!["Application".parse().unwrap()]);
5587 let got = schema
5588 .resources_for_action(&get_list)
5589 .unwrap()
5590 .cloned()
5591 .collect::<HashSet<_>>();
5592 assert_eq!(
5593 got,
5594 HashSet::from(["List".parse().unwrap(), "CoolList".parse().unwrap()])
5595 );
5596 assert!(schema.principals_for_action(&delete_user).is_none());
5597 }
5598
5599 #[test]
5600 fn principal_parents() {
5601 let schema = schema();
5602 let user: EntityType = "User".parse().unwrap();
5603 let parents = schema
5604 .ancestors(&user)
5605 .unwrap()
5606 .cloned()
5607 .collect::<HashSet<_>>();
5608 let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
5609 assert_eq!(parents, expected);
5610 let parents = schema
5611 .ancestors(&"List".parse().unwrap())
5612 .unwrap()
5613 .cloned()
5614 .collect::<HashSet<_>>();
5615 let expected = HashSet::from(["Application".parse().unwrap()]);
5616 assert_eq!(parents, expected);
5617 assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
5618 let parents = schema
5619 .ancestors(&"CoolList".parse().unwrap())
5620 .unwrap()
5621 .cloned()
5622 .collect::<HashSet<_>>();
5623 let expected = HashSet::from([]);
5624 assert_eq!(parents, expected);
5625 }
5626
5627 #[test]
5628 fn action_groups() {
5629 let schema = schema();
5630 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5631 let expected = ["Read", "Write", "Create"]
5632 .into_iter()
5633 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5634 .collect::<HashSet<EntityUID>>();
5635 assert_eq!(groups, expected);
5636 }
5637
5638 #[test]
5639 fn actions() {
5640 let schema = schema();
5641 let actions = schema.actions().cloned().collect::<HashSet<_>>();
5642 let expected = [
5643 "Read",
5644 "Write",
5645 "Create",
5646 "DeleteList",
5647 "EditShare",
5648 "UpdateList",
5649 "CreateTask",
5650 "UpdateTask",
5651 "DeleteTask",
5652 "GetList",
5653 "GetLists",
5654 "CreateList",
5655 ]
5656 .into_iter()
5657 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5658 .collect::<HashSet<EntityUID>>();
5659 assert_eq!(actions, expected);
5660 }
5661
5662 #[test]
5663 fn entities() {
5664 let schema = schema();
5665 let entities = schema
5666 .entity_types()
5667 .map(ValidatorEntityType::name)
5668 .cloned()
5669 .collect::<HashSet<_>>();
5670 let expected = ["List", "Application", "User", "CoolList", "Team"]
5671 .into_iter()
5672 .map(|ty| ty.parse().unwrap())
5673 .collect::<HashSet<EntityType>>();
5674 assert_eq!(entities, expected);
5675 }
5676
5677 #[test]
5678 fn actions_for_principal_and_resource() {
5679 let schema = schema();
5680 let pty: EntityType = "User".parse().unwrap();
5681 let rty: EntityType = "Application".parse().unwrap();
5682 let actions = schema
5683 .actions_for_principal_and_resource(&pty, &rty)
5684 .cloned()
5685 .collect::<HashSet<EntityUID>>();
5686 let expected = ["GetLists", "CreateList"]
5687 .into_iter()
5688 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5689 .collect::<HashSet<EntityUID>>();
5690 assert_eq!(actions, expected);
5691 }
5692
5693 #[test]
5694 fn actions_for_principal_and_resource_applies_to_multiple() {
5695 let schema = schema();
5696 let pty: EntityType = "User".parse().unwrap();
5697 let rty: EntityType = "CoolList".parse().unwrap();
5698 let actions = schema
5699 .actions_for_principal_and_resource(&pty, &rty)
5700 .cloned()
5701 .collect::<HashSet<EntityUID>>();
5702 let expected = ["GetList"]
5703 .into_iter()
5704 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5705 .collect::<HashSet<EntityUID>>();
5706 assert_eq!(actions, expected);
5707 }
5708
5709 #[test]
5710 fn actions_for_principal_and_resource_empty() {
5711 let schema = schema();
5712 let pty: EntityType = "User".parse().unwrap();
5713 let rty: EntityType = "Team".parse().unwrap();
5714 let actions = schema
5715 .actions_for_principal_and_resource(&pty, &rty)
5716 .cloned()
5717 .collect::<HashSet<EntityUID>>();
5718 assert_eq!(actions, HashSet::new());
5719 }
5720}
5721
5722#[cfg(test)]
5723mod test_access_namespace {
5724 use super::*;
5725
5726 fn schema() -> ValidatorSchema {
5727 let src = r#"
5728 namespace Foo {
5729 type Task = {
5730 "id": Long,
5731 "name": String,
5732 "state": String,
5733};
5734
5735type Tasks = Set<Task>;
5736entity List in [Application] = {
5737 "editors": Team,
5738 "name": String,
5739 "owner": User,
5740 "readers": Team,
5741 "tasks": Tasks,
5742};
5743entity Application;
5744entity User in [Team, Application] = {
5745 "joblevel": Long,
5746 "location": String,
5747};
5748
5749entity CoolList;
5750
5751entity Team in [Team, Application];
5752
5753action Read, Write, Create;
5754
5755action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5756 principal: [User],
5757 resource : [List]
5758};
5759
5760action GetList in Read appliesTo {
5761 principal : [User],
5762 resource : [List, CoolList]
5763};
5764
5765action GetLists in Read appliesTo {
5766 principal : [User],
5767 resource : [Application]
5768};
5769
5770action CreateList in Create appliesTo {
5771 principal : [User],
5772 resource : [Application]
5773};
5774 }
5775
5776 "#;
5777
5778 src.parse().unwrap()
5779 }
5780
5781 #[test]
5782 fn principals() {
5783 let schema = schema();
5784 let principals = schema.principals().collect::<HashSet<_>>();
5785 assert_eq!(principals.len(), 1);
5786 let user: EntityType = "Foo::User".parse().unwrap();
5787 assert!(principals.contains(&user));
5788 let principals = schema.principals().collect::<Vec<_>>();
5789 assert!(principals.len() > 1);
5790 assert!(principals.iter().all(|ety| **ety == user));
5791 }
5792
5793 #[test]
5794 fn empty_schema_principals_and_resources() {
5795 let empty: ValidatorSchema = "".parse().unwrap();
5796 assert!(empty.principals().next().is_none());
5797 assert!(empty.resources().next().is_none());
5798 }
5799
5800 #[test]
5801 fn resources() {
5802 let schema = schema();
5803 let resources = schema.resources().cloned().collect::<HashSet<_>>();
5804 let expected: HashSet<EntityType> = HashSet::from([
5805 "Foo::List".parse().unwrap(),
5806 "Foo::Application".parse().unwrap(),
5807 "Foo::CoolList".parse().unwrap(),
5808 ]);
5809 assert_eq!(resources, expected);
5810 }
5811
5812 #[test]
5813 fn principals_for_action() {
5814 let schema = schema();
5815 let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5816 let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5817 let got = schema
5818 .principals_for_action(&delete_list)
5819 .unwrap()
5820 .cloned()
5821 .collect::<Vec<_>>();
5822 assert_eq!(got, vec!["Foo::User".parse().unwrap()]);
5823 assert!(schema.principals_for_action(&delete_user).is_none());
5824 }
5825
5826 #[test]
5827 fn resources_for_action() {
5828 let schema = schema();
5829 let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5830 let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5831 let create_list: EntityUID = r#"Foo::Action::"CreateList""#.parse().unwrap();
5832 let get_list: EntityUID = r#"Foo::Action::"GetList""#.parse().unwrap();
5833 let got = schema
5834 .resources_for_action(&delete_list)
5835 .unwrap()
5836 .cloned()
5837 .collect::<Vec<_>>();
5838 assert_eq!(got, vec!["Foo::List".parse().unwrap()]);
5839 let got = schema
5840 .resources_for_action(&create_list)
5841 .unwrap()
5842 .cloned()
5843 .collect::<Vec<_>>();
5844 assert_eq!(got, vec!["Foo::Application".parse().unwrap()]);
5845 let got = schema
5846 .resources_for_action(&get_list)
5847 .unwrap()
5848 .cloned()
5849 .collect::<HashSet<_>>();
5850 assert_eq!(
5851 got,
5852 HashSet::from([
5853 "Foo::List".parse().unwrap(),
5854 "Foo::CoolList".parse().unwrap()
5855 ])
5856 );
5857 assert!(schema.principals_for_action(&delete_user).is_none());
5858 }
5859
5860 #[test]
5861 fn principal_parents() {
5862 let schema = schema();
5863 let user: EntityType = "Foo::User".parse().unwrap();
5864 let parents = schema
5865 .ancestors(&user)
5866 .unwrap()
5867 .cloned()
5868 .collect::<HashSet<_>>();
5869 let expected = HashSet::from([
5870 "Foo::Team".parse().unwrap(),
5871 "Foo::Application".parse().unwrap(),
5872 ]);
5873 assert_eq!(parents, expected);
5874 let parents = schema
5875 .ancestors(&"Foo::List".parse().unwrap())
5876 .unwrap()
5877 .cloned()
5878 .collect::<HashSet<_>>();
5879 let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
5880 assert_eq!(parents, expected);
5881 assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
5882 let parents = schema
5883 .ancestors(&"Foo::CoolList".parse().unwrap())
5884 .unwrap()
5885 .cloned()
5886 .collect::<HashSet<_>>();
5887 let expected = HashSet::from([]);
5888 assert_eq!(parents, expected);
5889 }
5890
5891 #[test]
5892 fn action_groups() {
5893 let schema = schema();
5894 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5895 let expected = ["Read", "Write", "Create"]
5896 .into_iter()
5897 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5898 .collect::<HashSet<EntityUID>>();
5899 assert_eq!(groups, expected);
5900 }
5901
5902 #[test]
5903 fn actions() {
5904 let schema = schema();
5905 let actions = schema.actions().cloned().collect::<HashSet<_>>();
5906 let expected = [
5907 "Read",
5908 "Write",
5909 "Create",
5910 "DeleteList",
5911 "EditShare",
5912 "UpdateList",
5913 "CreateTask",
5914 "UpdateTask",
5915 "DeleteTask",
5916 "GetList",
5917 "GetLists",
5918 "CreateList",
5919 ]
5920 .into_iter()
5921 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5922 .collect::<HashSet<EntityUID>>();
5923 assert_eq!(actions, expected);
5924 }
5925
5926 #[test]
5927 fn entities() {
5928 let schema = schema();
5929 let entities = schema
5930 .entity_types()
5931 .map(ValidatorEntityType::name)
5932 .cloned()
5933 .collect::<HashSet<_>>();
5934 let expected = [
5935 "Foo::List",
5936 "Foo::Application",
5937 "Foo::User",
5938 "Foo::CoolList",
5939 "Foo::Team",
5940 ]
5941 .into_iter()
5942 .map(|ty| ty.parse().unwrap())
5943 .collect::<HashSet<EntityType>>();
5944 assert_eq!(entities, expected);
5945 }
5946
5947 #[test]
5948 fn actions_for_principal_and_resource() {
5949 let schema = schema();
5950 let pty: EntityType = "Foo::User".parse().unwrap();
5951 let rty: EntityType = "Foo::Application".parse().unwrap();
5952 let actions = schema
5953 .actions_for_principal_and_resource(&pty, &rty)
5954 .cloned()
5955 .collect::<HashSet<EntityUID>>();
5956 let expected = ["GetLists", "CreateList"]
5957 .into_iter()
5958 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5959 .collect::<HashSet<EntityUID>>();
5960 assert_eq!(actions, expected);
5961 }
5962}