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