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