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