1use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet};
24use std::str::FromStr;
25
26use cedar_policy_core::{
27 ast::{Entity, EntityType, EntityUID, InternalName, Name, UnreservedId},
28 entities::{err::EntitiesError, Entities, TCComputation},
29 extensions::Extensions,
30 transitive_closure::compute_tc,
31};
32use itertools::Itertools;
33use nonempty::NonEmpty;
34use serde::{Deserialize, Serialize};
35use serde_with::serde_as;
36use smol_str::ToSmolStr;
37
38use crate::{
39 cedar_schema::SchemaWarning,
40 err::schema_errors::*,
41 err::*,
42 json_schema,
43 types::{Attributes, EntityRecordKind, OpenTag, Type},
44};
45
46mod action;
47pub use action::ValidatorActionId;
48pub(crate) use action::ValidatorApplySpec;
49mod entity_type;
50pub use entity_type::ValidatorEntityType;
51mod namespace_def;
52use namespace_def::try_entity_attributes_into_validator_type;
53pub(crate) use namespace_def::try_jsonschema_type_into_validator_type;
54pub use namespace_def::ValidatorNamespaceDef;
55mod raw_name;
56pub use raw_name::{ConditionalName, RawName, ReferenceType};
57
58#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
60pub enum ActionBehavior {
61 #[default]
67 ProhibitAttributes,
68 PermitAttributes,
70}
71
72#[derive(Debug)]
75pub struct ValidatorSchemaFragment<N, A>(Vec<ValidatorNamespaceDef<N, A>>);
76
77impl TryInto<ValidatorSchemaFragment<ConditionalName, ConditionalName>>
78 for json_schema::Fragment<RawName>
79{
80 type Error = SchemaError;
81
82 fn try_into(self) -> Result<ValidatorSchemaFragment<ConditionalName, ConditionalName>> {
83 ValidatorSchemaFragment::from_schema_fragment(
84 self,
85 ActionBehavior::default(),
86 Extensions::all_available(),
87 )
88 }
89}
90
91impl<N, A> ValidatorSchemaFragment<N, A> {
92 pub fn from_namespaces(
94 namespaces: impl IntoIterator<Item = ValidatorNamespaceDef<N, A>>,
95 ) -> Self {
96 Self(namespaces.into_iter().collect())
97 }
98
99 pub fn namespaces(&self) -> impl Iterator<Item = Option<&InternalName>> {
103 self.0.iter().map(|d| d.namespace())
104 }
105}
106
107impl ValidatorSchemaFragment<ConditionalName, ConditionalName> {
108 pub fn from_schema_fragment(
110 fragment: json_schema::Fragment<RawName>,
111 action_behavior: ActionBehavior,
112 extensions: &Extensions<'_>,
113 ) -> Result<Self> {
114 Ok(Self(
115 fragment
116 .0
117 .into_iter()
118 .map(|(fragment_ns, ns_def)| {
119 ValidatorNamespaceDef::from_namespace_definition(
120 fragment_ns.map(Into::into),
121 ns_def,
122 action_behavior,
123 extensions,
124 )
125 })
126 .collect::<Result<Vec<_>>>()?,
127 ))
128 }
129
130 pub fn fully_qualify_type_references(
137 self,
138 all_defs: &AllDefs,
139 ) -> Result<ValidatorSchemaFragment<InternalName, EntityType>> {
140 let (nsdefs, errs) = self
141 .0
142 .into_iter()
143 .map(|ns_def| ns_def.fully_qualify_type_references(all_defs))
144 .partition_result::<Vec<ValidatorNamespaceDef<InternalName, EntityType>>, Vec<SchemaError>, _, _>();
145 if let Some(errs) = NonEmpty::from_vec(errs) {
146 Err(SchemaError::join_nonempty(errs))
147 } else {
148 Ok(ValidatorSchemaFragment(nsdefs))
149 }
150 }
151}
152
153#[serde_as]
158#[derive(Clone, Debug, Serialize)]
159#[serde(rename_all = "camelCase")]
160pub struct ValidatorSchema {
161 #[serde_as(as = "Vec<(_, _)>")]
163 entity_types: HashMap<EntityType, ValidatorEntityType>,
164
165 #[serde_as(as = "Vec<(_, _)>")]
167 action_ids: HashMap<EntityUID, ValidatorActionId>,
168}
169
170impl std::str::FromStr for ValidatorSchema {
173 type Err = CedarSchemaError;
174
175 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
176 Self::from_cedarschema_str(s, Extensions::all_available()).map(|(schema, _)| schema)
177 }
178}
179
180impl TryFrom<json_schema::NamespaceDefinition<RawName>> for ValidatorSchema {
181 type Error = SchemaError;
182
183 fn try_from(nsd: json_schema::NamespaceDefinition<RawName>) -> Result<ValidatorSchema> {
184 ValidatorSchema::from_schema_fragments(
185 [ValidatorSchemaFragment::from_namespaces([nsd.try_into()?])],
186 Extensions::all_available(),
187 )
188 }
189}
190
191impl TryFrom<json_schema::Fragment<RawName>> for ValidatorSchema {
192 type Error = SchemaError;
193
194 fn try_from(frag: json_schema::Fragment<RawName>) -> Result<ValidatorSchema> {
195 ValidatorSchema::from_schema_fragments([frag.try_into()?], Extensions::all_available())
196 }
197}
198
199impl ValidatorSchema {
200 pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
202 self.action_ids
203 .values()
204 .flat_map(ValidatorActionId::principals)
205 }
206
207 pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
209 self.action_ids
210 .values()
211 .flat_map(ValidatorActionId::resources)
212 }
213
214 pub fn principals_for_action(
220 &self,
221 action: &EntityUID,
222 ) -> Option<impl Iterator<Item = &EntityType>> {
223 self.action_ids
224 .get(action)
225 .map(ValidatorActionId::principals)
226 }
227
228 pub fn resources_for_action(
234 &self,
235 action: &EntityUID,
236 ) -> Option<impl Iterator<Item = &EntityType>> {
237 self.action_ids
238 .get(action)
239 .map(ValidatorActionId::resources)
240 }
241
242 pub fn ancestors<'a>(
248 &'a self,
249 ty: &'a EntityType,
250 ) -> Option<impl Iterator<Item = &EntityType> + 'a> {
251 if self.entity_types.contains_key(ty) {
252 Some(self.entity_types.values().filter_map(|ety| {
253 if ety.descendants.contains(ty) {
254 Some(&ety.name)
255 } else {
256 None
257 }
258 }))
259 } else {
260 None
261 }
262 }
263
264 pub fn action_groups(&self) -> impl Iterator<Item = &EntityUID> {
266 self.action_ids.values().filter_map(|action| {
267 if action.descendants.is_empty() {
268 None
269 } else {
270 Some(&action.name)
271 }
272 })
273 }
274
275 pub fn actions(&self) -> impl Iterator<Item = &EntityUID> {
277 self.action_ids.keys()
278 }
279
280 pub fn empty() -> ValidatorSchema {
283 Self {
284 entity_types: HashMap::new(),
285 action_ids: HashMap::new(),
286 }
287 }
288
289 pub fn from_json_value(json: serde_json::Value, extensions: &Extensions<'_>) -> Result<Self> {
292 Self::from_schema_frag(
293 json_schema::Fragment::<RawName>::from_json_value(json)?,
294 ActionBehavior::default(),
295 extensions,
296 )
297 }
298
299 pub fn from_json_str(json: &str, extensions: &Extensions<'_>) -> Result<Self> {
302 Self::from_schema_frag(
303 json_schema::Fragment::<RawName>::from_json_str(json)?,
304 ActionBehavior::default(),
305 extensions,
306 )
307 }
308
309 pub fn from_json_file(file: impl std::io::Read, extensions: &Extensions<'_>) -> Result<Self> {
312 Self::from_schema_frag(
313 json_schema::Fragment::<RawName>::from_json_file(file)?,
314 ActionBehavior::default(),
315 extensions,
316 )
317 }
318
319 pub fn from_cedarschema_file<'a>(
322 r: impl std::io::Read,
323 extensions: &'a Extensions<'a>,
324 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
325 {
326 let (fragment, warnings) = json_schema::Fragment::from_cedarschema_file(r, extensions)?;
327 let schema_and_warnings =
328 Self::from_schema_frag(fragment, ActionBehavior::default(), extensions)
329 .map(|schema| (schema, warnings))?;
330 Ok(schema_and_warnings)
331 }
332
333 pub fn from_cedarschema_str<'a>(
336 src: &str,
337 extensions: &Extensions<'a>,
338 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
339 {
340 let (fragment, warnings) = json_schema::Fragment::from_cedarschema_str(src, extensions)?;
341 let schema_and_warnings =
342 Self::from_schema_frag(fragment, ActionBehavior::default(), extensions)
343 .map(|schema| (schema, warnings))?;
344 Ok(schema_and_warnings)
345 }
346
347 pub(crate) fn from_schema_frag(
349 schema_file: json_schema::Fragment<RawName>,
350 action_behavior: ActionBehavior,
351 extensions: &Extensions<'_>,
352 ) -> Result<ValidatorSchema> {
353 Self::from_schema_fragments(
354 [ValidatorSchemaFragment::from_schema_fragment(
355 schema_file,
356 action_behavior,
357 extensions,
358 )?],
359 extensions,
360 )
361 }
362
363 pub fn from_schema_fragments(
365 fragments: impl IntoIterator<Item = ValidatorSchemaFragment<ConditionalName, ConditionalName>>,
366 extensions: &Extensions<'_>,
367 ) -> Result<ValidatorSchema> {
368 let mut fragments = fragments
369 .into_iter()
370 .chain(std::iter::once(cedar_fragment(extensions)))
373 .collect::<Vec<_>>();
374
375 let mut all_defs = AllDefs::new(|| fragments.iter());
378
379 all_defs.rfc_70_shadowing_checks()?;
388
389 for tyname in primitive_types::<Name>()
397 .map(|(id, _)| Name::unqualified_name(id))
398 .chain(extensions.ext_types().cloned().map(Into::into))
399 {
400 if !all_defs.is_defined_as_entity(tyname.as_ref())
401 && !all_defs.is_defined_as_common(tyname.as_ref())
402 {
403 assert!(
404 tyname.is_unqualified(),
405 "expected all primitive and extension type names to be unqualified"
406 );
407 fragments.push(single_alias_in_empty_namespace(
408 tyname.basename().clone(),
409 tyname.as_ref().qualify_with(Some(&InternalName::__cedar())),
410 ));
411 all_defs.mark_as_defined_as_common_type(tyname.into());
412 }
413 }
414
415 let (fragments, errs) = fragments
423 .into_iter()
424 .map(|frag| frag.fully_qualify_type_references(&all_defs))
425 .partition_result::<Vec<ValidatorSchemaFragment<InternalName, EntityType>>, Vec<SchemaError>, _, _>();
426 if let Some(errs) = NonEmpty::from_vec(errs) {
427 return Err(SchemaError::join_nonempty(errs));
428 }
429
430 let mut common_types = HashMap::new();
436 let mut entity_type_fragments: HashMap<EntityType, _> = HashMap::new();
437 let mut action_fragments = HashMap::new();
438 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
439 for (name, ty) in ns_def.common_types.defs {
440 match common_types.entry(name) {
441 Entry::Vacant(v) => v.insert(ty),
442 Entry::Occupied(o) => {
443 return Err(DuplicateCommonTypeError(o.key().clone()).into());
444 }
445 };
446 }
447
448 for (name, entity_type) in ns_def.entity_types.defs {
449 match entity_type_fragments.entry(name) {
450 Entry::Vacant(v) => v.insert(entity_type),
451 Entry::Occupied(o) => {
452 return Err(DuplicateEntityTypeError(o.key().clone()).into())
453 }
454 };
455 }
456
457 for (action_euid, action) in ns_def.actions.actions {
458 match action_fragments.entry(action_euid) {
459 Entry::Vacant(v) => v.insert(action),
460 Entry::Occupied(o) => {
461 return Err(DuplicateActionError(o.key().to_smolstr()).into())
462 }
463 };
464 }
465 }
466
467 let resolver = CommonTypeResolver::new(&common_types);
468 let common_types = resolver.resolve(extensions)?;
469
470 let mut entity_children: HashMap<EntityType, HashSet<EntityType>> = HashMap::new();
473 for (name, entity_type) in entity_type_fragments.iter() {
474 for parent in entity_type.parents.iter() {
475 entity_children
476 .entry(internal_name_to_entity_type(parent.clone())?)
477 .or_default()
478 .insert(name.clone());
479 }
480 }
481
482 let mut entity_types = entity_type_fragments
483 .into_iter()
484 .map(|(name, entity_type)| -> Result<_> {
485 let descendants = entity_children.remove(&name).unwrap_or_default();
495 let (attributes, open_attributes) = {
496 let unresolved = try_entity_attributes_into_validator_type(
497 entity_type.attributes,
498 extensions,
499 )?;
500 Self::record_attributes_or_none(
501 unresolved.resolve_common_type_refs(&common_types)?,
502 )
503 .ok_or(ContextOrShapeNotRecordError(
504 ContextOrShape::EntityTypeShape(name.clone()),
505 ))?
506 };
507 Ok((
508 name.clone(),
509 ValidatorEntityType {
510 name,
511 descendants,
512 attributes,
513 open_attributes,
514 },
515 ))
516 })
517 .collect::<Result<HashMap<_, _>>>()?;
518
519 let mut action_children = HashMap::new();
520 for (euid, action) in action_fragments.iter() {
521 for parent in action.parents.iter() {
522 action_children
523 .entry(parent.clone().try_into()?)
524 .or_insert_with(HashSet::new)
525 .insert(euid.clone());
526 }
527 }
528 let mut action_ids = action_fragments
529 .into_iter()
530 .map(|(name, action)| -> Result<_> {
531 let descendants = action_children.remove(&name).unwrap_or_default();
532 let (context, open_context_attributes) = {
533 let unresolved =
534 try_jsonschema_type_into_validator_type(action.context, extensions)?;
535 Self::record_attributes_or_none(
536 unresolved.resolve_common_type_refs(&common_types)?,
537 )
538 .ok_or(ContextOrShapeNotRecordError(
539 ContextOrShape::ActionContext(name.clone()),
540 ))?
541 };
542 Ok((
543 name.clone(),
544 ValidatorActionId {
545 name,
546 applies_to: action.applies_to,
547 descendants,
548 context: Type::record_with_attributes(
549 context.attrs,
550 open_context_attributes,
551 ),
552 attribute_types: action.attribute_types,
553 attributes: action.attributes,
554 },
555 ))
556 })
557 .collect::<Result<HashMap<_, _>>>()?;
558
559 compute_tc(&mut entity_types, false)
562 .map_err(|e| EntityTypeTransitiveClosureError::from(Box::new(e)))?;
563 compute_tc(&mut action_ids, true)?;
566
567 Self::check_for_undeclared(
574 &entity_types,
575 entity_children.into_keys(),
576 &action_ids,
577 action_children.into_keys(),
578 common_types.into_values(),
579 )?;
580
581 Ok(ValidatorSchema {
582 entity_types,
583 action_ids,
584 })
585 }
586
587 fn check_for_undeclared(
592 entity_types: &HashMap<EntityType, ValidatorEntityType>,
593 undeclared_parent_entities: impl IntoIterator<Item = EntityType>,
594 action_ids: &HashMap<EntityUID, ValidatorActionId>,
595 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
596 common_types: impl IntoIterator<Item = Type>,
597 ) -> Result<()> {
598 let mut undeclared_e = undeclared_parent_entities
603 .into_iter()
604 .collect::<BTreeSet<EntityType>>();
605 for entity_type in entity_types.values() {
611 for (_, attr_typ) in entity_type.attributes() {
612 Self::check_undeclared_in_type(
613 &attr_typ.attr_type,
614 entity_types,
615 &mut undeclared_e,
616 );
617 }
618 }
619
620 for common_type in common_types {
622 Self::check_undeclared_in_type(&common_type, entity_types, &mut undeclared_e);
623 }
624
625 let undeclared_a = undeclared_parent_actions.into_iter();
627 for action in action_ids.values() {
631 Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
632
633 for p_entity in action.applies_to_principals() {
634 if !entity_types.contains_key(p_entity) {
635 undeclared_e.insert(p_entity.clone());
636 }
637 }
638
639 for r_entity in action.applies_to_resources() {
640 if !entity_types.contains_key(r_entity) {
641 undeclared_e.insert(r_entity.clone());
642 }
643 }
644 }
645 if !undeclared_e.is_empty() {
646 return Err(UndeclaredEntityTypesError(undeclared_e).into());
647 }
648 if let Some(euids) = NonEmpty::collect(undeclared_a) {
649 return Err(ActionInvariantViolationError { euids }.into());
652 }
653
654 Ok(())
655 }
656
657 fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
658 match ty {
659 Type::EntityOrRecord(EntityRecordKind::Record {
660 attrs,
661 open_attributes,
662 }) => Some((attrs, open_attributes)),
663 _ => None,
664 }
665 }
666
667 fn check_undeclared_in_type(
671 ty: &Type,
672 entity_types: &HashMap<EntityType, ValidatorEntityType>,
673 undeclared_types: &mut BTreeSet<EntityType>,
674 ) {
675 match ty {
676 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
677 for name in lub.iter() {
678 if !entity_types.contains_key(name) {
679 undeclared_types.insert(name.clone());
680 }
681 }
682 }
683
684 Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
685 for (_, attr_ty) in attrs.iter() {
686 Self::check_undeclared_in_type(
687 &attr_ty.attr_type,
688 entity_types,
689 undeclared_types,
690 );
691 }
692 }
693
694 Type::Set {
695 element_type: Some(element_type),
696 } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
697
698 _ => (),
699 }
700 }
701
702 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
704 self.action_ids.get(action_id)
705 }
706
707 pub fn get_entity_type<'a>(
709 &'a self,
710 entity_type_id: &EntityType,
711 ) -> Option<&'a ValidatorEntityType> {
712 self.entity_types.get(entity_type_id)
713 }
714
715 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
717 self.action_ids.contains_key(action_id)
718 }
719
720 pub(crate) fn is_known_entity_type(&self, entity_type: &EntityType) -> bool {
722 entity_type.is_action() || self.entity_types.contains_key(entity_type)
723 }
724
725 pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
727 self.is_known_entity_type(euid.entity_type())
728 }
729
730 pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
732 self.action_ids.keys()
733 }
734
735 pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &EntityType> {
737 self.entity_types.keys()
738 }
739
740 pub fn entity_types(&self) -> impl Iterator<Item = (&EntityType, &ValidatorEntityType)> {
742 self.entity_types.iter()
743 }
744
745 pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&EntityType> {
751 let mut descendants = self
752 .get_entity_type(entity.entity_type())
753 .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
754 .unwrap_or_default();
755 descendants.push(entity.entity_type());
756 descendants
757 }
758
759 pub(crate) fn get_entity_types_in_set<'a>(
763 &'a self,
764 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
765 ) -> impl Iterator<Item = &EntityType> {
766 euids.into_iter().flat_map(|e| self.get_entity_types_in(e))
767 }
768
769 pub(crate) fn get_actions_in_set<'a>(
773 &'a self,
774 euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
775 ) -> Option<Vec<&'a EntityUID>> {
776 euids
777 .into_iter()
778 .map(|e| {
779 self.get_action_id(e).map(|action| {
780 action
781 .descendants
782 .iter()
783 .chain(std::iter::once(&action.name))
784 })
785 })
786 .collect::<Option<Vec<_>>>()
787 .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
788 }
789
790 pub fn context_type(&self, action: &EntityUID) -> Option<&Type> {
795 self.get_action_id(action)
798 .map(ValidatorActionId::context_type)
799 }
800
801 pub(crate) fn action_entities_iter(
804 &self,
805 ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
806 let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
812 for (action_euid, action_def) in &self.action_ids {
813 for descendant in &action_def.descendants {
814 action_ancestors
815 .entry(descendant)
816 .or_default()
817 .insert(action_euid.clone());
818 }
819 }
820 self.action_ids.iter().map(move |(action_id, action)| {
821 Entity::new_with_attr_partial_value_serialized_as_expr(
822 action_id.clone(),
823 action.attributes.clone(),
824 action_ancestors.remove(action_id).unwrap_or_default(),
825 )
826 })
827 }
828
829 pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
831 let extensions = Extensions::all_available();
832 Entities::from_entities(
833 self.action_entities_iter(),
834 None::<&cedar_policy_core::entities::NoEntitiesSchema>, TCComputation::AssumeAlreadyComputed,
836 extensions,
837 )
838 .map_err(Into::into)
839 }
840}
841
842#[derive(Debug, Clone, Deserialize)]
845#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
846#[serde(transparent)]
847pub(crate) struct NamespaceDefinitionWithActionAttributes<N>(
848 pub(crate) json_schema::NamespaceDefinition<N>,
849);
850
851impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes<RawName> {
852 type Error = SchemaError;
853
854 fn try_into(self) -> Result<ValidatorSchema> {
855 ValidatorSchema::from_schema_fragments(
856 [ValidatorSchemaFragment::from_namespaces([
857 ValidatorNamespaceDef::from_namespace_definition(
858 None,
859 self.0,
860 crate::ActionBehavior::PermitAttributes,
861 Extensions::all_available(),
862 )?,
863 ])],
864 Extensions::all_available(),
865 )
866 }
867}
868
869fn cedar_fragment(
872 extensions: &Extensions<'_>,
873) -> ValidatorSchemaFragment<ConditionalName, ConditionalName> {
874 #[allow(clippy::unwrap_used)]
876 let mut common_types = HashMap::from_iter(primitive_types());
877 for ext_type in extensions.ext_types() {
878 assert!(
879 ext_type.is_unqualified(),
880 "expected extension type names to be unqualified"
881 );
882 let ext_type = ext_type.basename().clone();
883 common_types.insert(
884 ext_type.clone(),
885 json_schema::Type::Type(json_schema::TypeVariant::Extension { name: ext_type }),
886 );
887 }
888
889 #[allow(clippy::unwrap_used)]
891 ValidatorSchemaFragment(vec![ValidatorNamespaceDef::from_common_type_defs(
892 Some(InternalName::__cedar()),
893 common_types,
894 )
895 .unwrap()])
896}
897
898fn single_alias_in_empty_namespace(
906 id: UnreservedId,
907 def: InternalName,
908) -> ValidatorSchemaFragment<ConditionalName, ConditionalName> {
909 ValidatorSchemaFragment(vec![ValidatorNamespaceDef::from_common_type_def(
910 None,
911 (
912 id,
913 json_schema::Type::Type(json_schema::TypeVariant::EntityOrCommon {
914 type_name: ConditionalName::unconditional(def, ReferenceType::CommonOrEntity),
915 }),
916 ),
917 )])
918}
919
920fn primitive_types<N>() -> impl Iterator<Item = (UnreservedId, json_schema::Type<N>)> {
923 #[allow(clippy::unwrap_used)]
925 [
926 (
927 UnreservedId::from_str("Bool").unwrap(),
928 json_schema::Type::Type(json_schema::TypeVariant::Boolean),
929 ),
930 (
931 UnreservedId::from_str("Long").unwrap(),
932 json_schema::Type::Type(json_schema::TypeVariant::Long),
933 ),
934 (
935 UnreservedId::from_str("String").unwrap(),
936 json_schema::Type::Type(json_schema::TypeVariant::String),
937 ),
938 ]
939 .into_iter()
940}
941
942fn internal_name_to_entity_type(
947 name: InternalName,
948) -> std::result::Result<EntityType, cedar_policy_core::ast::ReservedNameError> {
949 Name::try_from(name).map(Into::into)
950}
951
952#[derive(Debug)]
955pub struct AllDefs {
956 entity_defs: HashSet<InternalName>,
958 common_defs: HashSet<InternalName>,
960 action_defs: HashSet<EntityUID>,
962}
963
964impl AllDefs {
965 pub fn new<'a, N: 'a, A: 'a, I>(fragments: impl Fn() -> I) -> Self
968 where
969 I: Iterator<Item = &'a ValidatorSchemaFragment<N, A>>,
970 {
971 Self {
972 entity_defs: fragments()
973 .flat_map(|f| f.0.iter())
974 .flat_map(|ns_def| ns_def.all_declared_entity_type_names().cloned())
975 .collect(),
976 common_defs: fragments()
977 .flat_map(|f| f.0.iter())
978 .flat_map(|ns_def| ns_def.all_declared_common_type_names().cloned())
979 .collect(),
980 action_defs: fragments()
981 .flat_map(|f| f.0.iter())
982 .flat_map(|ns_def| ns_def.all_declared_action_names().cloned())
983 .collect(),
984 }
985 }
986
987 pub fn single_fragment<N, A>(fragment: &ValidatorSchemaFragment<N, A>) -> Self {
992 Self::new(|| std::iter::once(fragment))
993 }
994
995 pub fn is_defined_as_entity(&self, name: &InternalName) -> bool {
998 self.entity_defs.contains(name)
999 }
1000
1001 pub fn is_defined_as_common(&self, name: &InternalName) -> bool {
1004 self.common_defs.contains(name)
1005 }
1006
1007 pub fn is_defined_as_action(&self, euid: &EntityUID) -> bool {
1010 self.action_defs.contains(euid)
1011 }
1012
1013 pub fn mark_as_defined_as_common_type(&mut self, name: InternalName) {
1015 self.common_defs.insert(name);
1016 }
1017
1018 pub fn rfc_70_shadowing_checks(&self) -> Result<()> {
1027 for unqualified_name in self
1028 .entity_and_common_names()
1029 .filter(|name| name.is_unqualified())
1030 {
1031 if let Some(name) = self.entity_and_common_names().find(|name| {
1033 !name.is_unqualified() && !name.is_reserved() && name.basename() == unqualified_name.basename()
1036 }) {
1037 return Err(TypeShadowingError {
1038 shadowed_def: unqualified_name.clone(),
1039 shadowing_def: name.clone(),
1040 }
1041 .into());
1042 }
1043 }
1044 for unqualified_action in self
1045 .action_defs
1046 .iter()
1047 .filter(|euid| euid.entity_type().as_ref().is_unqualified())
1048 {
1049 if let Some(action) = self.action_defs.iter().find(|euid| {
1051 !euid.entity_type().as_ref().is_unqualified() && euid.eid() == unqualified_action.eid()
1054 }) {
1055 return Err(ActionShadowingError {
1056 shadowed_def: unqualified_action.clone(),
1057 shadowing_def: action.clone(),
1058 }
1059 .into());
1060 }
1061 }
1062 Ok(())
1063 }
1064
1065 fn entity_and_common_names(&self) -> impl Iterator<Item = &InternalName> {
1068 self.entity_defs.iter().chain(self.common_defs.iter())
1069 }
1070
1071 #[cfg(test)]
1075 pub(crate) fn from_entity_defs(names: impl IntoIterator<Item = InternalName>) -> Self {
1076 Self {
1077 entity_defs: names.into_iter().collect(),
1078 common_defs: HashSet::new(),
1079 action_defs: HashSet::new(),
1080 }
1081 }
1082}
1083
1084#[derive(Debug)]
1095struct CommonTypeResolver<'a> {
1096 defs: &'a HashMap<InternalName, json_schema::Type<InternalName>>,
1105 graph: HashMap<&'a InternalName, HashSet<&'a InternalName>>,
1113}
1114
1115impl<'a> CommonTypeResolver<'a> {
1116 fn new(defs: &'a HashMap<InternalName, json_schema::Type<InternalName>>) -> Self {
1127 let mut graph = HashMap::new();
1128 for (name, ty) in defs {
1129 graph.insert(name, HashSet::from_iter(ty.common_type_references()));
1130 }
1131 Self { defs, graph }
1132 }
1133
1134 fn topo_sort(&self) -> std::result::Result<Vec<&'a InternalName>, InternalName> {
1145 let mut indegrees: HashMap<&InternalName, usize> = HashMap::new();
1149 for (ty_name, deps) in self.graph.iter() {
1150 indegrees.entry(ty_name).or_insert(0);
1152 for dep in deps {
1153 match indegrees.entry(dep) {
1154 std::collections::hash_map::Entry::Occupied(mut o) => {
1155 o.insert(o.get() + 1);
1156 }
1157 std::collections::hash_map::Entry::Vacant(v) => {
1158 v.insert(1);
1159 }
1160 }
1161 }
1162 }
1163
1164 let mut work_set: HashSet<&'a InternalName> = HashSet::new();
1166 let mut res: Vec<&'a InternalName> = Vec::new();
1167
1168 for (name, degree) in indegrees.iter() {
1170 let name = *name;
1171 if *degree == 0 {
1172 work_set.insert(name);
1173 if self.graph.contains_key(name) {
1175 res.push(name);
1176 }
1177 }
1178 }
1179
1180 while let Some(name) = work_set.iter().next().cloned() {
1182 work_set.remove(name);
1183 if let Some(deps) = self.graph.get(name) {
1184 for dep in deps {
1185 if let Some(degree) = indegrees.get_mut(dep) {
1186 *degree -= 1;
1196 if *degree == 0 {
1197 work_set.insert(dep);
1198 if self.graph.contains_key(dep) {
1199 res.push(dep);
1200 }
1201 }
1202 }
1203 }
1204 }
1205 }
1206
1207 let mut set: HashSet<&InternalName> = HashSet::from_iter(self.graph.keys().cloned());
1210 for name in res.iter() {
1211 set.remove(name);
1212 }
1213
1214 if let Some(cycle) = set.into_iter().next() {
1215 Err(cycle.clone())
1216 } else {
1217 res.reverse();
1220 Ok(res)
1221 }
1222 }
1223
1224 fn resolve_type(
1226 resolve_table: &HashMap<&InternalName, json_schema::Type<InternalName>>,
1227 ty: json_schema::Type<InternalName>,
1228 ) -> Result<json_schema::Type<InternalName>> {
1229 match ty {
1230 json_schema::Type::CommonTypeRef { type_name } => resolve_table
1231 .get(&type_name)
1232 .ok_or(CommonTypeInvariantViolationError { name: type_name }.into())
1233 .cloned(),
1234 json_schema::Type::Type(json_schema::TypeVariant::EntityOrCommon { type_name }) => {
1235 match resolve_table.get(&type_name) {
1236 Some(def) => Ok(def.clone()),
1237 None => Ok(json_schema::Type::Type(json_schema::TypeVariant::Entity {
1238 name: type_name,
1239 })),
1240 }
1241 }
1242 json_schema::Type::Type(json_schema::TypeVariant::Set { element }) => {
1243 Ok(json_schema::Type::Type(json_schema::TypeVariant::Set {
1244 element: Box::new(Self::resolve_type(resolve_table, *element)?),
1245 }))
1246 }
1247 json_schema::Type::Type(json_schema::TypeVariant::Record(
1248 json_schema::RecordType {
1249 attributes,
1250 additional_attributes,
1251 },
1252 )) => Ok(json_schema::Type::Type(json_schema::TypeVariant::Record(
1253 json_schema::RecordType {
1254 attributes: BTreeMap::from_iter(
1255 attributes
1256 .into_iter()
1257 .map(|(attr, attr_ty)| {
1258 Ok((
1259 attr,
1260 json_schema::RecordAttributeType {
1261 required: attr_ty.required,
1262 ty: Self::resolve_type(resolve_table, attr_ty.ty)?,
1263 },
1264 ))
1265 })
1266 .collect::<Result<Vec<(_, _)>>>()?,
1267 ),
1268 additional_attributes,
1269 },
1270 ))),
1271 _ => Ok(ty),
1272 }
1273 }
1274
1275 fn resolve(&self, extensions: &Extensions<'_>) -> Result<HashMap<&'a InternalName, Type>> {
1278 let sorted_names = self.topo_sort().map_err(|n| {
1279 SchemaError::CycleInCommonTypeReferences(CycleInCommonTypeReferencesError(n))
1280 })?;
1281
1282 let mut resolve_table: HashMap<&InternalName, json_schema::Type<InternalName>> =
1283 HashMap::new();
1284 let mut tys: HashMap<&'a InternalName, Type> = HashMap::new();
1285
1286 for &name in sorted_names.iter() {
1287 #[allow(clippy::unwrap_used)]
1289 let ty = self.defs.get(name).unwrap();
1290 let substituted_ty = Self::resolve_type(&resolve_table, ty.clone())?;
1291 resolve_table.insert(name, substituted_ty.clone());
1292 tys.insert(
1293 name,
1294 try_jsonschema_type_into_validator_type(substituted_ty, extensions)?
1295 .resolve_common_type_refs(&HashMap::new())?,
1296 );
1297 }
1298
1299 Ok(tys)
1300 }
1301}
1302
1303#[allow(clippy::panic)]
1305#[allow(clippy::indexing_slicing)]
1307#[cfg(test)]
1308pub(crate) mod test {
1309 use std::{
1310 collections::{BTreeMap, HashSet},
1311 str::FromStr,
1312 };
1313
1314 use crate::json_schema;
1315 use crate::types::Type;
1316
1317 use cedar_policy_core::ast::RestrictedExpr;
1318 use cedar_policy_core::test_utils::{expect_err, ExpectedErrorMessageBuilder};
1319 use cool_asserts::assert_matches;
1320
1321 use serde_json::json;
1322
1323 use super::*;
1324
1325 pub fn collect_warnings<A, B, E>(
1330 r: std::result::Result<(A, impl Iterator<Item = B>), E>,
1331 ) -> std::result::Result<(A, Vec<B>), E> {
1332 r.map(|(a, iter)| (a, iter.collect()))
1333 }
1334
1335 #[test]
1337 fn test_from_schema_file() {
1338 let src = json!(
1339 {
1340 "entityTypes": {
1341 "User": {
1342 "memberOfTypes": [ "Group" ]
1343 },
1344 "Group": {
1345 "memberOfTypes": []
1346 },
1347 "Photo": {
1348 "memberOfTypes": [ "Album" ]
1349 },
1350 "Album": {
1351 "memberOfTypes": []
1352 }
1353 },
1354 "actions": {
1355 "view_photo": {
1356 "appliesTo": {
1357 "principalTypes": ["User", "Group"],
1358 "resourceTypes": ["Photo"]
1359 }
1360 }
1361 }
1362 });
1363 let schema_file: json_schema::NamespaceDefinition<RawName> =
1364 serde_json::from_value(src).unwrap();
1365 let schema: Result<ValidatorSchema> = schema_file.try_into();
1366 assert!(schema.is_ok());
1367 }
1368
1369 #[test]
1371 fn test_from_schema_file_duplicate_entity() {
1372 let src = r#"
1375 {"": {
1376 "entityTypes": {
1377 "User": {
1378 "memberOfTypes": [ "Group" ]
1379 },
1380 "Group": {
1381 "memberOfTypes": []
1382 },
1383 "Photo": {
1384 "memberOfTypes": [ "Album" ]
1385 },
1386 "Photo": {
1387 "memberOfTypes": []
1388 }
1389 },
1390 "actions": {
1391 "view_photo": {
1392 "memberOf": [],
1393 "appliesTo": {
1394 "principalTypes": ["User", "Group"],
1395 "resourceTypes": ["Photo"]
1396 }
1397 }
1398 }
1399 }}"#;
1400
1401 match ValidatorSchema::from_json_str(src, Extensions::all_available()) {
1402 Err(SchemaError::JsonDeserialization(_)) => (),
1403 _ => panic!("Expected JSON deserialization error due to duplicate entity type."),
1404 }
1405 }
1406
1407 #[test]
1409 fn test_from_schema_file_duplicate_action() {
1410 let src = r#"
1413 {"": {
1414 "entityTypes": {
1415 "User": {
1416 "memberOfTypes": [ "Group" ]
1417 },
1418 "Group": {
1419 "memberOfTypes": []
1420 },
1421 "Photo": {
1422 "memberOfTypes": []
1423 }
1424 },
1425 "actions": {
1426 "view_photo": {
1427 "memberOf": [],
1428 "appliesTo": {
1429 "principalTypes": ["User", "Group"],
1430 "resourceTypes": ["Photo"]
1431 }
1432 },
1433 "view_photo": { }
1434 }
1435 }"#;
1436 match ValidatorSchema::from_json_str(src, Extensions::all_available()) {
1437 Err(SchemaError::JsonDeserialization(_)) => (),
1438 _ => panic!("Expected JSON deserialization error due to duplicate action type."),
1439 }
1440 }
1441
1442 #[test]
1444 fn test_from_schema_file_undefined_entities() {
1445 let src = json!(
1446 {
1447 "entityTypes": {
1448 "User": {
1449 "memberOfTypes": [ "Grop" ]
1450 },
1451 "Group": {
1452 "memberOfTypes": []
1453 },
1454 "Photo": {
1455 "memberOfTypes": []
1456 }
1457 },
1458 "actions": {
1459 "view_photo": {
1460 "appliesTo": {
1461 "principalTypes": ["Usr", "Group"],
1462 "resourceTypes": ["Phoot"]
1463 }
1464 }
1465 }
1466 });
1467 let schema_file: json_schema::NamespaceDefinition<RawName> =
1468 serde_json::from_value(src.clone()).unwrap();
1469 let schema: Result<ValidatorSchema> = schema_file.try_into();
1470 assert_matches!(schema, Err(e) => {
1471 expect_err(
1472 &src,
1473 &miette::Report::new(e),
1474 &ExpectedErrorMessageBuilder::error(r#"failed to resolve types: Grop, Usr, Phoot"#)
1475 .help("`Grop` has not been declared as an entity type")
1476 .build());
1477 });
1478 }
1479
1480 #[test]
1481 fn undefined_entity_namespace_member_of() {
1482 let src = json!(
1483 {"Foo": {
1484 "entityTypes": {
1485 "User": {
1486 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1487 },
1488 "Group": { }
1489 },
1490 "actions": {}
1491 }});
1492 let schema_file = json_schema::Fragment::from_json_value(src.clone()).unwrap();
1493 let schema: Result<ValidatorSchema> = schema_file.try_into();
1494 assert_matches!(schema, Err(e) => {
1495 expect_err(
1496 &src,
1497 &miette::Report::new(e),
1498 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: Bar::Group"#)
1499 .help("`Bar::Group` has not been declared as an entity type")
1500 .build());
1501 });
1502 }
1503
1504 #[test]
1505 fn undefined_entity_namespace_applies_to() {
1506 let src = json!(
1507 {"Foo": {
1508 "entityTypes": { "User": { }, "Photo": { } },
1509 "actions": {
1510 "view_photo": {
1511 "appliesTo": {
1512 "principalTypes": ["Foo::User", "Bar::User"],
1513 "resourceTypes": ["Photo", "Bar::Photo"],
1514 }
1515 }
1516 }
1517 }});
1518 let schema_file = json_schema::Fragment::from_json_value(src.clone()).unwrap();
1519 let schema: Result<ValidatorSchema> = schema_file.try_into();
1520 assert_matches!(schema, Err(e) => {
1521 expect_err(
1522 &src,
1523 &miette::Report::new(e),
1524 &ExpectedErrorMessageBuilder::error(r#"failed to resolve types: Bar::User, Bar::Photo"#)
1525 .help("`Bar::User` has not been declared as an entity type")
1526 .build());
1527 });
1528 }
1529
1530 #[test]
1532 fn test_from_schema_file_undefined_action() {
1533 let src = json!(
1534 {
1535 "entityTypes": {
1536 "User": {
1537 "memberOfTypes": [ "Group" ]
1538 },
1539 "Group": {
1540 "memberOfTypes": []
1541 },
1542 "Photo": {
1543 "memberOfTypes": []
1544 }
1545 },
1546 "actions": {
1547 "view_photo": {
1548 "memberOf": [ {"id": "photo_action"} ],
1549 "appliesTo": {
1550 "principalTypes": ["User", "Group"],
1551 "resourceTypes": ["Photo"]
1552 }
1553 }
1554 }
1555 });
1556 let schema_file: json_schema::NamespaceDefinition<RawName> =
1557 serde_json::from_value(src.clone()).unwrap();
1558 let schema: Result<ValidatorSchema> = schema_file.try_into();
1559 assert_matches!(schema, Err(e) => {
1560 expect_err(
1561 &src,
1562 &miette::Report::new(e),
1563 &ExpectedErrorMessageBuilder::error(r#"undeclared action: Action::"photo_action""#)
1564 .help("any actions appearing as parents need to be declared as actions")
1565 .build());
1566 });
1567 }
1568
1569 #[test]
1572 fn test_from_schema_file_action_cycle1() {
1573 let src = json!(
1574 {
1575 "entityTypes": {},
1576 "actions": {
1577 "view_photo": {
1578 "memberOf": [ {"id": "view_photo"} ]
1579 }
1580 }
1581 });
1582 let schema_file: json_schema::NamespaceDefinition<RawName> =
1583 serde_json::from_value(src).unwrap();
1584 let schema: Result<ValidatorSchema> = schema_file.try_into();
1585 assert_matches!(
1586 schema,
1587 Err(SchemaError::CycleInActionHierarchy(CycleInActionHierarchyError(euid))) => {
1588 assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
1589 }
1590 )
1591 }
1592
1593 #[test]
1596 fn test_from_schema_file_action_cycle2() {
1597 let src = json!(
1598 {
1599 "entityTypes": {},
1600 "actions": {
1601 "view_photo": {
1602 "memberOf": [ {"id": "edit_photo"} ]
1603 },
1604 "edit_photo": {
1605 "memberOf": [ {"id": "delete_photo"} ]
1606 },
1607 "delete_photo": {
1608 "memberOf": [ {"id": "view_photo"} ]
1609 },
1610 "other_action": {
1611 "memberOf": [ {"id": "edit_photo"} ]
1612 }
1613 }
1614 });
1615 let schema_file: json_schema::NamespaceDefinition<RawName> =
1616 serde_json::from_value(src).unwrap();
1617 let schema: Result<ValidatorSchema> = schema_file.try_into();
1618 assert_matches!(
1619 schema,
1620 Err(SchemaError::CycleInActionHierarchy(_)),
1622 )
1623 }
1624
1625 #[test]
1626 fn namespaced_schema() {
1627 let src = r#"
1628 { "N::S": {
1629 "entityTypes": {
1630 "User": {},
1631 "Photo": {}
1632 },
1633 "actions": {
1634 "view_photo": {
1635 "appliesTo": {
1636 "principalTypes": ["User"],
1637 "resourceTypes": ["Photo"]
1638 }
1639 }
1640 }
1641 } }
1642 "#;
1643 let schema_file = json_schema::Fragment::from_json_str(src).unwrap();
1644 let schema: ValidatorSchema = schema_file
1645 .try_into()
1646 .expect("Namespaced schema failed to convert.");
1647 dbg!(&schema);
1648 let user_entity_type = &"N::S::User"
1649 .parse()
1650 .expect("Namespaced entity type should have parsed");
1651 let photo_entity_type = &"N::S::Photo"
1652 .parse()
1653 .expect("Namespaced entity type should have parsed");
1654 assert!(
1655 schema.entity_types.contains_key(user_entity_type),
1656 "Expected and entity type User."
1657 );
1658 assert!(
1659 schema.entity_types.contains_key(photo_entity_type),
1660 "Expected an entity type Photo."
1661 );
1662 assert_eq!(
1663 schema.entity_types.len(),
1664 2,
1665 "Expected exactly 2 entity types."
1666 );
1667 assert!(
1668 schema.action_ids.contains_key(
1669 &"N::S::Action::\"view_photo\""
1670 .parse()
1671 .expect("Namespaced action should have parsed")
1672 ),
1673 "Expected an action \"view_photo\"."
1674 );
1675 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1676
1677 let action = &schema.action_ids.values().next().expect("Expected Action");
1678 assert_eq!(
1679 action.applies_to_principals().collect::<Vec<_>>(),
1680 vec![user_entity_type]
1681 );
1682 assert_eq!(
1683 action.applies_to_resources().collect::<Vec<_>>(),
1684 vec![photo_entity_type]
1685 );
1686 }
1687
1688 #[test]
1689 fn cant_use_namespace_in_entity_type() {
1690 let src = r#"
1691 {
1692 "entityTypes": { "NS::User": {} },
1693 "actions": {}
1694 }
1695 "#;
1696 assert_matches!(
1697 serde_json::from_str::<json_schema::NamespaceDefinition<RawName>>(src),
1698 Err(_)
1699 );
1700 }
1701
1702 #[test]
1703 fn entity_attribute_entity_type_with_namespace() {
1704 let src = json!(
1705 {"A::B": {
1706 "entityTypes": {
1707 "Foo": {
1708 "shape": {
1709 "type": "Record",
1710 "attributes": {
1711 "name": { "type": "Entity", "name": "C::D::Foo" }
1712 }
1713 }
1714 }
1715 },
1716 "actions": {}
1717 }});
1718 let schema_json = json_schema::Fragment::from_json_value(src.clone()).unwrap();
1719 let schema: Result<ValidatorSchema> = schema_json.try_into();
1720 assert_matches!(schema, Err(e) => {
1721 expect_err(
1722 &src,
1723 &miette::Report::new(e),
1724 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: C::D::Foo"#)
1725 .help("`C::D::Foo` has not been declared as an entity type")
1726 .build());
1727 });
1728 }
1729
1730 #[test]
1731 fn entity_attribute_entity_type_with_declared_namespace() {
1732 let schema_json = json_schema::Fragment::from_json_str(
1733 r#"
1734 {"A::B": {
1735 "entityTypes": {
1736 "Foo": {
1737 "shape": {
1738 "type": "Record",
1739 "attributes": {
1740 "name": { "type": "Entity", "name": "A::B::Foo" }
1741 }
1742 }
1743 }
1744 },
1745 "actions": {}
1746 }}
1747 "#,
1748 )
1749 .unwrap();
1750
1751 let schema: ValidatorSchema = schema_json
1752 .try_into()
1753 .expect("Expected schema to construct without error.");
1754
1755 let foo_name: EntityType = "A::B::Foo".parse().expect("Expected entity type name");
1756 let foo_type = schema
1757 .entity_types
1758 .get(&foo_name)
1759 .expect("Expected to find entity");
1760 let name_type = foo_type
1761 .attr("name")
1762 .expect("Expected attribute name")
1763 .attr_type
1764 .clone();
1765 assert_eq!(name_type, Type::named_entity_reference(foo_name));
1766 }
1767
1768 #[test]
1769 fn cannot_declare_action_type_when_prohibited() {
1770 let schema_json: json_schema::NamespaceDefinition<RawName> = serde_json::from_str(
1771 r#"
1772 {
1773 "entityTypes": { "Action": {} },
1774 "actions": {}
1775 }
1776 "#,
1777 )
1778 .unwrap();
1779 let schema: Result<ValidatorSchema> = schema_json.try_into();
1780 assert!(matches!(
1781 schema,
1782 Err(SchemaError::ActionEntityTypeDeclared(_))
1783 ));
1784 }
1785
1786 #[test]
1787 fn can_declare_other_type_when_action_type_prohibited() {
1788 let schema_json: json_schema::NamespaceDefinition<RawName> = serde_json::from_str(
1789 r#"
1790 {
1791 "entityTypes": { "Foo": { } },
1792 "actions": {}
1793 }
1794 "#,
1795 )
1796 .unwrap();
1797
1798 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1799 }
1800
1801 #[test]
1802 fn cannot_declare_action_in_group_when_prohibited() {
1803 let schema_json = json_schema::Fragment::from_json_str(
1804 r#"
1805 {"": {
1806 "entityTypes": {},
1807 "actions": {
1808 "universe": { },
1809 "view_photo": {
1810 "attributes": {"id": "universe"}
1811 },
1812 "edit_photo": {
1813 "attributes": {"id": "universe"}
1814 },
1815 "delete_photo": {
1816 "attributes": {"id": "universe"}
1817 }
1818 }
1819 }}
1820 "#,
1821 )
1822 .unwrap();
1823
1824 let schema = ValidatorSchemaFragment::from_schema_fragment(
1825 schema_json,
1826 ActionBehavior::ProhibitAttributes,
1827 Extensions::all_available(),
1828 );
1829 match schema {
1830 Err(e) => {
1831 expect_err(
1832 "",
1833 &miette::Report::new(e),
1834 &ExpectedErrorMessageBuilder::error("unsupported feature used in schema")
1835 .source(r#"action declared with attributes: [delete_photo, edit_photo, view_photo]"#)
1836 .build()
1837 )
1838 }
1839 _ => panic!("Did not see expected error."),
1840 }
1841 }
1842
1843 #[test]
1844 fn test_entity_type_no_namespace() {
1845 let src = json!({"type": "Entity", "name": "Foo"});
1846 let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
1847 assert_eq!(
1848 schema_ty,
1849 json_schema::Type::Type(json_schema::TypeVariant::Entity {
1850 name: "Foo".parse().unwrap()
1851 })
1852 );
1853 let schema_ty = schema_ty.conditionally_qualify_type_references(Some(
1854 &InternalName::parse_unqualified_name("NS").unwrap(),
1855 ));
1856 let all_defs = AllDefs::from_entity_defs([
1857 InternalName::from_str("NS::Foo").unwrap(),
1858 InternalName::from_str("Bar").unwrap(),
1859 ]);
1860 let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
1861 let ty: Type =
1862 try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available())
1863 .expect("Error converting schema type to type.")
1864 .resolve_common_type_refs(&HashMap::new())
1865 .unwrap();
1866 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1867 }
1868
1869 #[test]
1870 fn test_entity_type_namespace() {
1871 let src = json!({"type": "Entity", "name": "NS::Foo"});
1872 let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
1873 assert_eq!(
1874 schema_ty,
1875 json_schema::Type::Type(json_schema::TypeVariant::Entity {
1876 name: "NS::Foo".parse().unwrap()
1877 })
1878 );
1879 let schema_ty = schema_ty.conditionally_qualify_type_references(Some(
1880 &InternalName::parse_unqualified_name("NS").unwrap(),
1881 ));
1882 let all_defs = AllDefs::from_entity_defs([
1883 InternalName::from_str("NS::Foo").unwrap(),
1884 InternalName::from_str("Foo").unwrap(),
1885 ]);
1886 let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
1887 let ty: Type =
1888 try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available())
1889 .expect("Error converting schema type to type.")
1890 .resolve_common_type_refs(&HashMap::new())
1891 .unwrap();
1892 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1893 }
1894
1895 #[test]
1896 fn test_entity_type_namespace_parse_error() {
1897 let src = json!({"type": "Entity", "name": "::Foo"});
1898 assert_matches!(
1899 serde_json::from_value::<json_schema::Type<RawName>>(src),
1900 Err(_)
1901 );
1902 }
1903
1904 #[test]
1905 fn schema_type_record_is_validator_type_record() {
1906 let src = json!({"type": "Record", "attributes": {}});
1907 let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
1908 assert_eq!(
1909 schema_ty,
1910 json_schema::Type::Type(json_schema::TypeVariant::Record(json_schema::RecordType {
1911 attributes: BTreeMap::new(),
1912 additional_attributes: false,
1913 })),
1914 );
1915 let schema_ty = schema_ty.conditionally_qualify_type_references(None);
1916 let all_defs = AllDefs::from_entity_defs([InternalName::from_str("Foo").unwrap()]);
1917 let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
1918 let ty: Type =
1919 try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available())
1920 .expect("Error converting schema type to type.")
1921 .resolve_common_type_refs(&HashMap::new())
1922 .unwrap();
1923 assert_eq!(ty, Type::closed_record_with_attributes(None));
1924 }
1925
1926 #[test]
1927 fn get_namespaces() {
1928 let fragment = json_schema::Fragment::from_json_value(json!({
1929 "Foo::Bar::Baz": {
1930 "entityTypes": {},
1931 "actions": {}
1932 },
1933 "Foo": {
1934 "entityTypes": {},
1935 "actions": {}
1936 },
1937 "Bar": {
1938 "entityTypes": {},
1939 "actions": {}
1940 },
1941 }))
1942 .unwrap();
1943
1944 let schema_fragment: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
1945 fragment.try_into().unwrap();
1946 assert_eq!(
1947 schema_fragment
1948 .0
1949 .iter()
1950 .map(|f| f.namespace())
1951 .collect::<HashSet<_>>(),
1952 HashSet::from([
1953 Some(&"Foo::Bar::Baz".parse().unwrap()),
1954 Some(&"Foo".parse().unwrap()),
1955 Some(&"Bar".parse().unwrap())
1956 ])
1957 );
1958 }
1959
1960 #[test]
1961 fn schema_no_fragments() {
1962 let schema =
1963 ValidatorSchema::from_schema_fragments([], Extensions::all_available()).unwrap();
1964 assert!(schema.entity_types.is_empty());
1965 assert!(schema.action_ids.is_empty());
1966 }
1967
1968 #[test]
1969 fn same_action_different_namespace() {
1970 let fragment = json_schema::Fragment::from_json_value(json!({
1971 "Foo::Bar": {
1972 "entityTypes": {},
1973 "actions": {
1974 "Baz": {}
1975 }
1976 },
1977 "Bar::Foo": {
1978 "entityTypes": {},
1979 "actions": {
1980 "Baz": { }
1981 }
1982 },
1983 "Biz": {
1984 "entityTypes": {},
1985 "actions": {
1986 "Baz": { }
1987 }
1988 }
1989 }))
1990 .unwrap();
1991
1992 let schema: ValidatorSchema = fragment.try_into().unwrap();
1993 assert!(schema
1994 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1995 .is_some());
1996 assert!(schema
1997 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1998 .is_some());
1999 assert!(schema
2000 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2001 .is_some());
2002 }
2003
2004 #[test]
2005 fn same_type_different_namespace() {
2006 let fragment = json_schema::Fragment::from_json_value(json!({
2007 "Foo::Bar": {
2008 "entityTypes": {"Baz" : {}},
2009 "actions": { }
2010 },
2011 "Bar::Foo": {
2012 "entityTypes": {"Baz" : {}},
2013 "actions": { }
2014 },
2015 "Biz": {
2016 "entityTypes": {"Baz" : {}},
2017 "actions": { }
2018 }
2019 }))
2020 .unwrap();
2021 let schema: ValidatorSchema = fragment.try_into().unwrap();
2022
2023 assert!(schema
2024 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
2025 .is_some());
2026 assert!(schema
2027 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
2028 .is_some());
2029 assert!(schema
2030 .get_entity_type(&"Biz::Baz".parse().unwrap())
2031 .is_some());
2032 }
2033
2034 #[test]
2035 fn member_of_different_namespace() {
2036 let fragment = json_schema::Fragment::from_json_value(json!({
2037 "Bar": {
2038 "entityTypes": {
2039 "Baz": {
2040 "memberOfTypes": ["Foo::Buz"]
2041 }
2042 },
2043 "actions": {}
2044 },
2045 "Foo": {
2046 "entityTypes": { "Buz": {} },
2047 "actions": { }
2048 }
2049 }))
2050 .unwrap();
2051 let schema: ValidatorSchema = fragment.try_into().unwrap();
2052
2053 let buz = schema
2054 .get_entity_type(&"Foo::Buz".parse().unwrap())
2055 .unwrap();
2056 assert_eq!(
2057 buz.descendants,
2058 HashSet::from(["Bar::Baz".parse().unwrap()])
2059 );
2060 }
2061
2062 #[test]
2063 fn attribute_different_namespace() {
2064 let fragment = json_schema::Fragment::from_json_value(json!({
2065 "Bar": {
2066 "entityTypes": {
2067 "Baz": {
2068 "shape": {
2069 "type": "Record",
2070 "attributes": {
2071 "fiz": {
2072 "type": "Entity",
2073 "name": "Foo::Buz"
2074 }
2075 }
2076 }
2077 }
2078 },
2079 "actions": {}
2080 },
2081 "Foo": {
2082 "entityTypes": { "Buz": {} },
2083 "actions": { }
2084 }
2085 }))
2086 .unwrap();
2087
2088 let schema: ValidatorSchema = fragment.try_into().unwrap();
2089 let baz = schema
2090 .get_entity_type(&"Bar::Baz".parse().unwrap())
2091 .unwrap();
2092 assert_eq!(
2093 baz.attr("fiz").unwrap().attr_type,
2094 Type::named_entity_reference_from_str("Foo::Buz"),
2095 );
2096 }
2097
2098 #[test]
2099 fn applies_to_different_namespace() {
2100 let fragment = json_schema::Fragment::from_json_value(json!({
2101 "Foo::Bar": {
2102 "entityTypes": { },
2103 "actions": {
2104 "Baz": {
2105 "appliesTo": {
2106 "principalTypes": [ "Fiz::Buz" ],
2107 "resourceTypes": [ "Fiz::Baz" ],
2108 }
2109 }
2110 }
2111 },
2112 "Fiz": {
2113 "entityTypes": {
2114 "Buz": {},
2115 "Baz": {}
2116 },
2117 "actions": { }
2118 }
2119 }))
2120 .unwrap();
2121 let schema: ValidatorSchema = fragment.try_into().unwrap();
2122
2123 let baz = schema
2124 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2125 .unwrap();
2126 assert_eq!(
2127 baz.applies_to
2128 .applicable_principal_types()
2129 .collect::<HashSet<_>>(),
2130 HashSet::from([&("Fiz::Buz".parse().unwrap())])
2131 );
2132 assert_eq!(
2133 baz.applies_to
2134 .applicable_resource_types()
2135 .collect::<HashSet<_>>(),
2136 HashSet::from([&("Fiz::Baz".parse().unwrap())])
2137 );
2138 }
2139
2140 #[test]
2141 fn simple_defined_type() {
2142 let fragment = json_schema::Fragment::from_json_value(json!({
2143 "": {
2144 "commonTypes": {
2145 "MyLong": {"type": "Long"}
2146 },
2147 "entityTypes": {
2148 "User": {
2149 "shape": {
2150 "type": "Record",
2151 "attributes": {
2152 "a": {"type": "MyLong"}
2153 }
2154 }
2155 }
2156 },
2157 "actions": {}
2158 }
2159 }))
2160 .unwrap();
2161 let schema: ValidatorSchema = fragment.try_into().unwrap();
2162 assert_eq!(
2163 schema.entity_types.iter().next().unwrap().1.attributes,
2164 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2165 );
2166 }
2167
2168 #[test]
2169 fn defined_record_as_attrs() {
2170 let fragment = json_schema::Fragment::from_json_value(json!({
2171 "": {
2172 "commonTypes": {
2173 "MyRecord": {
2174 "type": "Record",
2175 "attributes": {
2176 "a": {"type": "Long"}
2177 }
2178 }
2179 },
2180 "entityTypes": {
2181 "User": { "shape": { "type": "MyRecord", } }
2182 },
2183 "actions": {}
2184 }
2185 }))
2186 .unwrap();
2187 let schema: ValidatorSchema = fragment.try_into().unwrap();
2188 assert_eq!(
2189 schema.entity_types.iter().next().unwrap().1.attributes,
2190 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2191 );
2192 }
2193
2194 #[test]
2195 fn cross_namespace_type() {
2196 let fragment = json_schema::Fragment::from_json_value(json!({
2197 "A": {
2198 "commonTypes": {
2199 "MyLong": {"type": "Long"}
2200 },
2201 "entityTypes": { },
2202 "actions": {}
2203 },
2204 "B": {
2205 "entityTypes": {
2206 "User": {
2207 "shape": {
2208 "type": "Record",
2209 "attributes": {
2210 "a": {"type": "A::MyLong"}
2211 }
2212 }
2213 }
2214 },
2215 "actions": {}
2216 }
2217 }))
2218 .unwrap();
2219 let schema: ValidatorSchema = fragment.try_into().unwrap();
2220 assert_eq!(
2221 schema.entity_types.iter().next().unwrap().1.attributes,
2222 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2223 );
2224 }
2225
2226 #[test]
2227 fn cross_fragment_type() {
2228 let fragment1: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2229 json_schema::Fragment::from_json_value(json!({
2230 "A": {
2231 "commonTypes": {
2232 "MyLong": {"type": "Long"}
2233 },
2234 "entityTypes": { },
2235 "actions": {}
2236 }
2237 }))
2238 .unwrap()
2239 .try_into()
2240 .unwrap();
2241 let fragment2: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2242 json_schema::Fragment::from_json_value(json!({
2243 "A": {
2244 "entityTypes": {
2245 "User": {
2246 "shape": {
2247 "type": "Record",
2248 "attributes": {
2249 "a": {"type": "MyLong"}
2250 }
2251 }
2252 }
2253 },
2254 "actions": {}
2255 }
2256 }))
2257 .unwrap()
2258 .try_into()
2259 .unwrap();
2260 let schema = ValidatorSchema::from_schema_fragments(
2261 [fragment1, fragment2],
2262 Extensions::all_available(),
2263 )
2264 .unwrap();
2265
2266 assert_eq!(
2267 schema.entity_types.iter().next().unwrap().1.attributes,
2268 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2269 );
2270 }
2271
2272 #[test]
2273 fn cross_fragment_duplicate_type() {
2274 let fragment1: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2275 json_schema::Fragment::from_json_value(json!({
2276 "A": {
2277 "commonTypes": {
2278 "MyLong": {"type": "Long"}
2279 },
2280 "entityTypes": {},
2281 "actions": {}
2282 }
2283 }))
2284 .unwrap()
2285 .try_into()
2286 .unwrap();
2287 let fragment2: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2288 json_schema::Fragment::from_json_value(json!({
2289 "A": {
2290 "commonTypes": {
2291 "MyLong": {"type": "Long"}
2292 },
2293 "entityTypes": {},
2294 "actions": {}
2295 }
2296 }))
2297 .unwrap()
2298 .try_into()
2299 .unwrap();
2300
2301 let schema = ValidatorSchema::from_schema_fragments(
2302 [fragment1, fragment2],
2303 Extensions::all_available(),
2304 );
2305
2306 assert_matches!(schema, Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError(s))) => {
2308 assert_eq!(s, "A::MyLong".parse().unwrap());
2309 });
2310 }
2311
2312 #[test]
2313 fn undeclared_type_in_attr() {
2314 let fragment = json_schema::Fragment::from_json_value(json!({
2315 "": {
2316 "commonTypes": { },
2317 "entityTypes": {
2318 "User": {
2319 "shape": {
2320 "type": "Record",
2321 "attributes": {
2322 "a": {"type": "MyLong"}
2323 }
2324 }
2325 }
2326 },
2327 "actions": {}
2328 }
2329 }))
2330 .unwrap();
2331 assert_matches!(
2332 TryInto::<ValidatorSchema>::try_into(fragment),
2333 Err(SchemaError::TypeNotDefined(_))
2334 );
2335 }
2336
2337 #[test]
2338 fn undeclared_type_in_common_types() {
2339 let fragment = json_schema::Fragment::from_json_value(json!({
2340 "": {
2341 "commonTypes": {
2342 "a": { "type": "b" }
2343 },
2344 "entityTypes": { },
2345 "actions": {}
2346 }
2347 }))
2348 .unwrap();
2349 assert_matches!(
2350 TryInto::<ValidatorSchema>::try_into(fragment),
2351 Err(SchemaError::TypeNotDefined(_))
2352 );
2353 }
2354
2355 #[test]
2356 fn shape_not_record() {
2357 let fragment = json_schema::Fragment::from_json_value(json!({
2358 "": {
2359 "commonTypes": {
2360 "MyLong": { "type": "Long" }
2361 },
2362 "entityTypes": {
2363 "User": {
2364 "shape": { "type": "MyLong" }
2365 }
2366 },
2367 "actions": {}
2368 }
2369 }))
2370 .unwrap();
2371 assert_matches!(
2372 TryInto::<ValidatorSchema>::try_into(fragment),
2373 Err(SchemaError::ContextOrShapeNotRecord(_))
2374 );
2375 }
2376
2377 #[test]
2381 fn counterexamples_from_cedar_134() {
2382 let bad1 = json!({
2384 "": {
2385 "entityTypes": {
2386 "User // comment": {
2387 "memberOfTypes": [
2388 "UserGroup"
2389 ]
2390 },
2391 "User": {
2392 "memberOfTypes": [
2393 "UserGroup"
2394 ]
2395 },
2396 "UserGroup": {}
2397 },
2398 "actions": {}
2399 }
2400 });
2401 assert_matches!(json_schema::Fragment::from_json_value(bad1), Err(_));
2402
2403 let bad2 = json!({
2405 "ABC :: //comment \n XYZ ": {
2406 "entityTypes": {
2407 "User": {
2408 "memberOfTypes": []
2409 }
2410 },
2411 "actions": {}
2412 }
2413 });
2414 assert_matches!(json_schema::Fragment::from_json_value(bad2), Err(_));
2415 }
2416
2417 #[test]
2418 fn simple_action_entity() {
2419 let src = json!(
2420 {
2421 "entityTypes": { },
2422 "actions": {
2423 "view_photo": { },
2424 }
2425 });
2426
2427 let schema_file: json_schema::NamespaceDefinition<RawName> =
2428 serde_json::from_value(src).unwrap();
2429 let schema: ValidatorSchema = schema_file.try_into().unwrap();
2430 let actions = schema.action_entities().expect("Entity Construct Error");
2431
2432 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2433 let view_photo = actions.entity(&action_uid);
2434 assert_eq!(
2435 view_photo.unwrap(),
2436 &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
2437 );
2438 }
2439
2440 #[test]
2441 fn action_entity_hierarchy() {
2442 let src = json!(
2443 {
2444 "entityTypes": { },
2445 "actions": {
2446 "read": {},
2447 "view": {
2448 "memberOf": [{"id": "read"}]
2449 },
2450 "view_photo": {
2451 "memberOf": [{"id": "view"}]
2452 },
2453 }
2454 });
2455
2456 let schema_file: json_schema::NamespaceDefinition<RawName> =
2457 serde_json::from_value(src).unwrap();
2458 let schema: ValidatorSchema = schema_file.try_into().unwrap();
2459 let actions = schema.action_entities().expect("Entity Construct Error");
2460
2461 let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2462 let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2463 let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2464
2465 let view_photo_entity = actions.entity(&view_photo_uid);
2466 assert_eq!(
2467 view_photo_entity.unwrap(),
2468 &Entity::new_with_attr_partial_value(
2469 view_photo_uid,
2470 HashMap::new(),
2471 HashSet::from([view_uid.clone(), read_uid.clone()])
2472 )
2473 );
2474
2475 let view_entity = actions.entity(&view_uid);
2476 assert_eq!(
2477 view_entity.unwrap(),
2478 &Entity::new_with_attr_partial_value(
2479 view_uid,
2480 HashMap::new(),
2481 HashSet::from([read_uid.clone()])
2482 )
2483 );
2484
2485 let read_entity = actions.entity(&read_uid);
2486 assert_eq!(
2487 read_entity.unwrap(),
2488 &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
2489 );
2490 }
2491
2492 #[test]
2493 fn action_entity_attribute() {
2494 let src = json!(
2495 {
2496 "entityTypes": { },
2497 "actions": {
2498 "view_photo": {
2499 "attributes": { "attr": "foo" }
2500 },
2501 }
2502 });
2503
2504 let schema_file: NamespaceDefinitionWithActionAttributes<RawName> =
2505 serde_json::from_value(src).unwrap();
2506 let schema: ValidatorSchema = schema_file.try_into().unwrap();
2507 let actions = schema.action_entities().expect("Entity Construct Error");
2508
2509 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2510 let view_photo = actions.entity(&action_uid);
2511 assert_eq!(
2512 view_photo.unwrap(),
2513 &Entity::new(
2514 action_uid,
2515 HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2516 HashSet::new(),
2517 Extensions::none(),
2518 )
2519 .unwrap(),
2520 );
2521 }
2522
2523 #[test]
2524 fn test_action_namespace_inference_multi_success() {
2525 let src = json!({
2526 "Foo" : {
2527 "entityTypes" : {},
2528 "actions" : {
2529 "read" : {}
2530 }
2531 },
2532 "ExampleCo::Personnel" : {
2533 "entityTypes" : {},
2534 "actions" : {
2535 "viewPhoto" : {
2536 "memberOf" : [
2537 {
2538 "id" : "read",
2539 "type" : "Foo::Action"
2540 }
2541 ]
2542 }
2543 }
2544 },
2545 });
2546 let schema_fragment =
2547 json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
2548 let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2549 let view_photo = schema
2550 .action_entities_iter()
2551 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2552 .unwrap();
2553 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2554 let read = ancestors[0];
2555 let read_eid: &str = read.eid().as_ref();
2556 assert_eq!(read_eid, "read");
2557 assert_eq!(read.entity_type().to_string(), "Foo::Action");
2558 }
2559
2560 #[test]
2561 fn test_action_namespace_inference_multi() {
2562 let src = json!({
2563 "ExampleCo::Personnel::Foo" : {
2564 "entityTypes" : {},
2565 "actions" : {
2566 "read" : {}
2567 }
2568 },
2569 "ExampleCo::Personnel" : {
2570 "entityTypes" : {},
2571 "actions" : {
2572 "viewPhoto" : {
2573 "memberOf" : [
2574 {
2575 "id" : "read",
2576 "type" : "Foo::Action"
2577 }
2578 ]
2579 }
2580 }
2581 },
2582 });
2583 let schema_fragment =
2584 json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
2585 let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2586 schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2587 }
2588
2589 #[test]
2590 fn test_action_namespace_inference() {
2591 let src = json!({
2592 "ExampleCo::Personnel" : {
2593 "entityTypes" : { },
2594 "actions" : {
2595 "read" : {},
2596 "viewPhoto" : {
2597 "memberOf" : [
2598 {
2599 "id" : "read",
2600 "type" : "Action"
2601 }
2602 ]
2603 }
2604 }
2605 }
2606 });
2607 let schema_fragment =
2608 json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
2609 let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2610 let view_photo = schema
2611 .action_entities_iter()
2612 .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2613 .unwrap();
2614 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2615 let read = ancestors[0];
2616 let read_eid: &str = read.eid().as_ref();
2617 assert_eq!(read_eid, "read");
2618 assert_eq!(
2619 read.entity_type().to_string(),
2620 "ExampleCo::Personnel::Action"
2621 );
2622 }
2623
2624 #[test]
2625 fn fallback_to_empty_namespace() {
2626 let src = json!(
2627 {
2628 "Demo": {
2629 "entityTypes": {
2630 "User": {
2631 "memberOfTypes": [],
2632 "shape": {
2633 "type": "Record",
2634 "attributes": {
2635 "id": { "type": "id" },
2636 }
2637 }
2638 }
2639 },
2640 "actions": {}
2641 },
2642 "": {
2643 "commonTypes": {
2644 "id": {
2645 "type": "String"
2646 },
2647 },
2648 "entityTypes": {},
2649 "actions": {}
2650 }
2651 }
2652 );
2653 let schema =
2654 ValidatorSchema::from_json_value(src.clone(), Extensions::all_available()).unwrap();
2655 let mut attributes = schema
2656 .get_entity_type(&"Demo::User".parse().unwrap())
2657 .unwrap()
2658 .attributes();
2659 let (attr_name, attr_ty) = attributes.next().unwrap();
2660 assert_eq!(attr_name, "id");
2661 assert_eq!(&attr_ty.attr_type, &Type::primitive_string());
2662 assert_matches!(attributes.next(), None);
2663 }
2664
2665 #[test]
2666 fn qualified_undeclared_common_types2() {
2667 let src = json!(
2668 {
2669 "Demo": {
2670 "entityTypes": {
2671 "User": {
2672 "memberOfTypes": [],
2673 "shape": {
2674 "type": "Record",
2675 "attributes": {
2676 "id": { "type": "Demo::id" },
2677 }
2678 }
2679 }
2680 },
2681 "actions": {}
2682 },
2683 "": {
2684 "commonTypes": {
2685 "id": {
2686 "type": "String"
2687 },
2688 },
2689 "entityTypes": {},
2690 "actions": {}
2691 }
2692 }
2693 );
2694 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2695 assert_matches!(schema, Err(e) => {
2696 expect_err(
2697 &src,
2698 &miette::Report::new(e),
2699 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: Demo::id"#)
2700 .help("`Demo::id` has not been declared as a common type")
2701 .build());
2702 });
2703 }
2704
2705 #[test]
2706 fn undeclared_entity_type_in_common_type() {
2707 let src = json!(
2708 {
2709 "": {
2710 "commonTypes": {
2711 "id": {
2712 "type": "Entity",
2713 "name": "undeclared"
2714 },
2715 },
2716 "entityTypes": {},
2717 "actions": {}
2718 }
2719 }
2720 );
2721 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2722 assert_matches!(schema, Err(e) => {
2723 expect_err(
2724 &src,
2725 &miette::Report::new(e),
2726 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
2727 .help("`undeclared` has not been declared as an entity type")
2728 .build());
2729 });
2730 }
2731
2732 #[test]
2733 fn undeclared_entity_type_in_common_type_record() {
2734 let src = json!(
2735 {
2736 "": {
2737 "commonTypes": {
2738 "id": {
2739 "type": "Record",
2740 "attributes": {
2741 "first": {
2742 "type": "Entity",
2743 "name": "undeclared"
2744 }
2745 }
2746 },
2747 },
2748 "entityTypes": {},
2749 "actions": {}
2750 }
2751 }
2752 );
2753 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2754 assert_matches!(schema, Err(e) => {
2755 expect_err(
2756 &src,
2757 &miette::Report::new(e),
2758 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
2759 .help("`undeclared` has not been declared as an entity type")
2760 .build());
2761 });
2762 }
2763
2764 #[test]
2765 fn undeclared_entity_type_in_common_type_set() {
2766 let src = json!(
2767 {
2768 "": {
2769 "commonTypes": {
2770 "id": {
2771 "type": "Set",
2772 "element": {
2773 "type": "Entity",
2774 "name": "undeclared"
2775 }
2776 },
2777 },
2778 "entityTypes": {},
2779 "actions": {}
2780 }
2781 }
2782 );
2783 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2784 assert_matches!(schema, Err(e) => {
2785 expect_err(
2786 &src,
2787 &miette::Report::new(e),
2788 &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
2789 .help("`undeclared` has not been declared as an entity type")
2790 .build());
2791 });
2792 }
2793
2794 #[test]
2795 fn unknown_extension_type() {
2796 let src: serde_json::Value = json!({
2797 "": {
2798 "commonTypes": { },
2799 "entityTypes": {
2800 "User": {
2801 "shape": {
2802 "type": "Record",
2803 "attributes": {
2804 "a": {
2805 "type": "Extension",
2806 "name": "ip",
2807 }
2808 }
2809 }
2810 }
2811 },
2812 "actions": {}
2813 }
2814 });
2815 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2816 assert_matches!(schema, Err(e) => {
2817 expect_err(
2818 &src,
2819 &miette::Report::new(e),
2820 &ExpectedErrorMessageBuilder::error("unknown extension type `ip`")
2821 .help("did you mean `ipaddr`?")
2822 .build());
2823 });
2824
2825 let src: serde_json::Value = json!({
2826 "": {
2827 "commonTypes": { },
2828 "entityTypes": {
2829 "User": {},
2830 "Folder" :{}
2831 },
2832 "actions": {
2833 "A": {
2834 "appliesTo": {
2835 "principalTypes" : ["User"],
2836 "resourceTypes" : ["Folder"],
2837 "context": {
2838 "type": "Record",
2839 "attributes": {
2840 "a": {
2841 "type": "Extension",
2842 "name": "deciml",
2843 }
2844 }
2845 }
2846 }
2847 }
2848 }
2849 }
2850 });
2851 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2852 assert_matches!(schema, Err(e) => {
2853 expect_err(
2854 &src,
2855 &miette::Report::new(e),
2856 &ExpectedErrorMessageBuilder::error("unknown extension type `deciml`")
2857 .help("did you mean `decimal`?")
2858 .build());
2859 });
2860
2861 let src: serde_json::Value = json!({
2862 "": {
2863 "commonTypes": {
2864 "ty": {
2865 "type": "Record",
2866 "attributes": {
2867 "a": {
2868 "type": "Extension",
2869 "name": "i",
2870 }
2871 }
2872 }
2873 },
2874 "entityTypes": { },
2875 "actions": { },
2876 }
2877 });
2878 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2879 assert_matches!(schema, Err(e) => {
2880 expect_err(
2881 &src,
2882 &miette::Report::new(e),
2883 &ExpectedErrorMessageBuilder::error("unknown extension type `i`")
2884 .help("did you mean `ipaddr`?")
2885 .build());
2886 });
2887
2888 let src: serde_json::Value = json!({
2889 "": {
2890 "commonTypes": {
2891 "ty": {
2892 "type": "Record",
2893 "attributes": {
2894 "a": {
2895 "type": "Extension",
2896 "name": "partial_evaluation",
2897 }
2898 }
2899 }
2900 },
2901 "entityTypes": { },
2902 "actions": { },
2903 }
2904 });
2905 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2906 assert_matches!(schema, Err(e) => {
2907 expect_err(
2908 &src,
2909 &miette::Report::new(e),
2910 &ExpectedErrorMessageBuilder::error("unknown extension type `partial_evaluation`")
2911 .help("did you mean `decimal`?")
2912 .build());
2913 });
2914 }
2915
2916 #[track_caller]
2917 fn assert_invalid_json_schema(src: serde_json::Value) {
2918 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2919 assert_matches!(schema, Err(SchemaError::JsonDeserialization(e)) if e.to_smolstr().contains("Used reserved schema keyword"));
2920 }
2921
2922 #[test]
2924 fn test_common_type_name_conflicts() {
2925 let src: serde_json::Value = json!({
2927 "": {
2928 "commonTypes": {
2929 "Record": {
2930 "type": "Record",
2931 "attributes": {
2932 "a": {
2933 "type": "Long",
2934 }
2935 }
2936 }
2937 },
2938 "entityTypes": {
2939 "b": {
2940 "shape" : {
2941 "type" : "Record",
2942 "attributes" : {
2943 "c" : {
2944 "type" : "Record"
2945 }
2946 }
2947 }
2948 }
2949 },
2950 "actions": { },
2951 }
2952 });
2953 assert_invalid_json_schema(src);
2954
2955 let src: serde_json::Value = json!({
2956 "NS": {
2957 "commonTypes": {
2958 "Record": {
2959 "type": "Record",
2960 "attributes": {
2961 "a": {
2962 "type": "Long",
2963 }
2964 }
2965 }
2966 },
2967 "entityTypes": {
2968 "b": {
2969 "shape" : {
2970 "type" : "Record",
2971 "attributes" : {
2972 "c" : {
2973 "type" : "Record"
2974 }
2975 }
2976 }
2977 }
2978 },
2979 "actions": { },
2980 }
2981 });
2982 assert_invalid_json_schema(src);
2983
2984 let src: serde_json::Value = json!({
2986 "": {
2987 "commonTypes": {
2988 "Extension": {
2989 "type": "Record",
2990 "attributes": {
2991 "a": {
2992 "type": "Long",
2993 }
2994 }
2995 }
2996 },
2997 "entityTypes": {
2998 "b": {
2999 "shape" : {
3000 "type" : "Record",
3001 "attributes" : {
3002 "c" : {
3003 "type" : "Extension"
3004 }
3005 }
3006 }
3007 }
3008 },
3009 "actions": { },
3010 }
3011 });
3012 assert_invalid_json_schema(src);
3013
3014 let src: serde_json::Value = json!({
3015 "NS": {
3016 "commonTypes": {
3017 "Extension": {
3018 "type": "Record",
3019 "attributes": {
3020 "a": {
3021 "type": "Long",
3022 }
3023 }
3024 }
3025 },
3026 "entityTypes": {
3027 "b": {
3028 "shape" : {
3029 "type" : "Record",
3030 "attributes" : {
3031 "c" : {
3032 "type" : "Extension"
3033 }
3034 }
3035 }
3036 }
3037 },
3038 "actions": { },
3039 }
3040 });
3041 assert_invalid_json_schema(src);
3042
3043 let src: serde_json::Value = json!({
3045 "": {
3046 "commonTypes": {
3047 "Entity": {
3048 "type": "Record",
3049 "attributes": {
3050 "a": {
3051 "type": "Long",
3052 }
3053 }
3054 }
3055 },
3056 "entityTypes": {
3057 "b": {
3058 "shape" : {
3059 "type" : "Record",
3060 "attributes" : {
3061 "c" : {
3062 "type" : "Entity"
3063 }
3064 }
3065 }
3066 }
3067 },
3068 "actions": { },
3069 }
3070 });
3071 assert_invalid_json_schema(src);
3072
3073 let src: serde_json::Value = json!({
3074 "NS": {
3075 "commonTypes": {
3076 "Entity": {
3077 "type": "Record",
3078 "attributes": {
3079 "a": {
3080 "type": "Long",
3081 }
3082 }
3083 }
3084 },
3085 "entityTypes": {
3086 "b": {
3087 "shape" : {
3088 "type" : "Record",
3089 "attributes" : {
3090 "c" : {
3091 "type" : "Entity"
3092 }
3093 }
3094 }
3095 }
3096 },
3097 "actions": { },
3098 }
3099 });
3100 assert_invalid_json_schema(src);
3101
3102 let src: serde_json::Value = json!({
3104 "": {
3105 "commonTypes": {
3106 "Set": {
3107 "type": "Record",
3108 "attributes": {
3109 "a": {
3110 "type": "Long",
3111 }
3112 }
3113 }
3114 },
3115 "entityTypes": {
3116 "b": {
3117 "shape" : {
3118 "type" : "Record",
3119 "attributes" : {
3120 "c" : {
3121 "type" : "Set"
3122 }
3123 }
3124 }
3125 }
3126 },
3127 "actions": { },
3128 }
3129 });
3130 assert_invalid_json_schema(src);
3131
3132 let src: serde_json::Value = json!({
3133 "NS": {
3134 "commonTypes": {
3135 "Set": {
3136 "type": "Record",
3137 "attributes": {
3138 "a": {
3139 "type": "Long",
3140 }
3141 }
3142 }
3143 },
3144 "entityTypes": {
3145 "b": {
3146 "shape" : {
3147 "type" : "Record",
3148 "attributes" : {
3149 "c" : {
3150 "type" : "Set"
3151 }
3152 }
3153 }
3154 }
3155 },
3156 "actions": { },
3157 }
3158 });
3159 assert_invalid_json_schema(src);
3160
3161 let src: serde_json::Value = json!({
3163 "": {
3164 "commonTypes": {
3165 "Long": {
3166 "type": "Record",
3167 "attributes": {
3168 "a": {
3169 "type": "Long",
3170 }
3171 }
3172 }
3173 },
3174 "entityTypes": {
3175 "b": {
3176 "shape" : {
3177 "type" : "Record",
3178 "attributes" : {
3179 "c" : {
3180 "type" : "Long"
3181 }
3182 }
3183 }
3184 }
3185 },
3186 "actions": { },
3187 }
3188 });
3189 assert_invalid_json_schema(src);
3190
3191 let src: serde_json::Value = json!({
3192 "NS": {
3193 "commonTypes": {
3194 "Long": {
3195 "type": "Record",
3196 "attributes": {
3197 "a": {
3198 "type": "Long",
3199 }
3200 }
3201 }
3202 },
3203 "entityTypes": {
3204 "b": {
3205 "shape" : {
3206 "type" : "Record",
3207 "attributes" : {
3208 "c" : {
3209 "type" : "Long"
3210 }
3211 }
3212 }
3213 }
3214 },
3215 "actions": { },
3216 }
3217 });
3218 assert_invalid_json_schema(src);
3219
3220 let src: serde_json::Value = json!({
3222 "": {
3223 "commonTypes": {
3224 "Boolean": {
3225 "type": "Record",
3226 "attributes": {
3227 "a": {
3228 "type": "Long",
3229 }
3230 }
3231 }
3232 },
3233 "entityTypes": {
3234 "b": {
3235 "shape" : {
3236 "type" : "Record",
3237 "attributes" : {
3238 "c" : {
3239 "type" : "Boolean"
3240 }
3241 }
3242 }
3243 }
3244 },
3245 "actions": { },
3246 }
3247 });
3248 assert_invalid_json_schema(src);
3249
3250 let src: serde_json::Value = json!({
3251 "NS": {
3252 "commonTypes": {
3253 "Boolean": {
3254 "type": "Record",
3255 "attributes": {
3256 "a": {
3257 "type": "Long",
3258 }
3259 }
3260 }
3261 },
3262 "entityTypes": {
3263 "b": {
3264 "shape" : {
3265 "type" : "Record",
3266 "attributes" : {
3267 "c" : {
3268 "type" : "Boolean"
3269 }
3270 }
3271 }
3272 }
3273 },
3274 "actions": { },
3275 }
3276 });
3277 assert_invalid_json_schema(src);
3278
3279 let src: serde_json::Value = json!({
3281 "": {
3282 "commonTypes": {
3283 "String": {
3284 "type": "Record",
3285 "attributes": {
3286 "a": {
3287 "type": "Long",
3288 }
3289 }
3290 }
3291 },
3292 "entityTypes": {
3293 "b": {
3294 "shape" : {
3295 "type" : "Record",
3296 "attributes" : {
3297 "c" : {
3298 "type" : "String"
3299 }
3300 }
3301 }
3302 }
3303 },
3304 "actions": { },
3305 }
3306 });
3307 assert_invalid_json_schema(src);
3308
3309 let src: serde_json::Value = json!({
3310 "NS": {
3311 "commonTypes": {
3312 "String": {
3313 "type": "Record",
3314 "attributes": {
3315 "a": {
3316 "type": "Long",
3317 }
3318 }
3319 }
3320 },
3321 "entityTypes": {
3322 "b": {
3323 "shape" : {
3324 "type" : "Record",
3325 "attributes" : {
3326 "c" : {
3327 "type" : "String"
3328 }
3329 }
3330 }
3331 }
3332 },
3333 "actions": { },
3334 }
3335 });
3336 assert_invalid_json_schema(src);
3337
3338 let src: serde_json::Value = json!({
3342 "": {
3343 "commonTypes": {
3344 "Record": {
3345 "type": "Set",
3346 "element": {
3347 "type": "Long"
3348 }
3349 }
3350 },
3351 "entityTypes": {
3352 "b": {
3353 "shape" :
3354 {
3355 "type": "Record",
3356 "attributes" : {
3357 "c" : {
3358 "type" : "String"
3359 }
3360 }
3361 }
3362 }
3363 },
3364 "actions": { },
3365 }
3366 });
3367 assert_invalid_json_schema(src);
3368 }
3369
3370 #[test]
3371 fn reserved_namespace() {
3372 let src: serde_json::Value = json!({
3373 "__cedar": {
3374 "commonTypes": { },
3375 "entityTypes": { },
3376 "actions": { },
3377 }
3378 });
3379 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3380 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3381
3382 let src: serde_json::Value = json!({
3383 "__cedar::A": {
3384 "commonTypes": { },
3385 "entityTypes": { },
3386 "actions": { },
3387 }
3388 });
3389 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3390 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3391
3392 let src: serde_json::Value = json!({
3393 "": {
3394 "commonTypes": {
3395 "__cedar": {
3396 "type": "String",
3397 }
3398 },
3399 "entityTypes": { },
3400 "actions": { },
3401 }
3402 });
3403 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3404 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3405
3406 let src: serde_json::Value = json!({
3407 "A": {
3408 "commonTypes": {
3409 "__cedar": {
3410 "type": "String",
3411 }
3412 },
3413 "entityTypes": { },
3414 "actions": { },
3415 }
3416 });
3417 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3418 assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3419
3420 let src: serde_json::Value = json!({
3421 "": {
3422 "commonTypes": {
3423 "A": {
3424 "type": "__cedar",
3425 }
3426 },
3427 "entityTypes": { },
3428 "actions": { },
3429 }
3430 });
3431 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3432 assert_matches!(schema, Err(e) => {
3433 expect_err(
3434 &src,
3435 &miette::Report::new(e),
3436 &ExpectedErrorMessageBuilder::error("failed to resolve type: __cedar")
3437 .help("`__cedar` has not been declared as a common type")
3438 .build(),
3439 );
3440 });
3441 }
3442}
3443
3444#[cfg(test)]
3445mod test_579; #[cfg(test)]
3448mod test_rfc70 {
3449 use super::{test::collect_warnings, CedarSchemaError};
3450 use super::{SchemaError, ValidatorSchema};
3451 use crate::types::Type;
3452 use cedar_policy_core::{
3453 extensions::Extensions,
3454 test_utils::{expect_err, ExpectedErrorMessageBuilder},
3455 };
3456 use cool_asserts::assert_matches;
3457 use serde_json::json;
3458
3459 #[track_caller]
3460 fn assert_valid_cedar_schema(src: &str) -> ValidatorSchema {
3461 match ValidatorSchema::from_cedarschema_str(src, Extensions::all_available()) {
3462 Ok((schema, _)) => schema,
3463 Err(e) => panic!("{:?}", miette::Report::new(e)),
3464 }
3465 }
3466
3467 #[track_caller]
3468 fn assert_invalid_cedar_schema(src: &str) {
3469 match ValidatorSchema::from_cedarschema_str(src, Extensions::all_available()) {
3470 Ok(_) => panic!("{src} should be an invalid schema"),
3471 Err(CedarSchemaError::Parsing(_)) => {}
3472 Err(e) => panic!("unexpected error: {:?}", miette::Report::new(e)),
3473 }
3474 }
3475
3476 #[track_caller]
3477 fn assert_valid_json_schema(json: serde_json::Value) -> ValidatorSchema {
3478 match ValidatorSchema::from_json_value(json, Extensions::all_available()) {
3479 Ok(schema) => schema,
3480 Err(e) => panic!("{:?}", miette::Report::new(e)),
3481 }
3482 }
3483
3484 #[track_caller]
3485 fn assert_invalid_json_schema(json: serde_json::Value) {
3486 match ValidatorSchema::from_json_value(json.clone(), Extensions::all_available()) {
3487 Ok(_) => panic!("{json} should be an invalid schema"),
3488 Err(SchemaError::JsonDeserialization(_)) => {}
3489 Err(e) => panic!("unexpected error: {:?}", miette::Report::new(e)),
3490 }
3491 }
3492
3493 #[test]
3495 fn common_common_conflict() {
3496 let src = "
3497 type T = String;
3498 namespace NS {
3499 type T = String;
3500 entity User { t: T };
3501 }
3502 ";
3503 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3504 expect_err(
3505 src,
3506 &miette::Report::new(e),
3507 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3508 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3509 .build(),
3510 );
3511 });
3512
3513 let src_json = json!({
3514 "": {
3515 "commonTypes": {
3516 "T": { "type": "String" },
3517 },
3518 "entityTypes": {},
3519 "actions": {},
3520 },
3521 "NS": {
3522 "commonTypes": {
3523 "T": { "type": "String" },
3524 },
3525 "entityTypes": {
3526 "User": {
3527 "shape": {
3528 "type": "Record",
3529 "attributes": {
3530 "t": { "type": "T" },
3531 },
3532 }
3533 }
3534 },
3535 "actions": {},
3536 }
3537 });
3538 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3539 expect_err(
3540 &src_json,
3541 &miette::Report::new(e),
3542 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3543 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3544 .build(),
3545 );
3546 });
3547 }
3548
3549 #[test]
3551 fn entity_entity_conflict() {
3552 let src = "
3553 entity T in T { foo: String };
3554 namespace NS {
3555 entity T { bar: String };
3556 entity User { t: T };
3557 }
3558 ";
3559 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3560 expect_err(
3561 src,
3562 &miette::Report::new(e),
3563 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3564 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3565 .build(),
3566 );
3567 });
3568
3569 let src = "
3571 entity T { foo: String };
3572 namespace NS {
3573 entity T { bar: String };
3574 }
3575 ";
3576 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3577 expect_err(
3578 src,
3579 &miette::Report::new(e),
3580 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3581 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3582 .build(),
3583 );
3584 });
3585
3586 let src_json = json!({
3587 "": {
3588 "entityTypes": {
3589 "T": {
3590 "memberOfTypes": ["T"],
3591 "shape": {
3592 "type": "Record",
3593 "attributes": {
3594 "foo": { "type": "String" },
3595 },
3596 }
3597 }
3598 },
3599 "actions": {},
3600 },
3601 "NS": {
3602 "entityTypes": {
3603 "T": {
3604 "shape": {
3605 "type": "Record",
3606 "attributes": {
3607 "bar": { "type": "String" },
3608 },
3609 }
3610 },
3611 "User": {
3612 "shape": {
3613 "type": "Record",
3614 "attributes": {
3615 "t": { "type": "Entity", "name": "T" },
3616 },
3617 }
3618 },
3619 },
3620 "actions": {},
3621 }
3622 });
3623 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3624 expect_err(
3625 &src_json,
3626 &miette::Report::new(e),
3627 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3628 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3629 .build(),
3630 );
3631 });
3632 }
3633
3634 #[test]
3637 fn common_entity_conflict() {
3638 let src = "
3639 entity T in T { foo: String };
3640 namespace NS {
3641 type T = String;
3642 entity User { t: T };
3643 }
3644 ";
3645 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3646 expect_err(
3647 src,
3648 &miette::Report::new(e),
3649 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3650 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3651 .build(),
3652 );
3653 });
3654
3655 let src_json = json!({
3656 "": {
3657 "entityTypes": {
3658 "T": {
3659 "memberOfTypes": ["T"],
3660 "shape": {
3661 "type": "Record",
3662 "attributes": {
3663 "foo": { "type": "String" },
3664 },
3665 }
3666 }
3667 },
3668 "actions": {},
3669 },
3670 "NS": {
3671 "commonTypes": {
3672 "T": { "type": "String" },
3673 },
3674 "entityTypes": {
3675 "User": {
3676 "shape": {
3677 "type": "Record",
3678 "attributes": {
3679 "t": { "type": "T" },
3680 }
3681 }
3682 }
3683 },
3684 "actions": {},
3685 }
3686 });
3687 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3688 expect_err(
3689 &src_json,
3690 &miette::Report::new(e),
3691 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3692 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3693 .build(),
3694 );
3695 });
3696 }
3697
3698 #[test]
3701 fn entity_common_conflict() {
3702 let src = "
3703 type T = String;
3704 namespace NS {
3705 entity T in T { foo: String };
3706 entity User { t: T };
3707 }
3708 ";
3709 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3710 expect_err(
3711 src,
3712 &miette::Report::new(e),
3713 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3714 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3715 .build(),
3716 );
3717 });
3718
3719 let src_json = json!({
3720 "": {
3721 "commonTypes": {
3722 "T": { "type": "String" },
3723 },
3724 "entityTypes": {},
3725 "actions": {},
3726 },
3727 "NS": {
3728 "entityTypes": {
3729 "T": {
3730 "memberOfTypes": ["T"],
3731 "shape": {
3732 "type": "Record",
3733 "attributes": {
3734 "foo": { "type": "String" },
3735 },
3736 }
3737 },
3738 "User": {
3739 "shape": {
3740 "type": "Record",
3741 "attributes": {
3742 "t": { "type": "T" },
3743 }
3744 }
3745 }
3746 },
3747 "actions": {},
3748 }
3749 });
3750 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3751 expect_err(
3752 &src_json,
3753 &miette::Report::new(e),
3754 &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3755 .help("try renaming one of the definitions, or moving `T` to a different namespace")
3756 .build(),
3757 );
3758 });
3759 }
3760
3761 #[test]
3763 fn action_action_conflict() {
3764 let src = "
3765 action A;
3766 namespace NS {
3767 action A;
3768 }
3769 ";
3770 assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3771 expect_err(
3772 src,
3773 &miette::Report::new(e),
3774 &ExpectedErrorMessageBuilder::error("definition of `NS::Action::\"A\"` illegally shadows the existing definition of `Action::\"A\"`")
3775 .help("try renaming one of the actions, or moving `Action::\"A\"` to a different namespace")
3776 .build(),
3777 );
3778 });
3779
3780 let src_json = json!({
3781 "": {
3782 "entityTypes": {},
3783 "actions": {
3784 "A": {},
3785 },
3786 },
3787 "NS": {
3788 "entityTypes": {},
3789 "actions": {
3790 "A": {},
3791 },
3792 }
3793 });
3794 assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3795 expect_err(
3796 &src_json,
3797 &miette::Report::new(e),
3798 &ExpectedErrorMessageBuilder::error("definition of `NS::Action::\"A\"` illegally shadows the existing definition of `Action::\"A\"`")
3799 .help("try renaming one of the actions, or moving `Action::\"A\"` to a different namespace")
3800 .build(),
3801 );
3802 });
3803 }
3804
3805 #[test]
3807 fn action_common_conflict() {
3808 let src = "
3809 action A;
3810 action B; // same name as a common type in same (empty) namespace
3811 action C; // same name as a common type in different (nonempty) namespace
3812 type B = String;
3813 type E = String;
3814 namespace NS1 {
3815 type C = String;
3816 entity User { b: B, c: C, e: E };
3817 }
3818 namespace NS2 {
3819 type D = String;
3820 action D; // same name as a common type in same (nonempty) namespace
3821 action E; // same name as a common type in different (empty) namespace
3822 entity User { b: B, d: D, e: E };
3823 }
3824 ";
3825 assert_valid_cedar_schema(src);
3826
3827 let src_json = json!({
3828 "": {
3829 "commonTypes": {
3830 "B": { "type": "String" },
3831 "E": { "type": "String" },
3832 },
3833 "entityTypes": {},
3834 "actions": {
3835 "A": {},
3836 "B": {},
3837 "C": {},
3838 },
3839 },
3840 "NS1": {
3841 "commonTypes": {
3842 "C": { "type": "String" },
3843 },
3844 "entityTypes": {
3845 "User": {
3846 "shape": {
3847 "type": "Record",
3848 "attributes": {
3849 "b": { "type": "B" },
3850 "c": { "type": "C" },
3851 "e": { "type": "E" },
3852 }
3853 }
3854 },
3855 },
3856 "actions": {}
3857 },
3858 "NS2": {
3859 "commonTypes": {
3860 "D": { "type": "String" },
3861 },
3862 "entityTypes": {
3863 "User": {
3864 "shape": {
3865 "type": "Record",
3866 "attributes": {
3867 "b": { "type": "B" },
3868 "d": { "type": "D" },
3869 "e": { "type": "E" },
3870 }
3871 }
3872 }
3873 },
3874 "actions": {
3875 "D": {},
3876 "E": {},
3877 }
3878 }
3879 });
3880 assert_valid_json_schema(src_json);
3881 }
3882
3883 #[test]
3885 fn action_entity_conflict() {
3886 let src = "
3887 action A;
3888 action B; // same name as an entity type in same (empty) namespace
3889 action C; // same name as an entity type in different (nonempty) namespace
3890 entity B;
3891 entity E;
3892 namespace NS1 {
3893 entity C;
3894 entity User { b: B, c: C, e: E };
3895 }
3896 namespace NS2 {
3897 entity D;
3898 action D; // same name as an entity type in same (nonempty) namespace
3899 action E; // same name as an entity type in different (empty) namespace
3900 entity User { b: B, d: D, e: E };
3901 }
3902 ";
3903 assert_valid_cedar_schema(src);
3904
3905 let src_json = json!({
3906 "": {
3907 "entityTypes": {
3908 "B": {},
3909 "E": {},
3910 },
3911 "actions": {
3912 "A": {},
3913 "B": {},
3914 "C": {},
3915 },
3916 },
3917 "NS1": {
3918 "entityTypes": {
3919 "C": {},
3920 "User": {
3921 "shape": {
3922 "type": "Record",
3923 "attributes": {
3924 "b": { "type": "Entity", "name": "B" },
3925 "c": { "type": "Entity", "name": "C" },
3926 "e": { "type": "Entity", "name": "E" },
3927 }
3928 }
3929 },
3930 },
3931 "actions": {}
3932 },
3933 "NS2": {
3934 "entityTypes": {
3935 "D": {},
3936 "User": {
3937 "shape": {
3938 "type": "Record",
3939 "attributes": {
3940 "b": { "type": "Entity", "name": "B" },
3941 "d": { "type": "Entity", "name": "D" },
3942 "e": { "type": "Entity", "name": "E" },
3943 }
3944 }
3945 }
3946 },
3947 "actions": {
3948 "D": {},
3949 "E": {},
3950 }
3951 }
3952 });
3953 assert_valid_json_schema(src_json);
3954 }
3955
3956 #[test]
3962 fn common_shadowing_entity_same_namespace() {
3963 let src = "
3964 entity T;
3965 type T = Bool; // works in the empty namespace
3966 namespace NS {
3967 entity E;
3968 type E = Bool; // works in a nonempty namespace
3969 }
3970 ";
3971 assert_valid_cedar_schema(src);
3972
3973 let src_json = json!({
3974 "": {
3975 "commonTypes": {
3976 "T": { "type": "Entity", "name": "T" },
3977 },
3978 "entityTypes": {
3979 "T": {},
3980 },
3981 "actions": {}
3982 },
3983 "NS1": {
3984 "commonTypes": {
3985 "E": { "type": "Entity", "name": "E" },
3986 },
3987 "entityTypes": {
3988 "E": {},
3989 },
3990 "actions": {}
3991 },
3992 "NS2": {
3993 "commonTypes": {
3994 "E": { "type": "String" },
3995 },
3996 "entityTypes": {
3997 "E": {},
3998 },
3999 "actions": {}
4000 }
4001 });
4002 assert_valid_json_schema(src_json);
4003 }
4004
4005 #[test]
4008 fn common_shadowing_primitive() {
4009 let src = "
4010 type String = Long;
4011 entity E {
4012 a: String,
4013 b: __cedar::String,
4014 c: Long,
4015 d: __cedar::Long,
4016 };
4017 namespace NS {
4018 type Bool = Long;
4019 entity F {
4020 a: Bool,
4021 b: __cedar::Bool,
4022 c: Long,
4023 d: __cedar::Long,
4024 };
4025 }
4026 ";
4027 assert_invalid_cedar_schema(src);
4028 let src = "
4029 type _String = Long;
4030 entity E {
4031 a: _String,
4032 b: __cedar::String,
4033 c: Long,
4034 d: __cedar::Long,
4035 };
4036 namespace NS {
4037 type _Bool = Long;
4038 entity F {
4039 a: _Bool,
4040 b: __cedar::Bool,
4041 c: Long,
4042 d: __cedar::Long,
4043 };
4044 }
4045 ";
4046 let schema = assert_valid_cedar_schema(src);
4047 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4048 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4049 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4051 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4052 assert_eq!(&atype.attr_type, &Type::primitive_string());
4053 });
4054 assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4055 assert_eq!(&atype.attr_type, &Type::primitive_long());
4056 });
4057 assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4058 assert_eq!(&atype.attr_type, &Type::primitive_long());
4059 });
4060 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4061 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4062 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4064 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4065 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4066 });
4067 assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4068 assert_eq!(&atype.attr_type, &Type::primitive_long());
4069 });
4070 assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4071 assert_eq!(&atype.attr_type, &Type::primitive_long());
4072 });
4073
4074 let src_json = json!({
4075 "": {
4076 "commonTypes": {
4077 "String": { "type": "Long" },
4078 },
4079 "entityTypes": {
4080 "E": {
4081 "shape": {
4082 "type": "Record",
4083 "attributes": {
4084 "a": { "type": "String" },
4085 "b": { "type": "__cedar::String" },
4086 "c": { "type": "Long" },
4087 "d": { "type": "__cedar::Long" },
4088 }
4089 }
4090 },
4091 },
4092 "actions": {}
4093 },
4094 "NS": {
4095 "commonTypes": {
4096 "Bool": { "type": "Long" },
4097 },
4098 "entityTypes": {
4099 "F": {
4100 "shape": {
4101 "type": "Record",
4102 "attributes": {
4103 "a": { "type": "Bool" },
4104 "b": { "type": "__cedar::Bool" },
4105 "c": { "type": "Long" },
4106 "d": { "type": "__cedar::Long" },
4107 }
4108 }
4109 },
4110 },
4111 "actions": {}
4112 }
4113 });
4114 assert_invalid_json_schema(src_json);
4115 let src_json = json!({
4116 "": {
4117 "commonTypes": {
4118 "_String": { "type": "Long" },
4119 },
4120 "entityTypes": {
4121 "E": {
4122 "shape": {
4123 "type": "Record",
4124 "attributes": {
4125 "a": { "type": "_String" },
4126 "b": { "type": "__cedar::String" },
4127 "c": { "type": "Long" },
4128 "d": { "type": "__cedar::Long" },
4129 }
4130 }
4131 },
4132 },
4133 "actions": {}
4134 },
4135 "NS": {
4136 "commonTypes": {
4137 "_Bool": { "type": "Long" },
4138 },
4139 "entityTypes": {
4140 "F": {
4141 "shape": {
4142 "type": "Record",
4143 "attributes": {
4144 "a": { "type": "_Bool" },
4145 "b": { "type": "__cedar::Bool" },
4146 "c": { "type": "Long" },
4147 "d": { "type": "__cedar::Long" },
4148 }
4149 }
4150 },
4151 },
4152 "actions": {}
4153 }
4154 });
4155 let schema = assert_valid_json_schema(src_json);
4156 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4157 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4158 assert_eq!(&atype.attr_type, &Type::primitive_long());
4159 });
4160 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4161 assert_eq!(&atype.attr_type, &Type::primitive_string());
4162 });
4163 assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4164 assert_eq!(&atype.attr_type, &Type::primitive_long());
4165 });
4166 assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4167 assert_eq!(&atype.attr_type, &Type::primitive_long());
4168 });
4169 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4170 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4171 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4173 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4174 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4175 });
4176 assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4177 assert_eq!(&atype.attr_type, &Type::primitive_long());
4178 });
4179 assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4180 assert_eq!(&atype.attr_type, &Type::primitive_long());
4181 });
4182 }
4183
4184 #[test]
4187 fn common_shadowing_extension() {
4188 let src = "
4189 type ipaddr = Long;
4190 entity E {
4191 a: ipaddr,
4192 b: __cedar::ipaddr,
4193 c: Long,
4194 d: __cedar::Long,
4195 };
4196 namespace NS {
4197 type decimal = Long;
4198 entity F {
4199 a: decimal,
4200 b: __cedar::decimal,
4201 c: Long,
4202 d: __cedar::Long,
4203 };
4204 }
4205 ";
4206 let schema = assert_valid_cedar_schema(src);
4207 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4208 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4209 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4211 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4212 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4213 });
4214 assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4215 assert_eq!(&atype.attr_type, &Type::primitive_long());
4216 });
4217 assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4218 assert_eq!(&atype.attr_type, &Type::primitive_long());
4219 });
4220 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4221 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4222 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4224 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4225 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4226 });
4227 assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4228 assert_eq!(&atype.attr_type, &Type::primitive_long());
4229 });
4230 assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4231 assert_eq!(&atype.attr_type, &Type::primitive_long());
4232 });
4233
4234 let src_json = json!({
4235 "": {
4236 "commonTypes": {
4237 "ipaddr": { "type": "Long" },
4238 },
4239 "entityTypes": {
4240 "E": {
4241 "shape": {
4242 "type": "Record",
4243 "attributes": {
4244 "a": { "type": "ipaddr" },
4245 "b": { "type": "__cedar::ipaddr" },
4246 "c": { "type": "Long" },
4247 "d": { "type": "__cedar::Long" },
4248 }
4249 }
4250 },
4251 },
4252 "actions": {}
4253 },
4254 "NS": {
4255 "commonTypes": {
4256 "decimal": { "type": "Long" },
4257 },
4258 "entityTypes": {
4259 "F": {
4260 "shape": {
4261 "type": "Record",
4262 "attributes": {
4263 "a": { "type": "decimal" },
4264 "b": { "type": "__cedar::decimal" },
4265 "c": { "type": "Long" },
4266 "d": { "type": "__cedar::Long" },
4267 }
4268 }
4269 },
4270 },
4271 "actions": {}
4272 }
4273 });
4274 let schema = assert_valid_json_schema(src_json);
4275 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4276 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4277 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4279 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4280 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4281 });
4282 assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4283 assert_eq!(&atype.attr_type, &Type::primitive_long());
4284 });
4285 assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4286 assert_eq!(&atype.attr_type, &Type::primitive_long());
4287 });
4288 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4289 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4290 assert_eq!(&atype.attr_type, &Type::primitive_long()); });
4292 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4293 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4294 });
4295 assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4296 assert_eq!(&atype.attr_type, &Type::primitive_long());
4297 });
4298 assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4299 assert_eq!(&atype.attr_type, &Type::primitive_long());
4300 });
4301 }
4302
4303 #[test]
4306 fn entity_shadowing_primitive() {
4307 let src = "
4308 entity String;
4309 entity E {
4310 a: String,
4311 b: __cedar::String,
4312 };
4313 namespace NS {
4314 entity Bool;
4315 entity F {
4316 a: Bool,
4317 b: __cedar::Bool,
4318 };
4319 }
4320 ";
4321 let schema = assert_valid_cedar_schema(src);
4322 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4323 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4324 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("String"));
4325 });
4326 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4327 assert_eq!(&atype.attr_type, &Type::primitive_string());
4328 });
4329 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4330 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4331 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::Bool")); });
4333 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4334 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4335 });
4336
4337 let src_json = json!({
4338 "": {
4339 "entityTypes": {
4340 "String": {},
4341 "E": {
4342 "shape": {
4343 "type": "Record",
4344 "attributes": {
4345 "a": { "type": "Entity", "name": "String" },
4346 "b": { "type": "__cedar::String" },
4347 }
4348 }
4349 },
4350 },
4351 "actions": {}
4352 },
4353 "NS": {
4354 "entityTypes": {
4355 "Bool": {},
4356 "F": {
4357 "shape": {
4358 "type": "Record",
4359 "attributes": {
4360 "a": { "type": "Entity", "name": "Bool" },
4361 "b": { "type": "__cedar::Bool" },
4362 }
4363 }
4364 },
4365 },
4366 "actions": {}
4367 }
4368 });
4369 let schema = assert_valid_json_schema(src_json);
4370 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4371 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4372 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("String"));
4373 });
4374 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4375 assert_eq!(&atype.attr_type, &Type::primitive_string());
4376 });
4377 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4378 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4379 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::Bool"));
4380 });
4381 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4382 assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4383 });
4384 }
4385
4386 #[test]
4389 fn entity_shadowing_extension() {
4390 let src = "
4391 entity ipaddr;
4392 entity E {
4393 a: ipaddr,
4394 b: __cedar::ipaddr,
4395 };
4396 namespace NS {
4397 entity decimal;
4398 entity F {
4399 a: decimal,
4400 b: __cedar::decimal,
4401 };
4402 }
4403 ";
4404 let schema = assert_valid_cedar_schema(src);
4405 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4406 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4407 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("ipaddr"));
4408 });
4409 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4410 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4411 });
4412 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4413 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4414 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::decimal"));
4415 });
4416 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4417 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4418 });
4419
4420 let src_json = json!({
4421 "": {
4422 "entityTypes": {
4423 "ipaddr": {},
4424 "E": {
4425 "shape": {
4426 "type": "Record",
4427 "attributes": {
4428 "a": { "type": "Entity", "name": "ipaddr" },
4429 "b": { "type": "__cedar::ipaddr" },
4430 }
4431 }
4432 },
4433 },
4434 "actions": {}
4435 },
4436 "NS": {
4437 "entityTypes": {
4438 "decimal": {},
4439 "F": {
4440 "shape": {
4441 "type": "Record",
4442 "attributes": {
4443 "a": { "type": "Entity", "name": "decimal" },
4444 "b": { "type": "__cedar::decimal" },
4445 }
4446 }
4447 },
4448 },
4449 "actions": {}
4450 }
4451 });
4452 let schema = assert_valid_json_schema(src_json);
4453 let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4454 assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4455 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("ipaddr"));
4456 });
4457 assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4458 assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4459 });
4460 let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4461 assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4462 assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::decimal"));
4463 });
4464 assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4465 assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4466 });
4467 }
4468}
4469
4470#[cfg(test)]
4471mod test_resolver {
4472 use std::collections::HashMap;
4473
4474 use cedar_policy_core::{ast::InternalName, extensions::Extensions};
4475 use cool_asserts::assert_matches;
4476
4477 use super::{AllDefs, CommonTypeResolver};
4478 use crate::{
4479 err::SchemaError, json_schema, types::Type, ConditionalName, ValidatorSchemaFragment,
4480 };
4481
4482 fn resolve(schema_json: serde_json::Value) -> Result<HashMap<InternalName, Type>, SchemaError> {
4483 let sfrag = json_schema::Fragment::from_json_value(schema_json).unwrap();
4484 let schema: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
4485 sfrag.try_into().unwrap();
4486 let all_defs = AllDefs::single_fragment(&schema);
4487 let schema = schema.fully_qualify_type_references(&all_defs).unwrap();
4488 let mut defs = HashMap::new();
4489 for def in schema.0 {
4490 defs.extend(def.common_types.defs.into_iter());
4491 }
4492 let resolver = CommonTypeResolver::new(&defs);
4493 resolver
4494 .resolve(Extensions::all_available())
4495 .map(|map| map.into_iter().map(|(k, v)| (k.clone(), v)).collect())
4496 }
4497
4498 #[test]
4499 fn test_simple() {
4500 let schema = serde_json::json!(
4501 {
4502 "": {
4503 "entityTypes": {},
4504 "actions": {},
4505 "commonTypes": {
4506 "a" : {
4507 "type": "b"
4508 },
4509 "b": {
4510 "type": "Boolean"
4511 }
4512 }
4513 }
4514 }
4515 );
4516 let res = resolve(schema).unwrap();
4517 assert_eq!(
4518 res,
4519 HashMap::from_iter([
4520 ("a".parse().unwrap(), Type::primitive_boolean()),
4521 ("b".parse().unwrap(), Type::primitive_boolean())
4522 ])
4523 );
4524
4525 let schema = serde_json::json!(
4526 {
4527 "": {
4528 "entityTypes": {},
4529 "actions": {},
4530 "commonTypes": {
4531 "a" : {
4532 "type": "b"
4533 },
4534 "b": {
4535 "type": "c"
4536 },
4537 "c": {
4538 "type": "Boolean"
4539 }
4540 }
4541 }
4542 }
4543 );
4544 let res = resolve(schema).unwrap();
4545 assert_eq!(
4546 res,
4547 HashMap::from_iter([
4548 ("a".parse().unwrap(), Type::primitive_boolean()),
4549 ("b".parse().unwrap(), Type::primitive_boolean()),
4550 ("c".parse().unwrap(), Type::primitive_boolean())
4551 ])
4552 );
4553 }
4554
4555 #[test]
4556 fn test_set() {
4557 let schema = serde_json::json!(
4558 {
4559 "": {
4560 "entityTypes": {},
4561 "actions": {},
4562 "commonTypes": {
4563 "a" : {
4564 "type": "Set",
4565 "element": {
4566 "type": "b"
4567 }
4568 },
4569 "b": {
4570 "type": "Boolean"
4571 }
4572 }
4573 }
4574 }
4575 );
4576 let res = resolve(schema).unwrap();
4577 assert_eq!(
4578 res,
4579 HashMap::from_iter([
4580 ("a".parse().unwrap(), Type::set(Type::primitive_boolean())),
4581 ("b".parse().unwrap(), Type::primitive_boolean())
4582 ])
4583 );
4584 }
4585
4586 #[test]
4587 fn test_record() {
4588 let schema = serde_json::json!(
4589 {
4590 "": {
4591 "entityTypes": {},
4592 "actions": {},
4593 "commonTypes": {
4594 "a" : {
4595 "type": "Record",
4596 "attributes": {
4597 "foo": {
4598 "type": "b"
4599 }
4600 }
4601 },
4602 "b": {
4603 "type": "Boolean"
4604 }
4605 }
4606 }
4607 }
4608 );
4609 let res = resolve(schema).unwrap();
4610 assert_eq!(
4611 res,
4612 HashMap::from_iter([
4613 (
4614 "a".parse().unwrap(),
4615 Type::record_with_required_attributes(
4616 std::iter::once(("foo".into(), Type::primitive_boolean())),
4617 crate::types::OpenTag::ClosedAttributes
4618 )
4619 ),
4620 ("b".parse().unwrap(), Type::primitive_boolean())
4621 ])
4622 );
4623 }
4624
4625 #[test]
4626 fn test_names() {
4627 let schema = serde_json::json!(
4628 {
4629 "A": {
4630 "entityTypes": {},
4631 "actions": {},
4632 "commonTypes": {
4633 "a" : {
4634 "type": "B::a"
4635 }
4636 }
4637 },
4638 "B": {
4639 "entityTypes": {},
4640 "actions": {},
4641 "commonTypes": {
4642 "a" : {
4643 "type": "Boolean"
4644 }
4645 }
4646 }
4647 }
4648 );
4649 let res = resolve(schema).unwrap();
4650 assert_eq!(
4651 res,
4652 HashMap::from_iter([
4653 ("A::a".parse().unwrap(), Type::primitive_boolean()),
4654 ("B::a".parse().unwrap(), Type::primitive_boolean())
4655 ])
4656 );
4657 }
4658
4659 #[test]
4660 fn test_cycles() {
4661 let schema = serde_json::json!(
4663 {
4664 "": {
4665 "entityTypes": {},
4666 "actions": {},
4667 "commonTypes": {
4668 "a" : {
4669 "type": "a"
4670 }
4671 }
4672 }
4673 }
4674 );
4675 let res = resolve(schema);
4676 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4677
4678 let schema = serde_json::json!(
4680 {
4681 "": {
4682 "entityTypes": {},
4683 "actions": {},
4684 "commonTypes": {
4685 "a" : {
4686 "type": "b"
4687 },
4688 "b" : {
4689 "type": "a"
4690 }
4691 }
4692 }
4693 }
4694 );
4695 let res = resolve(schema);
4696 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4697
4698 let schema = serde_json::json!(
4700 {
4701 "": {
4702 "entityTypes": {},
4703 "actions": {},
4704 "commonTypes": {
4705 "a" : {
4706 "type": "b"
4707 },
4708 "b" : {
4709 "type": "c"
4710 },
4711 "c" : {
4712 "type": "a"
4713 }
4714 }
4715 }
4716 }
4717 );
4718 let res = resolve(schema);
4719 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4720
4721 let schema = serde_json::json!(
4723 {
4724 "A": {
4725 "entityTypes": {},
4726 "actions": {},
4727 "commonTypes": {
4728 "a" : {
4729 "type": "B::a"
4730 }
4731 }
4732 },
4733 "B": {
4734 "entityTypes": {},
4735 "actions": {},
4736 "commonTypes": {
4737 "a" : {
4738 "type": "A::a"
4739 }
4740 }
4741 }
4742 }
4743 );
4744 let res = resolve(schema);
4745 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4746
4747 let schema = serde_json::json!(
4749 {
4750 "A": {
4751 "entityTypes": {},
4752 "actions": {},
4753 "commonTypes": {
4754 "a" : {
4755 "type": "B::a"
4756 }
4757 }
4758 },
4759 "B": {
4760 "entityTypes": {},
4761 "actions": {},
4762 "commonTypes": {
4763 "a" : {
4764 "type": "C::a"
4765 }
4766 }
4767 },
4768 "C": {
4769 "entityTypes": {},
4770 "actions": {},
4771 "commonTypes": {
4772 "a" : {
4773 "type": "A::a"
4774 }
4775 }
4776 }
4777 }
4778 );
4779 let res = resolve(schema);
4780 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4781
4782 let schema = serde_json::json!(
4784 {
4785 "A": {
4786 "entityTypes": {},
4787 "actions": {},
4788 "commonTypes": {
4789 "a" : {
4790 "type": "B::a"
4791 }
4792 }
4793 },
4794 "B": {
4795 "entityTypes": {},
4796 "actions": {},
4797 "commonTypes": {
4798 "a" : {
4799 "type": "c"
4800 },
4801 "c": {
4802 "type": "A::a"
4803 }
4804 }
4805 }
4806 }
4807 );
4808 let res = resolve(schema);
4809 assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4810 }
4811}
4812
4813#[cfg(test)]
4814mod test_access {
4815 use super::*;
4816
4817 fn schema() -> ValidatorSchema {
4818 let src = r#"
4819 type Task = {
4820 "id": Long,
4821 "name": String,
4822 "state": String,
4823};
4824
4825type Tasks = Set<Task>;
4826entity List in [Application] = {
4827 "editors": Team,
4828 "name": String,
4829 "owner": User,
4830 "readers": Team,
4831 "tasks": Tasks,
4832};
4833entity Application;
4834entity User in [Team, Application] = {
4835 "joblevel": Long,
4836 "location": String,
4837};
4838
4839entity CoolList;
4840
4841entity Team in [Team, Application];
4842
4843action Read, Write, Create;
4844
4845action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
4846 principal: [User],
4847 resource : [List]
4848};
4849
4850action GetList in Read appliesTo {
4851 principal : [User],
4852 resource : [List, CoolList]
4853};
4854
4855action GetLists in Read appliesTo {
4856 principal : [User],
4857 resource : [Application]
4858};
4859
4860action CreateList in Create appliesTo {
4861 principal : [User],
4862 resource : [Application]
4863};
4864
4865 "#;
4866
4867 src.parse().unwrap()
4868 }
4869
4870 #[test]
4871 fn principals() {
4872 let schema = schema();
4873 let principals = schema.principals().collect::<HashSet<_>>();
4874 assert_eq!(principals.len(), 1);
4875 let user: EntityType = "User".parse().unwrap();
4876 assert!(principals.contains(&user));
4877 let principals = schema.principals().collect::<Vec<_>>();
4878 assert!(principals.len() > 1);
4879 assert!(principals.iter().all(|ety| **ety == user));
4880 }
4881
4882 #[test]
4883 fn empty_schema_principals_and_resources() {
4884 let empty: ValidatorSchema = "".parse().unwrap();
4885 assert!(empty.principals().collect::<Vec<_>>().is_empty());
4886 assert!(empty.resources().collect::<Vec<_>>().is_empty());
4887 }
4888
4889 #[test]
4890 fn resources() {
4891 let schema = schema();
4892 let resources = schema.resources().cloned().collect::<HashSet<_>>();
4893 let expected: HashSet<EntityType> = HashSet::from([
4894 "List".parse().unwrap(),
4895 "Application".parse().unwrap(),
4896 "CoolList".parse().unwrap(),
4897 ]);
4898 assert_eq!(resources, expected);
4899 }
4900
4901 #[test]
4902 fn principals_for_action() {
4903 let schema = schema();
4904 let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
4905 let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
4906 let got = schema
4907 .principals_for_action(&delete_list)
4908 .unwrap()
4909 .cloned()
4910 .collect::<Vec<_>>();
4911 assert_eq!(got, vec!["User".parse().unwrap()]);
4912 assert!(schema.principals_for_action(&delete_user).is_none());
4913 }
4914
4915 #[test]
4916 fn resources_for_action() {
4917 let schema = schema();
4918 let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
4919 let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
4920 let create_list: EntityUID = r#"Action::"CreateList""#.parse().unwrap();
4921 let get_list: EntityUID = r#"Action::"GetList""#.parse().unwrap();
4922 let got = schema
4923 .resources_for_action(&delete_list)
4924 .unwrap()
4925 .cloned()
4926 .collect::<Vec<_>>();
4927 assert_eq!(got, vec!["List".parse().unwrap()]);
4928 let got = schema
4929 .resources_for_action(&create_list)
4930 .unwrap()
4931 .cloned()
4932 .collect::<Vec<_>>();
4933 assert_eq!(got, vec!["Application".parse().unwrap()]);
4934 let got = schema
4935 .resources_for_action(&get_list)
4936 .unwrap()
4937 .cloned()
4938 .collect::<HashSet<_>>();
4939 assert_eq!(
4940 got,
4941 HashSet::from(["List".parse().unwrap(), "CoolList".parse().unwrap()])
4942 );
4943 assert!(schema.principals_for_action(&delete_user).is_none());
4944 }
4945
4946 #[test]
4947 fn principal_parents() {
4948 let schema = schema();
4949 let user: EntityType = "User".parse().unwrap();
4950 let parents = schema
4951 .ancestors(&user)
4952 .unwrap()
4953 .cloned()
4954 .collect::<HashSet<_>>();
4955 let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
4956 assert_eq!(parents, expected);
4957 let parents = schema
4958 .ancestors(&"List".parse().unwrap())
4959 .unwrap()
4960 .cloned()
4961 .collect::<HashSet<_>>();
4962 let expected = HashSet::from(["Application".parse().unwrap()]);
4963 assert_eq!(parents, expected);
4964 assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
4965 let parents = schema
4966 .ancestors(&"CoolList".parse().unwrap())
4967 .unwrap()
4968 .cloned()
4969 .collect::<HashSet<_>>();
4970 let expected = HashSet::from([]);
4971 assert_eq!(parents, expected);
4972 }
4973
4974 #[test]
4975 fn action_groups() {
4976 let schema = schema();
4977 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
4978 let expected = ["Read", "Write", "Create"]
4979 .into_iter()
4980 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
4981 .collect::<HashSet<EntityUID>>();
4982 assert_eq!(groups, expected);
4983 }
4984
4985 #[test]
4986 fn actions() {
4987 let schema = schema();
4988 let actions = schema.actions().cloned().collect::<HashSet<_>>();
4989 let expected = [
4990 "Read",
4991 "Write",
4992 "Create",
4993 "DeleteList",
4994 "EditShare",
4995 "UpdateList",
4996 "CreateTask",
4997 "UpdateTask",
4998 "DeleteTask",
4999 "GetList",
5000 "GetLists",
5001 "CreateList",
5002 ]
5003 .into_iter()
5004 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5005 .collect::<HashSet<EntityUID>>();
5006 assert_eq!(actions, expected);
5007 }
5008
5009 #[test]
5010 fn entities() {
5011 let schema = schema();
5012 let entities = schema
5013 .entity_types()
5014 .map(|(ty, _)| ty)
5015 .cloned()
5016 .collect::<HashSet<_>>();
5017 let expected = ["List", "Application", "User", "CoolList", "Team"]
5018 .into_iter()
5019 .map(|ty| ty.parse().unwrap())
5020 .collect::<HashSet<EntityType>>();
5021 assert_eq!(entities, expected);
5022 }
5023}
5024
5025#[cfg(test)]
5026mod test_access_namespace {
5027 use super::*;
5028
5029 fn schema() -> ValidatorSchema {
5030 let src = r#"
5031 namespace Foo {
5032 type Task = {
5033 "id": Long,
5034 "name": String,
5035 "state": String,
5036};
5037
5038type Tasks = Set<Task>;
5039entity List in [Application] = {
5040 "editors": Team,
5041 "name": String,
5042 "owner": User,
5043 "readers": Team,
5044 "tasks": Tasks,
5045};
5046entity Application;
5047entity User in [Team, Application] = {
5048 "joblevel": Long,
5049 "location": String,
5050};
5051
5052entity CoolList;
5053
5054entity Team in [Team, Application];
5055
5056action Read, Write, Create;
5057
5058action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5059 principal: [User],
5060 resource : [List]
5061};
5062
5063action GetList in Read appliesTo {
5064 principal : [User],
5065 resource : [List, CoolList]
5066};
5067
5068action GetLists in Read appliesTo {
5069 principal : [User],
5070 resource : [Application]
5071};
5072
5073action CreateList in Create appliesTo {
5074 principal : [User],
5075 resource : [Application]
5076};
5077 }
5078
5079 "#;
5080
5081 src.parse().unwrap()
5082 }
5083
5084 #[test]
5085 fn principals() {
5086 let schema = schema();
5087 let principals = schema.principals().collect::<HashSet<_>>();
5088 assert_eq!(principals.len(), 1);
5089 let user: EntityType = "Foo::User".parse().unwrap();
5090 assert!(principals.contains(&user));
5091 let principals = schema.principals().collect::<Vec<_>>();
5092 assert!(principals.len() > 1);
5093 assert!(principals.iter().all(|ety| **ety == user));
5094 }
5095
5096 #[test]
5097 fn empty_schema_principals_and_resources() {
5098 let empty: ValidatorSchema = "".parse().unwrap();
5099 assert!(empty.principals().collect::<Vec<_>>().is_empty());
5100 assert!(empty.resources().collect::<Vec<_>>().is_empty());
5101 }
5102
5103 #[test]
5104 fn resources() {
5105 let schema = schema();
5106 let resources = schema.resources().cloned().collect::<HashSet<_>>();
5107 let expected: HashSet<EntityType> = HashSet::from([
5108 "Foo::List".parse().unwrap(),
5109 "Foo::Application".parse().unwrap(),
5110 "Foo::CoolList".parse().unwrap(),
5111 ]);
5112 assert_eq!(resources, expected);
5113 }
5114
5115 #[test]
5116 fn principals_for_action() {
5117 let schema = schema();
5118 let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5119 let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5120 let got = schema
5121 .principals_for_action(&delete_list)
5122 .unwrap()
5123 .cloned()
5124 .collect::<Vec<_>>();
5125 assert_eq!(got, vec!["Foo::User".parse().unwrap()]);
5126 assert!(schema.principals_for_action(&delete_user).is_none());
5127 }
5128
5129 #[test]
5130 fn resources_for_action() {
5131 let schema = schema();
5132 let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5133 let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5134 let create_list: EntityUID = r#"Foo::Action::"CreateList""#.parse().unwrap();
5135 let get_list: EntityUID = r#"Foo::Action::"GetList""#.parse().unwrap();
5136 let got = schema
5137 .resources_for_action(&delete_list)
5138 .unwrap()
5139 .cloned()
5140 .collect::<Vec<_>>();
5141 assert_eq!(got, vec!["Foo::List".parse().unwrap()]);
5142 let got = schema
5143 .resources_for_action(&create_list)
5144 .unwrap()
5145 .cloned()
5146 .collect::<Vec<_>>();
5147 assert_eq!(got, vec!["Foo::Application".parse().unwrap()]);
5148 let got = schema
5149 .resources_for_action(&get_list)
5150 .unwrap()
5151 .cloned()
5152 .collect::<HashSet<_>>();
5153 assert_eq!(
5154 got,
5155 HashSet::from([
5156 "Foo::List".parse().unwrap(),
5157 "Foo::CoolList".parse().unwrap()
5158 ])
5159 );
5160 assert!(schema.principals_for_action(&delete_user).is_none());
5161 }
5162
5163 #[test]
5164 fn principal_parents() {
5165 let schema = schema();
5166 let user: EntityType = "Foo::User".parse().unwrap();
5167 let parents = schema
5168 .ancestors(&user)
5169 .unwrap()
5170 .cloned()
5171 .collect::<HashSet<_>>();
5172 let expected = HashSet::from([
5173 "Foo::Team".parse().unwrap(),
5174 "Foo::Application".parse().unwrap(),
5175 ]);
5176 assert_eq!(parents, expected);
5177 let parents = schema
5178 .ancestors(&"Foo::List".parse().unwrap())
5179 .unwrap()
5180 .cloned()
5181 .collect::<HashSet<_>>();
5182 let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
5183 assert_eq!(parents, expected);
5184 assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
5185 let parents = schema
5186 .ancestors(&"Foo::CoolList".parse().unwrap())
5187 .unwrap()
5188 .cloned()
5189 .collect::<HashSet<_>>();
5190 let expected = HashSet::from([]);
5191 assert_eq!(parents, expected);
5192 }
5193
5194 #[test]
5195 fn action_groups() {
5196 let schema = schema();
5197 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5198 let expected = ["Read", "Write", "Create"]
5199 .into_iter()
5200 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5201 .collect::<HashSet<EntityUID>>();
5202 assert_eq!(groups, expected);
5203 }
5204
5205 #[test]
5206 fn actions() {
5207 let schema = schema();
5208 let actions = schema.actions().cloned().collect::<HashSet<_>>();
5209 let expected = [
5210 "Read",
5211 "Write",
5212 "Create",
5213 "DeleteList",
5214 "EditShare",
5215 "UpdateList",
5216 "CreateTask",
5217 "UpdateTask",
5218 "DeleteTask",
5219 "GetList",
5220 "GetLists",
5221 "CreateList",
5222 ]
5223 .into_iter()
5224 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5225 .collect::<HashSet<EntityUID>>();
5226 assert_eq!(actions, expected);
5227 }
5228
5229 #[test]
5230 fn entities() {
5231 let schema = schema();
5232 let entities = schema
5233 .entity_types()
5234 .map(|(ty, _)| ty)
5235 .cloned()
5236 .collect::<HashSet<_>>();
5237 let expected = [
5238 "Foo::List",
5239 "Foo::Application",
5240 "Foo::User",
5241 "Foo::CoolList",
5242 "Foo::Team",
5243 ]
5244 .into_iter()
5245 .map(|ty| ty.parse().unwrap())
5246 .collect::<HashSet<EntityType>>();
5247 assert_eq!(entities, expected);
5248 }
5249}