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