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