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