1use std::collections::{hash_map::Entry, HashMap, HashSet};
24use std::sync::Arc;
25
26use cedar_policy_core::{
27 ast::{Eid, Entity, EntityType, EntityUID, Id, Name, RestrictedExpr},
28 entities::{Entities, JSONValue, TCComputation},
29 parser::err::ParseError,
30 transitive_closure::{compute_tc, TCNode},
31 FromNormalizedStr,
32};
33use serde::{Deserialize, Serialize};
34use serde_with::serde_as;
35use smol_str::SmolStr;
36
37use crate::{
38 schema_file_format,
39 types::{AttributeType, Attributes, EntityRecordKind, Type},
40 ActionEntityUID, ActionType, SchemaFragment, SchemaType, SchemaTypeVariant, TypeOfAttribute,
41 SCHEMA_TYPE_VARIANT_TAGS,
42};
43
44use super::err::*;
45use super::NamespaceDefinition;
46
47pub(crate) static ACTION_ENTITY_TYPE: &str = "Action";
52
53pub(crate) fn is_action_entity_type(ty: &Name) -> bool {
57 ty.basename().as_ref() == ACTION_ENTITY_TYPE
58}
59
60#[derive(Eq, PartialEq, Copy, Clone, Default)]
62pub enum ActionBehavior {
63 #[default]
66 ProhibitAttributes,
67 PermitAttributes,
69}
70
71#[derive(Debug)]
79pub struct ValidatorNamespaceDef {
80 namespace: Option<Name>,
85 type_defs: TypeDefs,
88 entity_types: EntityTypesDef,
90 actions: ActionsDef,
92}
93
94#[derive(Debug)]
97pub struct TypeDefs {
98 type_defs: HashMap<Name, Type>,
99}
100
101#[derive(Debug)]
104pub struct EntityTypesDef {
105 entity_types: HashMap<Name, EntityTypeFragment>,
106}
107
108#[derive(Debug)]
112pub struct EntityTypeFragment {
113 attributes: WithUnresolvedTypeDefs<Type>,
118 parents: HashSet<Name>,
123}
124
125#[derive(Debug)]
128pub struct ActionsDef {
129 actions: HashMap<EntityUID, ActionFragment>,
130}
131
132#[derive(Debug)]
133pub struct ActionFragment {
134 context: WithUnresolvedTypeDefs<Type>,
138 applies_to: ValidatorApplySpec,
140 parents: HashSet<EntityUID>,
142 attribute_types: Attributes,
144 attributes: HashMap<SmolStr, RestrictedExpr>,
148}
149
150type ResolveFunc<T> = dyn FnOnce(&HashMap<Name, Type>) -> Result<T>;
151pub enum WithUnresolvedTypeDefs<T> {
154 WithUnresolved(Box<ResolveFunc<T>>),
155 WithoutUnresolved(T),
156}
157
158impl<T: 'static> WithUnresolvedTypeDefs<T> {
159 pub fn new(f: impl FnOnce(&HashMap<Name, Type>) -> Result<T> + 'static) -> Self {
160 Self::WithUnresolved(Box::new(f))
161 }
162
163 pub fn map<U: 'static>(self, f: impl FnOnce(T) -> U + 'static) -> WithUnresolvedTypeDefs<U> {
164 match self {
165 Self::WithUnresolved(_) => {
166 WithUnresolvedTypeDefs::new(|type_defs| self.resolve_type_defs(type_defs).map(f))
167 }
168 Self::WithoutUnresolved(v) => WithUnresolvedTypeDefs::WithoutUnresolved(f(v)),
169 }
170 }
171
172 pub fn resolve_type_defs(self, type_defs: &HashMap<Name, Type>) -> Result<T> {
175 match self {
176 WithUnresolvedTypeDefs::WithUnresolved(f) => f(type_defs),
177 WithUnresolvedTypeDefs::WithoutUnresolved(v) => Ok(v),
178 }
179 }
180}
181
182impl<T: 'static> From<T> for WithUnresolvedTypeDefs<T> {
183 fn from(value: T) -> Self {
184 Self::WithoutUnresolved(value)
185 }
186}
187
188impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedTypeDefs<T> {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match self {
191 WithUnresolvedTypeDefs::WithUnresolved(_) => f.debug_tuple("WithUnresolved").finish(),
192 WithUnresolvedTypeDefs::WithoutUnresolved(v) => {
193 f.debug_tuple("WithoutUnresolved").field(v).finish()
194 }
195 }
196 }
197}
198
199impl TryInto<ValidatorNamespaceDef> for NamespaceDefinition {
200 type Error = SchemaError;
201
202 fn try_into(self) -> Result<ValidatorNamespaceDef> {
203 ValidatorNamespaceDef::from_namespace_definition(None, self, ActionBehavior::default())
204 }
205}
206
207impl ValidatorNamespaceDef {
208 pub fn from_namespace_definition(
212 namespace: Option<SmolStr>,
213 namespace_def: NamespaceDefinition,
214 action_behavior: ActionBehavior,
215 ) -> Result<ValidatorNamespaceDef> {
216 let mut e_types_ids: HashSet<SmolStr> = HashSet::new();
218 for name in namespace_def.entity_types.keys() {
219 if !e_types_ids.insert(name.clone()) {
220 return Err(SchemaError::DuplicateEntityType(name.to_string()));
222 }
223 }
224 let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
225 for name in namespace_def.actions.keys() {
226 if !a_name_eids.insert(name.clone()) {
227 return Err(SchemaError::DuplicateAction(name.to_string()));
229 }
230 }
231
232 let schema_namespace = match namespace.as_deref() {
233 None => None,
234 Some("") => None, Some(ns) => {
236 Some(Name::from_normalized_str(ns).map_err(SchemaError::NamespaceParseError)?)
237 }
238 };
239
240 Self::check_action_behavior(&namespace_def, action_behavior)?;
243
244 let type_defs =
247 Self::build_type_defs(namespace_def.common_types, schema_namespace.as_ref())?;
248 let actions = Self::build_action_ids(namespace_def.actions, schema_namespace.as_ref())?;
249 let entity_types =
250 Self::build_entity_types(namespace_def.entity_types, schema_namespace.as_ref())?;
251
252 Ok(ValidatorNamespaceDef {
253 namespace: schema_namespace,
254 type_defs,
255 entity_types,
256 actions,
257 })
258 }
259
260 fn is_builtin_type_name(name: &SmolStr) -> bool {
261 SCHEMA_TYPE_VARIANT_TAGS
262 .iter()
263 .any(|type_name| name == type_name)
264 }
265
266 fn build_type_defs(
267 schema_file_type_def: HashMap<SmolStr, SchemaType>,
268 schema_namespace: Option<&Name>,
269 ) -> Result<TypeDefs> {
270 let type_defs = schema_file_type_def
271 .into_iter()
272 .map(|(name_str, schema_ty)| -> Result<_> {
273 if Self::is_builtin_type_name(&name_str) {
274 return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
275 }
276 let name = Self::parse_unqualified_name_with_namespace(
277 &name_str,
278 schema_namespace.cloned(),
279 )
280 .map_err(SchemaError::CommonTypeParseError)?;
281 let ty = Self::try_schema_type_into_validator_type(schema_namespace, schema_ty)?
282 .resolve_type_defs(&HashMap::new())?;
283 Ok((name, ty))
284 })
285 .collect::<Result<HashMap<_, _>>>()?;
286 Ok(TypeDefs { type_defs })
287 }
288
289 fn build_entity_types(
293 schema_files_types: HashMap<SmolStr, schema_file_format::EntityType>,
294 schema_namespace: Option<&Name>,
295 ) -> Result<EntityTypesDef> {
296 Ok(EntityTypesDef {
297 entity_types: schema_files_types
298 .into_iter()
299 .map(|(name_str, entity_type)| -> Result<_> {
300 let name = Self::parse_unqualified_name_with_namespace(
301 &name_str,
302 schema_namespace.cloned(),
303 )
304 .map_err(SchemaError::EntityTypeParseError)?;
305
306 let parents = entity_type
307 .member_of_types
308 .iter()
309 .map(|parent| -> Result<_> {
310 Self::parse_possibly_qualified_name_with_default_namespace(
311 parent,
312 schema_namespace,
313 )
314 .map_err(SchemaError::EntityTypeParseError)
315 })
316 .collect::<Result<HashSet<_>>>()?;
317
318 let attributes = Self::try_schema_type_into_validator_type(
319 schema_namespace,
320 entity_type.shape.into_inner(),
321 )?;
322
323 Ok((
324 name,
325 EntityTypeFragment {
326 attributes,
327 parents,
328 },
329 ))
330 })
331 .collect::<Result<HashMap<_, _>>>()?,
332 })
333 }
334
335 fn jsonval_to_type_helper(v: &JSONValue) -> Result<Type> {
341 match v {
342 JSONValue::Bool(_) => Ok(Type::primitive_boolean()),
343 JSONValue::Long(_) => Ok(Type::primitive_long()),
344 JSONValue::String(_) => Ok(Type::primitive_string()),
345 JSONValue::Record(r) => {
346 let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
347 for (k, v_prime) in r {
348 let t = Self::jsonval_to_type_helper(v_prime);
349 match t {
350 Ok(ty) => required_attrs.insert(k.clone(), ty),
351 Err(e) => return Err(e),
352 };
353 }
354 Ok(Type::EntityOrRecord(EntityRecordKind::Record {
355 attrs: Attributes::with_required_attributes(required_attrs),
356 }))
357 }
358 JSONValue::Set(v) => match v.get(0) {
359 None => Err(SchemaError::ActionEntityAttributeEmptySet),
361 Some(element) => {
362 let element_type = Self::jsonval_to_type_helper(element);
363 match element_type {
364 Ok(t) => Ok(Type::Set {
365 element_type: Some(Box::new(t)),
366 }),
367 Err(_) => element_type,
368 }
369 }
370 },
371 _ => Err(SchemaError::ActionEntityAttributeUnsupportedType),
372 }
373 }
374
375 fn convert_attr_jsonval_map_to_attributes(
377 m: HashMap<SmolStr, JSONValue>,
378 ) -> Result<(Attributes, HashMap<SmolStr, RestrictedExpr>)> {
379 let mut attr_types: HashMap<SmolStr, Type> = HashMap::new();
380 let mut attr_values: HashMap<SmolStr, RestrictedExpr> = HashMap::new();
381
382 for (k, v) in m {
383 let t = Self::jsonval_to_type_helper(&v);
384 match t {
385 Ok(ty) => attr_types.insert(k.clone(), ty),
386 Err(e) => return Err(e),
387 };
388
389 let e = v.into_expr().expect("`Self::jsonval_to_type_helper` will always return `Err` for a `JSONValue` that might make `into_expr` return `Err`");
398 attr_values.insert(k.clone(), e);
399 }
400 Ok((
401 Attributes::with_required_attributes(attr_types),
402 attr_values,
403 ))
404 }
405
406 fn build_action_ids(
410 schema_file_actions: HashMap<SmolStr, ActionType>,
411 schema_namespace: Option<&Name>,
412 ) -> Result<ActionsDef> {
413 Ok(ActionsDef {
414 actions: schema_file_actions
415 .into_iter()
416 .map(|(action_id_str, action_type)| -> Result<_> {
417 let action_id = Self::parse_action_id_with_namespace(
418 &ActionEntityUID::default_type(action_id_str),
419 schema_namespace,
420 )?;
421
422 let (principal_types, resource_types, context) = action_type
423 .applies_to
424 .map(|applies_to| {
425 (
426 applies_to.principal_types,
427 applies_to.resource_types,
428 applies_to.context,
429 )
430 })
431 .unwrap_or_default();
432
433 let applies_to = ValidatorApplySpec::new(
437 Self::parse_apply_spec_type_list(principal_types, schema_namespace)?,
438 Self::parse_apply_spec_type_list(resource_types, schema_namespace)?,
439 );
440
441 let context = Self::try_schema_type_into_validator_type(
442 schema_namespace,
443 context.into_inner(),
444 )?;
445
446 let parents = action_type
447 .member_of
448 .unwrap_or_default()
449 .iter()
450 .map(|parent| -> Result<_> {
451 Self::parse_action_id_with_namespace(parent, schema_namespace)
452 })
453 .collect::<Result<HashSet<_>>>()?;
454
455 let (attribute_types, attributes) =
456 Self::convert_attr_jsonval_map_to_attributes(
457 action_type.attributes.unwrap_or_default(),
458 )?;
459
460 Ok((
461 action_id,
462 ActionFragment {
463 context,
464 applies_to,
465 parents,
466 attribute_types,
467 attributes,
468 },
469 ))
470 })
471 .collect::<Result<HashMap<_, _>>>()?,
472 })
473 }
474
475 fn check_action_behavior(
481 schema_file: &NamespaceDefinition,
482 action_behavior: ActionBehavior,
483 ) -> Result<()> {
484 if schema_file
485 .entity_types
486 .iter()
487 .any(|(name, _)| name == ACTION_ENTITY_TYPE)
491 {
492 return Err(SchemaError::ActionEntityTypeDeclared);
493 }
494 if action_behavior == ActionBehavior::ProhibitAttributes {
495 let mut actions_with_attributes: Vec<String> = Vec::new();
496 for (name, a) in &schema_file.actions {
497 if a.attributes.is_some() {
498 actions_with_attributes.push(name.to_string());
499 }
500 }
501 if !actions_with_attributes.is_empty() {
502 return Err(SchemaError::ActionEntityAttributes(actions_with_attributes));
503 }
504 }
505
506 Ok(())
507 }
508
509 fn parse_record_attributes(
514 schema_namespace: Option<&Name>,
515 attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
516 ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
517 let attrs_with_type_defs = attrs
518 .into_iter()
519 .map(|(attr, ty)| -> Result<_> {
520 Ok((
521 attr,
522 (
523 Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
524 ty.required,
525 ),
526 ))
527 })
528 .collect::<Result<Vec<_>>>()?;
529 Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
530 attrs_with_type_defs
531 .into_iter()
532 .map(|(s, (attr_ty, is_req))| {
533 attr_ty
534 .resolve_type_defs(typ_defs)
535 .map(|ty| (s, AttributeType::new(ty, is_req)))
536 })
537 .collect::<Result<Vec<_>>>()
538 .map(Attributes::with_attributes)
539 }))
540 }
541
542 fn parse_apply_spec_type_list(
547 types: Option<Vec<SmolStr>>,
548 namespace: Option<&Name>,
549 ) -> Result<HashSet<EntityType>> {
550 types
551 .map(|types| {
552 types
553 .iter()
554 .map(|ty_str| {
558 Ok(EntityType::Concrete(
559 Self::parse_possibly_qualified_name_with_default_namespace(
560 ty_str, namespace,
561 )
562 .map_err(SchemaError::EntityTypeParseError)?,
563 ))
564 })
565 .collect::<Result<HashSet<_>>>()
567 })
568 .unwrap_or_else(|| Ok(HashSet::from([EntityType::Unspecified])))
569 }
570
571 pub(crate) fn parse_possibly_qualified_name_with_default_namespace(
576 name_str: &SmolStr,
577 default_namespace: Option<&Name>,
578 ) -> std::result::Result<Name, Vec<ParseError>> {
579 let name = Name::from_normalized_str(name_str)?;
580
581 let qualified_name = if name.namespace_components().next().is_some() {
582 name
584 } else {
585 match default_namespace {
588 Some(namespace) => {
589 Name::type_in_namespace(name.basename().clone(), namespace.clone())
590 }
591 None => name,
592 }
593 };
594
595 Ok(qualified_name)
596 }
597
598 fn parse_unqualified_name_with_namespace(
602 type_name: impl AsRef<str>,
603 namespace: Option<Name>,
604 ) -> std::result::Result<Name, Vec<ParseError>> {
605 let type_name = Id::from_normalized_str(type_name.as_ref())?;
606 match namespace {
607 Some(namespace) => Ok(Name::type_in_namespace(type_name, namespace)),
608 None => Ok(Name::unqualified_name(type_name)),
609 }
610 }
611
612 fn parse_action_id_with_namespace(
618 action_id: &ActionEntityUID,
619 namespace: Option<&Name>,
620 ) -> Result<EntityUID> {
621 let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
622 Self::parse_possibly_qualified_name_with_default_namespace(action_ty, namespace)
623 .map_err(SchemaError::EntityTypeParseError)?
624 } else {
625 let id = Id::from_normalized_str(ACTION_ENTITY_TYPE).expect(
626 "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
627 );
628 match namespace {
629 Some(namespace) => Name::type_in_namespace(id, namespace.clone()),
630 None => Name::unqualified_name(id),
631 }
632 };
633 Ok(EntityUID::from_components(
634 namespaced_action_type,
635 Eid::new(action_id.id.clone()),
636 ))
637 }
638
639 pub(crate) fn try_schema_type_into_validator_type(
645 default_namespace: Option<&Name>,
646 schema_ty: SchemaType,
647 ) -> Result<WithUnresolvedTypeDefs<Type>> {
648 match schema_ty {
649 SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
650 SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
651 SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
652 SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
653 Self::try_schema_type_into_validator_type(default_namespace, *element)?
654 .map(Type::set),
655 ),
656 SchemaType::Type(SchemaTypeVariant::Record {
657 attributes,
658 additional_attributes,
659 }) => {
660 if additional_attributes {
661 Err(SchemaError::UnsupportedSchemaFeature(
662 UnsupportedFeature::OpenRecordsAndEntities,
663 ))
664 } else {
665 Ok(
666 Self::parse_record_attributes(default_namespace, attributes)?
667 .map(Type::record_with_attributes),
668 )
669 }
670 }
671 SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
672 let entity_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
673 &name,
674 default_namespace,
675 )
676 .map_err(SchemaError::EntityTypeParseError)?;
677 Ok(Type::named_entity_reference(entity_type_name).into())
678 }
679 SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
680 let extension_type_name = Name::from_normalized_str(&name)
681 .map_err(SchemaError::ExtensionTypeParseError)?;
682 Ok(Type::extension(extension_type_name).into())
683 }
684 SchemaType::TypeDef { type_name } => {
685 let defined_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
686 &type_name,
687 default_namespace,
688 )
689 .map_err(SchemaError::CommonTypeParseError)?;
690 Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
691 typ_defs.get(&defined_type_name).cloned().ok_or(
692 SchemaError::UndeclaredCommonType(HashSet::from([type_name.to_string()])),
693 )
694 }))
695 }
696 }
697 }
698
699 pub fn namespace(&self) -> &Option<Name> {
701 &self.namespace
702 }
703}
704
705#[derive(Debug)]
706pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
707
708impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
709 type Error = SchemaError;
710
711 fn try_into(self) -> Result<ValidatorSchemaFragment> {
712 ValidatorSchemaFragment::from_schema_fragment(self, ActionBehavior::default())
713 }
714}
715
716impl ValidatorSchemaFragment {
717 pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
718 Self(namespaces.into_iter().collect())
719 }
720
721 pub fn from_schema_fragment(
722 fragment: SchemaFragment,
723 action_behavior: ActionBehavior,
724 ) -> Result<Self> {
725 Ok(Self(
726 fragment
727 .0
728 .into_iter()
729 .map(|(fragment_ns, ns_def)| {
730 ValidatorNamespaceDef::from_namespace_definition(
731 Some(fragment_ns),
732 ns_def,
733 action_behavior,
734 )
735 })
736 .collect::<Result<Vec<_>>>()?,
737 ))
738 }
739
740 pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
742 self.0.iter().map(|d| d.namespace())
743 }
744}
745
746#[serde_as]
747#[derive(Clone, Debug, Serialize)]
748pub struct ValidatorSchema {
749 #[serde(rename = "entityTypes")]
751 #[serde_as(as = "Vec<(_, _)>")]
752 entity_types: HashMap<Name, ValidatorEntityType>,
753
754 #[serde(rename = "actionIds")]
756 #[serde_as(as = "Vec<(_, _)>")]
757 action_ids: HashMap<EntityUID, ValidatorActionId>,
758}
759
760impl std::str::FromStr for ValidatorSchema {
761 type Err = SchemaError;
762
763 fn from_str(s: &str) -> Result<Self> {
764 serde_json::from_str::<SchemaFragment>(s)?.try_into()
765 }
766}
767
768impl TryFrom<NamespaceDefinition> for ValidatorSchema {
769 type Error = SchemaError;
770
771 fn try_from(nsd: NamespaceDefinition) -> Result<ValidatorSchema> {
772 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
773 nsd.try_into()?
774 ])])
775 }
776}
777
778impl TryFrom<SchemaFragment> for ValidatorSchema {
779 type Error = SchemaError;
780
781 fn try_from(frag: SchemaFragment) -> Result<ValidatorSchema> {
782 ValidatorSchema::from_schema_fragments([frag.try_into()?])
783 }
784}
785
786impl ValidatorSchema {
787 pub fn empty() -> ValidatorSchema {
789 Self {
790 entity_types: HashMap::new(),
791 action_ids: HashMap::new(),
792 }
793 }
794
795 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
798 Self::from_schema_file(
799 SchemaFragment::from_json_value(json)?,
800 ActionBehavior::default(),
801 )
802 }
803
804 pub fn from_file(file: impl std::io::Read) -> Result<Self> {
806 Self::from_schema_file(SchemaFragment::from_file(file)?, ActionBehavior::default())
807 }
808
809 pub fn from_schema_file(
810 schema_file: SchemaFragment,
811 action_behavior: ActionBehavior,
812 ) -> Result<ValidatorSchema> {
813 Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
814 schema_file,
815 action_behavior,
816 )?])
817 }
818
819 pub fn from_schema_fragments(
821 fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
822 ) -> Result<ValidatorSchema> {
823 let mut type_defs = HashMap::new();
824 let mut entity_type_fragments = HashMap::new();
825 let mut action_fragments = HashMap::new();
826
827 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
828 for (name, ty) in ns_def.type_defs.type_defs {
834 match type_defs.entry(name) {
835 Entry::Vacant(v) => v.insert(ty),
836 Entry::Occupied(o) => {
837 return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
838 }
839 };
840 }
841
842 for (name, entity_type) in ns_def.entity_types.entity_types {
843 match entity_type_fragments.entry(name) {
844 Entry::Vacant(v) => v.insert(entity_type),
845 Entry::Occupied(o) => {
846 return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
847 }
848 };
849 }
850
851 for (action_euid, action) in ns_def.actions.actions {
852 match action_fragments.entry(action_euid) {
853 Entry::Vacant(v) => v.insert(action),
854 Entry::Occupied(o) => {
855 return Err(SchemaError::DuplicateAction(o.key().to_string()))
856 }
857 };
858 }
859 }
860
861 let mut entity_children = HashMap::new();
864 for (name, entity_type) in entity_type_fragments.iter() {
865 for parent in entity_type.parents.iter() {
866 entity_children
867 .entry(parent.clone())
868 .or_insert_with(HashSet::new)
869 .insert(name.clone());
870 }
871 }
872
873 let mut entity_types = entity_type_fragments
874 .into_iter()
875 .map(|(name, entity_type)| -> Result<_> {
876 let descendants = entity_children.remove(&name).unwrap_or_default();
886 Ok((
887 name.clone(),
888 ValidatorEntityType {
889 name: name.clone(),
890 descendants,
891 attributes: Self::record_attributes_or_none(
892 entity_type.attributes.resolve_type_defs(&type_defs)?,
893 )
894 .ok_or(SchemaError::ContextOrShapeNotRecord(
895 ContextOrShape::EntityTypeShape(name),
896 ))?,
897 },
898 ))
899 })
900 .collect::<Result<HashMap<_, _>>>()?;
901
902 let mut action_children = HashMap::new();
903 for (euid, action) in action_fragments.iter() {
904 for parent in action.parents.iter() {
905 action_children
906 .entry(parent.clone())
907 .or_insert_with(HashSet::new)
908 .insert(euid.clone());
909 }
910 }
911 let mut action_ids = action_fragments
912 .into_iter()
913 .map(|(name, action)| -> Result<_> {
914 let descendants = action_children.remove(&name).unwrap_or_default();
915
916 Ok((
917 name.clone(),
918 ValidatorActionId {
919 name: name.clone(),
920 applies_to: action.applies_to,
921 descendants,
922 context: Self::record_attributes_or_none(
923 action.context.resolve_type_defs(&type_defs)?,
924 )
925 .ok_or(SchemaError::ContextOrShapeNotRecord(
926 ContextOrShape::ActionContext(name),
927 ))?,
928 attribute_types: action.attribute_types,
929 attributes: action.attributes,
930 },
931 ))
932 })
933 .collect::<Result<HashMap<_, _>>>()?;
934
935 compute_tc(&mut entity_types, false)?;
938 compute_tc(&mut action_ids, true)?;
941
942 Self::check_for_undeclared(
949 &entity_types,
950 entity_children.into_keys(),
951 &action_ids,
952 action_children.into_keys(),
953 )?;
954
955 Ok(ValidatorSchema {
956 entity_types,
957 action_ids,
958 })
959 }
960
961 fn check_for_undeclared(
966 entity_types: &HashMap<Name, ValidatorEntityType>,
967 undeclared_parent_entities: impl IntoIterator<Item = Name>,
968 action_ids: &HashMap<EntityUID, ValidatorActionId>,
969 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
970 ) -> Result<()> {
971 let mut undeclared_e = undeclared_parent_entities
976 .into_iter()
977 .map(|n| n.to_string())
978 .collect::<HashSet<_>>();
979 for entity_type in entity_types.values() {
985 for (_, attr_typ) in entity_type.attributes() {
986 Self::check_undeclared_in_type(
987 &attr_typ.attr_type,
988 entity_types,
989 &mut undeclared_e,
990 );
991 }
992 }
993
994 let undeclared_a = undeclared_parent_actions
996 .into_iter()
997 .map(|n| n.to_string())
998 .collect::<HashSet<_>>();
999 for action in action_ids.values() {
1003 for (_, attr_typ) in action.context.iter() {
1004 Self::check_undeclared_in_type(
1005 &attr_typ.attr_type,
1006 entity_types,
1007 &mut undeclared_e,
1008 );
1009 }
1010
1011 for p_entity in action.applies_to.applicable_principal_types() {
1012 match p_entity {
1013 EntityType::Concrete(p_entity) => {
1014 if !entity_types.contains_key(p_entity) {
1015 undeclared_e.insert(p_entity.to_string());
1016 }
1017 }
1018 EntityType::Unspecified => (),
1019 }
1020 }
1021
1022 for r_entity in action.applies_to.applicable_resource_types() {
1023 match r_entity {
1024 EntityType::Concrete(r_entity) => {
1025 if !entity_types.contains_key(r_entity) {
1026 undeclared_e.insert(r_entity.to_string());
1027 }
1028 }
1029 EntityType::Unspecified => (),
1030 }
1031 }
1032 }
1033 if !undeclared_e.is_empty() {
1034 return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
1035 }
1036 if !undeclared_a.is_empty() {
1037 return Err(SchemaError::UndeclaredActions(undeclared_a));
1038 }
1039
1040 Ok(())
1041 }
1042
1043 fn record_attributes_or_none(ty: Type) -> Option<Attributes> {
1044 match ty {
1045 Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => Some(attrs),
1046 _ => None,
1047 }
1048 }
1049
1050 fn check_undeclared_in_type(
1054 ty: &Type,
1055 entity_types: &HashMap<Name, ValidatorEntityType>,
1056 undeclared_types: &mut HashSet<String>,
1057 ) {
1058 match ty {
1059 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
1060 for name in lub.iter() {
1061 if !entity_types.contains_key(name) {
1062 undeclared_types.insert(name.to_string());
1063 }
1064 }
1065 }
1066
1067 Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => {
1068 for (_, attr_ty) in attrs.iter() {
1069 Self::check_undeclared_in_type(
1070 &attr_ty.attr_type,
1071 entity_types,
1072 undeclared_types,
1073 );
1074 }
1075 }
1076
1077 Type::Set {
1078 element_type: Some(element_type),
1079 } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
1080
1081 _ => (),
1082 }
1083 }
1084
1085 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
1087 self.action_ids.get(action_id)
1088 }
1089
1090 pub fn get_entity_type(&self, entity_type_id: &Name) -> Option<&ValidatorEntityType> {
1092 self.entity_types.get(entity_type_id)
1093 }
1094
1095 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
1097 self.action_ids.contains_key(action_id)
1098 }
1099
1100 pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
1102 self.entity_types.contains_key(entity_type)
1103 }
1104
1105 pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
1107 self.action_ids.keys()
1108 }
1109
1110 pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
1112 self.entity_types.keys()
1113 }
1114
1115 pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
1117 self.entity_types.iter()
1118 }
1119
1120 pub(crate) fn get_entity_eq<'a, H, K>(&self, var: H, euid: EntityUID) -> Option<K>
1123 where
1124 H: 'a + HeadVar<K>,
1125 K: 'a,
1126 {
1127 var.get_euid_component(euid)
1128 }
1129
1130 pub(crate) fn get_entities_in<'a, H, K>(
1133 &'a self,
1134 var: H,
1135 euid: EntityUID,
1136 ) -> impl Iterator<Item = K> + 'a
1137 where
1138 H: 'a + HeadVar<K>,
1139 K: 'a + Clone,
1140 {
1141 var.get_descendants_if_present(self, euid.clone())
1142 .into_iter()
1143 .flatten()
1144 .map(Clone::clone)
1145 .chain(var.get_euid_component_if_present(self, euid).into_iter())
1146 }
1147
1148 pub(crate) fn get_entities_in_set<'a, H, K>(
1151 &'a self,
1152 var: H,
1153 euids: impl IntoIterator<Item = EntityUID> + 'a,
1154 ) -> impl Iterator<Item = K> + 'a
1155 where
1156 H: 'a + HeadVar<K>,
1157 K: 'a + Clone,
1158 {
1159 euids
1160 .into_iter()
1161 .flat_map(move |e| self.get_entities_in(var, e))
1162 }
1163
1164 pub fn get_context_schema(
1169 &self,
1170 action: &EntityUID,
1171 ) -> Option<impl cedar_policy_core::entities::ContextSchema> {
1172 self.get_action_id(action).map(|action_id| {
1173 crate::types::Type::record_with_attributes(
1174 action_id
1175 .context
1176 .iter()
1177 .map(|(k, v)| (k.clone(), v.clone())),
1178 )
1179 })
1180 }
1181
1182 fn action_entities_iter(&self) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
1184 let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
1190 for (action_euid, action_def) in &self.action_ids {
1191 for descendant in &action_def.descendants {
1192 action_ancestors
1193 .entry(descendant)
1194 .or_default()
1195 .insert(action_euid.clone());
1196 }
1197 }
1198 self.action_ids.iter().map(move |(action_id, action)| {
1199 Entity::new(
1200 action_id.clone(),
1201 action.attributes.clone(),
1202 action_ancestors.remove(action_id).unwrap_or_default(),
1203 )
1204 })
1205 }
1206
1207 pub fn action_entities(&self) -> cedar_policy_core::entities::Result<Entities> {
1210 Entities::from_entities(
1211 self.action_entities_iter(),
1212 TCComputation::AssumeAlreadyComputed,
1213 )
1214 }
1215}
1216
1217pub struct CoreSchema<'a> {
1219 schema: &'a ValidatorSchema,
1221 actions: HashMap<EntityUID, Arc<Entity>>,
1227}
1228
1229impl<'a> CoreSchema<'a> {
1230 pub fn new(schema: &'a ValidatorSchema) -> Self {
1231 Self {
1232 actions: schema
1233 .action_entities_iter()
1234 .map(|e| (e.uid(), Arc::new(e)))
1235 .collect(),
1236 schema,
1237 }
1238 }
1239}
1240
1241impl<'a> cedar_policy_core::entities::Schema for CoreSchema<'a> {
1242 type EntityTypeDescription = EntityTypeDescription;
1243
1244 fn entity_type(
1245 &self,
1246 entity_type: &cedar_policy_core::ast::EntityType,
1247 ) -> Option<EntityTypeDescription> {
1248 match entity_type {
1249 cedar_policy_core::ast::EntityType::Unspecified => None, cedar_policy_core::ast::EntityType::Concrete(name) => {
1251 EntityTypeDescription::new(self.schema, name)
1252 }
1253 }
1254 }
1255
1256 fn action(&self, action: &EntityUID) -> Option<Arc<cedar_policy_core::ast::Entity>> {
1257 self.actions.get(action).map(Arc::clone)
1258 }
1259
1260 fn entity_types_with_basename<'b>(
1261 &'b self,
1262 basename: &'b Id,
1263 ) -> Box<dyn Iterator<Item = EntityType> + 'b> {
1264 Box::new(self.schema.entity_types().filter_map(move |(name, _)| {
1265 if name.basename() == basename {
1266 Some(EntityType::Concrete(name.clone()))
1267 } else {
1268 None
1269 }
1270 }))
1271 }
1272}
1273
1274pub struct EntityTypeDescription {
1276 core_type: cedar_policy_core::ast::EntityType,
1278 validator_type: ValidatorEntityType,
1280 allowed_parent_types: Arc<HashSet<cedar_policy_core::ast::EntityType>>,
1283}
1284
1285impl EntityTypeDescription {
1286 pub fn new(schema: &ValidatorSchema, type_name: &Name) -> Option<Self> {
1289 Some(Self {
1290 core_type: cedar_policy_core::ast::EntityType::Concrete(type_name.clone()),
1291 validator_type: schema.get_entity_type(type_name).cloned()?,
1292 allowed_parent_types: {
1293 let mut set = HashSet::new();
1294 for (possible_parent_typename, possible_parent_et) in &schema.entity_types {
1295 if possible_parent_et.descendants.contains(type_name) {
1296 set.insert(cedar_policy_core::ast::EntityType::Concrete(
1297 possible_parent_typename.clone(),
1298 ));
1299 }
1300 }
1301 Arc::new(set)
1302 },
1303 })
1304 }
1305}
1306
1307impl cedar_policy_core::entities::EntityTypeDescription for EntityTypeDescription {
1308 fn entity_type(&self) -> cedar_policy_core::ast::EntityType {
1309 self.core_type.clone()
1310 }
1311
1312 fn attr_type(&self, attr: &str) -> Option<cedar_policy_core::entities::SchemaType> {
1313 let attr_type: &crate::types::Type = &self.validator_type.attr(attr)?.attr_type;
1314 let core_schema_type: cedar_policy_core::entities::SchemaType = attr_type
1315 .clone()
1316 .try_into()
1317 .expect("failed to convert validator type into Core SchemaType");
1318 debug_assert!(attr_type.is_consistent_with(&core_schema_type));
1319 Some(core_schema_type)
1320 }
1321
1322 fn required_attrs<'s>(&'s self) -> Box<dyn Iterator<Item = SmolStr> + 's> {
1323 Box::new(
1324 self.validator_type
1325 .attributes
1326 .iter()
1327 .filter(|(_, ty)| ty.is_required)
1328 .map(|(attr, _)| attr.clone()),
1329 )
1330 }
1331
1332 fn allowed_parent_types(&self) -> Arc<HashSet<cedar_policy_core::ast::EntityType>> {
1333 Arc::clone(&self.allowed_parent_types)
1334 }
1335}
1336
1337impl cedar_policy_core::entities::ContextSchema for crate::types::Type {
1339 fn context_type(&self) -> cedar_policy_core::entities::SchemaType {
1340 self.clone()
1341 .try_into()
1342 .expect("failed to convert validator type into Core SchemaType")
1343 }
1344}
1345
1346#[derive(Clone, Debug, Serialize)]
1350pub struct ValidatorEntityType {
1351 pub(crate) name: Name,
1353
1354 pub descendants: HashSet<Name>,
1359
1360 pub(crate) attributes: Attributes,
1363}
1364
1365impl ValidatorEntityType {
1366 pub fn attr(&self, attr: &str) -> Option<&AttributeType> {
1368 self.attributes.get_attr(attr)
1369 }
1370
1371 pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1373 self.attributes.iter()
1374 }
1375}
1376
1377impl TCNode<Name> for ValidatorEntityType {
1378 fn get_key(&self) -> Name {
1379 self.name.clone()
1380 }
1381
1382 fn add_edge_to(&mut self, k: Name) {
1383 self.descendants.insert(k);
1384 }
1385
1386 fn out_edges(&self) -> Box<dyn Iterator<Item = &Name> + '_> {
1387 Box::new(self.descendants.iter())
1388 }
1389
1390 fn has_edge_to(&self, e: &Name) -> bool {
1391 self.descendants.contains(e)
1392 }
1393}
1394
1395#[derive(Clone, Debug, Serialize)]
1399pub struct ValidatorActionId {
1400 pub(crate) name: EntityUID,
1402
1403 #[serde(rename = "appliesTo")]
1405 pub(crate) applies_to: ValidatorApplySpec,
1406
1407 pub(crate) descendants: HashSet<EntityUID>,
1412
1413 pub(crate) context: Attributes,
1416
1417 pub(crate) attribute_types: Attributes,
1419
1420 pub(crate) attributes: HashMap<SmolStr, RestrictedExpr>,
1424}
1425
1426impl ValidatorActionId {
1427 pub fn context(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1429 self.context.iter()
1430 }
1431}
1432
1433impl TCNode<EntityUID> for ValidatorActionId {
1434 fn get_key(&self) -> EntityUID {
1435 self.name.clone()
1436 }
1437
1438 fn add_edge_to(&mut self, k: EntityUID) {
1439 self.descendants.insert(k);
1440 }
1441
1442 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
1443 Box::new(self.descendants.iter())
1444 }
1445
1446 fn has_edge_to(&self, e: &EntityUID) -> bool {
1447 self.descendants.contains(e)
1448 }
1449}
1450
1451#[derive(Clone, Debug, Serialize)]
1453pub(crate) struct ValidatorApplySpec {
1454 #[serde(rename = "principalApplySpec")]
1461 principal_apply_spec: HashSet<EntityType>,
1462
1463 #[serde(rename = "resourceApplySpec")]
1466 resource_apply_spec: HashSet<EntityType>,
1467}
1468
1469impl ValidatorApplySpec {
1470 pub(crate) fn new(
1473 principal_apply_spec: HashSet<EntityType>,
1474 resource_apply_spec: HashSet<EntityType>,
1475 ) -> Self {
1476 Self {
1477 principal_apply_spec,
1478 resource_apply_spec,
1479 }
1480 }
1481
1482 pub(crate) fn applicable_principal_types(&self) -> impl Iterator<Item = &EntityType> {
1484 self.principal_apply_spec.iter()
1485 }
1486
1487 pub(crate) fn applicable_resource_types(&self) -> impl Iterator<Item = &EntityType> {
1489 self.resource_apply_spec.iter()
1490 }
1491}
1492
1493pub(crate) trait HeadVar<K>: Copy {
1496 fn get_known_vars<'a>(
1500 &self,
1501 schema: &'a ValidatorSchema,
1502 ) -> Box<dyn Iterator<Item = &'a K> + 'a>;
1503
1504 fn get_euid_component(&self, euid: EntityUID) -> Option<K>;
1507
1508 fn get_euid_component_if_present(&self, schema: &ValidatorSchema, euid: EntityUID)
1511 -> Option<K>;
1512
1513 fn get_descendants_if_present<'a>(
1516 &self,
1517 schema: &'a ValidatorSchema,
1518 euid: EntityUID,
1519 ) -> Option<Box<dyn Iterator<Item = &'a K> + 'a>>;
1520}
1521
1522#[derive(Debug, Clone, Copy)]
1526pub(crate) enum PrincipalOrResourceHeadVar {
1527 PrincipalOrResource,
1528}
1529
1530impl HeadVar<Name> for PrincipalOrResourceHeadVar {
1531 fn get_known_vars<'a>(
1532 &self,
1533 schema: &'a ValidatorSchema,
1534 ) -> Box<dyn Iterator<Item = &'a Name> + 'a> {
1535 Box::new(schema.known_entity_types())
1536 }
1537
1538 fn get_euid_component(&self, euid: EntityUID) -> Option<Name> {
1539 let (ty, _) = euid.components();
1540 match ty {
1541 EntityType::Unspecified => None,
1542 EntityType::Concrete(name) => Some(name),
1543 }
1544 }
1545
1546 fn get_euid_component_if_present(
1547 &self,
1548 schema: &ValidatorSchema,
1549 euid: EntityUID,
1550 ) -> Option<Name> {
1551 let euid_component = self.get_euid_component(euid)?;
1552 if schema.is_known_entity_type(&euid_component) {
1553 Some(euid_component)
1554 } else {
1555 None
1556 }
1557 }
1558
1559 fn get_descendants_if_present<'a>(
1560 &self,
1561 schema: &'a ValidatorSchema,
1562 euid: EntityUID,
1563 ) -> Option<Box<dyn Iterator<Item = &'a Name> + 'a>> {
1564 let euid_component = self.get_euid_component(euid)?;
1565 match schema.get_entity_type(&euid_component) {
1566 Some(entity_type) => Some(Box::new(entity_type.descendants.iter())),
1567 None => None,
1568 }
1569 }
1570}
1571
1572#[derive(Debug, Clone, Copy)]
1575pub(crate) enum ActionHeadVar {
1576 Action,
1577}
1578
1579impl HeadVar<EntityUID> for ActionHeadVar {
1580 fn get_known_vars<'a>(
1581 &self,
1582 schema: &'a ValidatorSchema,
1583 ) -> Box<dyn Iterator<Item = &'a EntityUID> + 'a> {
1584 Box::new(schema.known_action_ids())
1585 }
1586
1587 fn get_euid_component(&self, euid: EntityUID) -> Option<EntityUID> {
1588 Some(euid)
1589 }
1590
1591 fn get_euid_component_if_present(
1592 &self,
1593 schema: &ValidatorSchema,
1594 euid: EntityUID,
1595 ) -> Option<EntityUID> {
1596 let euid_component = self.get_euid_component(euid)?;
1597 if schema.is_known_action_id(&euid_component) {
1598 Some(euid_component)
1599 } else {
1600 None
1601 }
1602 }
1603
1604 fn get_descendants_if_present<'a>(
1605 &self,
1606 schema: &'a ValidatorSchema,
1607 euid: EntityUID,
1608 ) -> Option<Box<dyn Iterator<Item = &'a EntityUID> + 'a>> {
1609 let euid_component = self.get_euid_component(euid)?;
1610 match schema.get_action_id(&euid_component) {
1611 Some(action_id) => Some(Box::new(action_id.descendants.iter())),
1612 None => None,
1613 }
1614 }
1615}
1616
1617#[derive(Debug, Clone, Deserialize)]
1620#[serde(transparent)]
1621pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
1622
1623impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
1624 type Error = SchemaError;
1625
1626 fn try_into(self) -> Result<ValidatorSchema> {
1627 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
1628 ValidatorNamespaceDef::from_namespace_definition(
1629 None,
1630 self.0,
1631 crate::ActionBehavior::PermitAttributes,
1632 )?,
1633 ])])
1634 }
1635}
1636
1637#[cfg(test)]
1638mod test {
1639 use std::{collections::BTreeMap, str::FromStr};
1640
1641 use crate::types::Type;
1642
1643 use serde_json::json;
1644
1645 use super::*;
1646
1647 #[test]
1649 fn test_from_schema_file() {
1650 let src = json!(
1651 {
1652 "entityTypes": {
1653 "User": {
1654 "memberOfTypes": [ "Group" ]
1655 },
1656 "Group": {
1657 "memberOfTypes": []
1658 },
1659 "Photo": {
1660 "memberOfTypes": [ "Album" ]
1661 },
1662 "Album": {
1663 "memberOfTypes": []
1664 }
1665 },
1666 "actions": {
1667 "view_photo": {
1668 "appliesTo": {
1669 "principalTypes": ["User", "Group"],
1670 "resourceTypes": ["Photo"]
1671 }
1672 }
1673 }
1674 });
1675 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1676 let schema: Result<ValidatorSchema> = schema_file.try_into();
1677 assert!(schema.is_ok());
1678 }
1679
1680 #[test]
1682 fn test_from_schema_file_duplicate_entity() {
1683 let src = r#"
1686 {"": {
1687 "entityTypes": {
1688 "User": {
1689 "memberOfTypes": [ "Group" ]
1690 },
1691 "Group": {
1692 "memberOfTypes": []
1693 },
1694 "Photo": {
1695 "memberOfTypes": [ "Album" ]
1696 },
1697 "Photo": {
1698 "memberOfTypes": []
1699 }
1700 },
1701 "actions": {
1702 "view_photo": {
1703 "memberOf": [],
1704 "appliesTo": {
1705 "principalTypes": ["User", "Group"],
1706 "resourceTypes": ["Photo"]
1707 }
1708 }
1709 }
1710 }}"#;
1711
1712 match ValidatorSchema::from_str(src) {
1713 Err(SchemaError::ParseFileFormat(_)) => (),
1714 _ => panic!("Expected serde error due to duplicate entity type."),
1715 }
1716 }
1717
1718 #[test]
1720 fn test_from_schema_file_duplicate_action() {
1721 let src = r#"
1724 {"": {
1725 "entityTypes": {
1726 "User": {
1727 "memberOfTypes": [ "Group" ]
1728 },
1729 "Group": {
1730 "memberOfTypes": []
1731 },
1732 "Photo": {
1733 "memberOfTypes": []
1734 }
1735 },
1736 "actions": {
1737 "view_photo": {
1738 "memberOf": [],
1739 "appliesTo": {
1740 "principalTypes": ["User", "Group"],
1741 "resourceTypes": ["Photo"]
1742 }
1743 },
1744 "view_photo": { }
1745 }
1746 }"#;
1747 match ValidatorSchema::from_str(src) {
1748 Err(SchemaError::ParseFileFormat(_)) => (),
1749 _ => panic!("Expected serde error due to duplicate action type."),
1750 }
1751 }
1752
1753 #[test]
1755 fn test_from_schema_file_undefined_entities() {
1756 let src = json!(
1757 {
1758 "entityTypes": {
1759 "User": {
1760 "memberOfTypes": [ "Grop" ]
1761 },
1762 "Group": {
1763 "memberOfTypes": []
1764 },
1765 "Photo": {
1766 "memberOfTypes": []
1767 }
1768 },
1769 "actions": {
1770 "view_photo": {
1771 "appliesTo": {
1772 "principalTypes": ["Usr", "Group"],
1773 "resourceTypes": ["Phoot"]
1774 }
1775 }
1776 }
1777 });
1778 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1779 let schema: Result<ValidatorSchema> = schema_file.try_into();
1780 match schema {
1781 Ok(_) => panic!("from_schema_file should have failed"),
1782 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1783 assert_eq!(v.len(), 3)
1784 }
1785 _ => panic!("Unexpected error from from_schema_file"),
1786 }
1787 }
1788
1789 #[test]
1790 fn undefined_entity_namespace_member_of() {
1791 let src = json!(
1792 {"Foo": {
1793 "entityTypes": {
1794 "User": {
1795 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1796 },
1797 "Group": { }
1798 },
1799 "actions": {}
1800 }});
1801 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1802 let schema: Result<ValidatorSchema> = schema_file.try_into();
1803 match schema {
1804 Ok(_) => panic!("try_into should have failed"),
1805 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1806 assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1807 }
1808 _ => panic!("Unexpected error from try_into"),
1809 }
1810 }
1811
1812 #[test]
1813 fn undefined_entity_namespace_applies_to() {
1814 let src = json!(
1815 {"Foo": {
1816 "entityTypes": { "User": { }, "Photo": { } },
1817 "actions": {
1818 "view_photo": {
1819 "appliesTo": {
1820 "principalTypes": ["Foo::User", "Bar::User"],
1821 "resourceTypes": ["Photo", "Bar::Photo"],
1822 }
1823 }
1824 }
1825 }});
1826 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1827 let schema: Result<ValidatorSchema> = schema_file.try_into();
1828 match schema {
1829 Ok(_) => panic!("try_into should have failed"),
1830 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1831 assert_eq!(
1832 v,
1833 HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1834 )
1835 }
1836 _ => panic!("Unexpected error from try_into"),
1837 }
1838 }
1839
1840 #[test]
1842 fn test_from_schema_file_undefined_action() {
1843 let src = json!(
1844 {
1845 "entityTypes": {
1846 "User": {
1847 "memberOfTypes": [ "Group" ]
1848 },
1849 "Group": {
1850 "memberOfTypes": []
1851 },
1852 "Photo": {
1853 "memberOfTypes": []
1854 }
1855 },
1856 "actions": {
1857 "view_photo": {
1858 "memberOf": [ {"id": "photo_action"} ],
1859 "appliesTo": {
1860 "principalTypes": ["User", "Group"],
1861 "resourceTypes": ["Photo"]
1862 }
1863 }
1864 }
1865 });
1866 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1867 let schema: Result<ValidatorSchema> = schema_file.try_into();
1868 match schema {
1869 Ok(_) => panic!("from_schema_file should have failed"),
1870 Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1871 _ => panic!("Unexpected error from from_schema_file"),
1872 }
1873 }
1874
1875 #[test]
1878 fn test_from_schema_file_action_cycle1() {
1879 let src = json!(
1880 {
1881 "entityTypes": {},
1882 "actions": {
1883 "view_photo": {
1884 "memberOf": [ {"id": "view_photo"} ]
1885 }
1886 }
1887 });
1888 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1889 let schema: Result<ValidatorSchema> = schema_file.try_into();
1890 match schema {
1891 Ok(_) => panic!("from_schema_file should have failed"),
1892 Err(SchemaError::CycleInActionHierarchy) => (), e => panic!("Unexpected error from from_schema_file: {:?}", e),
1894 }
1895 }
1896
1897 #[test]
1900 fn test_from_schema_file_action_cycle2() {
1901 let src = json!(
1902 {
1903 "entityTypes": {},
1904 "actions": {
1905 "view_photo": {
1906 "memberOf": [ {"id": "edit_photo"} ]
1907 },
1908 "edit_photo": {
1909 "memberOf": [ {"id": "delete_photo"} ]
1910 },
1911 "delete_photo": {
1912 "memberOf": [ {"id": "view_photo"} ]
1913 },
1914 "other_action": {
1915 "memberOf": [ {"id": "edit_photo"} ]
1916 }
1917 }
1918 });
1919 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1920 let schema: Result<ValidatorSchema> = schema_file.try_into();
1921 match schema {
1922 Ok(x) => {
1923 println!("{:?}", x);
1924 panic!("from_schema_file should have failed");
1925 }
1926 Err(SchemaError::CycleInActionHierarchy) => (), e => panic!("Unexpected error from from_schema_file: {:?}", e),
1928 }
1929 }
1930
1931 #[test]
1932 fn namespaced_schema() {
1933 let src = r#"
1934 { "N::S": {
1935 "entityTypes": {
1936 "User": {},
1937 "Photo": {}
1938 },
1939 "actions": {
1940 "view_photo": {
1941 "appliesTo": {
1942 "principalTypes": ["User"],
1943 "resourceTypes": ["Photo"]
1944 }
1945 }
1946 }
1947 } }
1948 "#;
1949 let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1950 let schema: ValidatorSchema = schema_file
1951 .try_into()
1952 .expect("Namespaced schema failed to convert.");
1953 dbg!(&schema);
1954 let user_entity_type = &"N::S::User"
1955 .parse()
1956 .expect("Namespaced entity type should have parsed");
1957 let photo_entity_type = &"N::S::Photo"
1958 .parse()
1959 .expect("Namespaced entity type should have parsed");
1960 assert!(
1961 schema.entity_types.contains_key(user_entity_type),
1962 "Expected and entity type User."
1963 );
1964 assert!(
1965 schema.entity_types.contains_key(photo_entity_type),
1966 "Expected an entity type Photo."
1967 );
1968 assert_eq!(
1969 schema.entity_types.len(),
1970 2,
1971 "Expected exactly 2 entity types."
1972 );
1973 assert!(
1974 schema.action_ids.contains_key(
1975 &"N::S::Action::\"view_photo\""
1976 .parse()
1977 .expect("Namespaced action should have parsed")
1978 ),
1979 "Expected an action \"view_photo\"."
1980 );
1981 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1982
1983 let apply_spec = &schema
1984 .action_ids
1985 .values()
1986 .next()
1987 .expect("Expected Action")
1988 .applies_to;
1989 assert_eq!(
1990 apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1991 vec![&EntityType::Concrete(user_entity_type.clone())]
1992 );
1993 assert_eq!(
1994 apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1995 vec![&EntityType::Concrete(photo_entity_type.clone())]
1996 );
1997 }
1998
1999 #[test]
2000 fn cant_use_namespace_in_entity_type() {
2001 let src = r#"
2002 {
2003 "entityTypes": { "NS::User": {} },
2004 "actions": {}
2005 }
2006 "#;
2007 let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
2008 assert!(
2009 matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::EntityTypeParseError(_))),
2010 "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
2011 }
2012
2013 #[test]
2014 fn entity_attribute_entity_type_with_namespace() {
2015 let schema_json: SchemaFragment = serde_json::from_str(
2016 r#"
2017 {"A::B": {
2018 "entityTypes": {
2019 "Foo": {
2020 "shape": {
2021 "type": "Record",
2022 "attributes": {
2023 "name": { "type": "Entity", "name": "C::D::Foo" }
2024 }
2025 }
2026 }
2027 },
2028 "actions": {}
2029 }}
2030 "#,
2031 )
2032 .expect("Expected valid schema");
2033
2034 let schema: Result<ValidatorSchema> = schema_json.try_into();
2035 match schema {
2036 Err(SchemaError::UndeclaredEntityTypes(tys)) => {
2037 assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
2038 }
2039 _ => panic!("Schema construction should have failed due to undeclared entity type."),
2040 }
2041 }
2042
2043 #[test]
2044 fn entity_attribute_entity_type_with_declared_namespace() {
2045 let schema_json: SchemaFragment = serde_json::from_str(
2046 r#"
2047 {"A::B": {
2048 "entityTypes": {
2049 "Foo": {
2050 "shape": {
2051 "type": "Record",
2052 "attributes": {
2053 "name": { "type": "Entity", "name": "A::B::Foo" }
2054 }
2055 }
2056 }
2057 },
2058 "actions": {}
2059 }}
2060 "#,
2061 )
2062 .expect("Expected valid schema");
2063
2064 let schema: ValidatorSchema = schema_json
2065 .try_into()
2066 .expect("Expected schema to construct without error.");
2067
2068 let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
2069 let foo_type = schema
2070 .entity_types
2071 .get(&foo_name)
2072 .expect("Expected to find entity");
2073 let name_type = foo_type
2074 .attr("name")
2075 .expect("Expected attribute name")
2076 .attr_type
2077 .clone();
2078 let expected_name_type = Type::named_entity_reference(foo_name);
2079 assert_eq!(name_type, expected_name_type);
2080 }
2081
2082 #[test]
2083 fn cannot_declare_action_type_when_prohibited() {
2084 let schema_json: NamespaceDefinition = serde_json::from_str(
2085 r#"
2086 {
2087 "entityTypes": { "Action": {} },
2088 "actions": {}
2089 }
2090 "#,
2091 )
2092 .expect("Expected valid schema");
2093
2094 let schema: Result<ValidatorSchema> = schema_json.try_into();
2095 assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
2096 }
2097
2098 #[test]
2099 fn can_declare_other_type_when_action_type_prohibited() {
2100 let schema_json: NamespaceDefinition = serde_json::from_str(
2101 r#"
2102 {
2103 "entityTypes": { "Foo": { } },
2104 "actions": {}
2105 }
2106 "#,
2107 )
2108 .expect("Expected valid schema");
2109
2110 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
2111 }
2112
2113 #[test]
2114 fn cannot_declare_action_in_group_when_prohibited() {
2115 let schema_json: SchemaFragment = serde_json::from_str(
2116 r#"
2117 {"": {
2118 "entityTypes": {},
2119 "actions": {
2120 "universe": { },
2121 "view_photo": {
2122 "attributes": {"id": "universe"}
2123 },
2124 "edit_photo": {
2125 "attributes": {"id": "universe"}
2126 },
2127 "delete_photo": {
2128 "attributes": {"id": "universe"}
2129 }
2130 }
2131 }}
2132 "#,
2133 )
2134 .expect("Expected valid schema");
2135
2136 let schema = ValidatorSchemaFragment::from_schema_fragment(
2137 schema_json,
2138 ActionBehavior::ProhibitAttributes,
2139 );
2140 match schema {
2141 Err(SchemaError::ActionEntityAttributes(actions)) => {
2142 assert_eq!(
2143 actions.into_iter().collect::<HashSet<_>>(),
2144 HashSet::from([
2145 "view_photo".to_string(),
2146 "edit_photo".to_string(),
2147 "delete_photo".to_string(),
2148 ])
2149 )
2150 }
2151 _ => panic!("Did not see expected error."),
2152 }
2153 }
2154
2155 #[test]
2156 fn test_entity_type_no_namespace() {
2157 let src = json!({"type": "Entity", "name": "Foo"});
2158 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2159 assert_eq!(
2160 schema_ty,
2161 SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
2162 );
2163 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2164 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
2165 schema_ty,
2166 )
2167 .expect("Error converting schema type to type.")
2168 .resolve_type_defs(&HashMap::new())
2169 .unwrap();
2170 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2171 }
2172
2173 #[test]
2174 fn test_entity_type_namespace() {
2175 let src = json!({"type": "Entity", "name": "NS::Foo"});
2176 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2177 assert_eq!(
2178 schema_ty,
2179 SchemaType::Type(SchemaTypeVariant::Entity {
2180 name: "NS::Foo".into()
2181 })
2182 );
2183 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2184 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
2185 schema_ty,
2186 )
2187 .expect("Error converting schema type to type.")
2188 .resolve_type_defs(&HashMap::new())
2189 .unwrap();
2190 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2191 }
2192
2193 #[test]
2194 fn test_entity_type_namespace_parse_error() {
2195 let src = json!({"type": "Entity", "name": "::Foo"});
2196 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2197 assert_eq!(
2198 schema_ty,
2199 SchemaType::Type(SchemaTypeVariant::Entity {
2200 name: "::Foo".into()
2201 })
2202 );
2203 match ValidatorNamespaceDef::try_schema_type_into_validator_type(
2204 Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
2205 schema_ty,
2206 ) {
2207 Err(SchemaError::EntityTypeParseError(_)) => (),
2208 _ => panic!("Did not see expected EntityTypeParseError."),
2209 }
2210 }
2211
2212 #[test]
2213 fn schema_type_record_is_validator_type_record() {
2214 let src = json!({"type": "Record", "attributes": {}});
2215 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2216 assert_eq!(
2217 schema_ty,
2218 SchemaType::Type(SchemaTypeVariant::Record {
2219 attributes: BTreeMap::new(),
2220 additional_attributes: false,
2221 }),
2222 );
2223 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
2224 .expect("Error converting schema type to type.")
2225 .resolve_type_defs(&HashMap::new())
2226 .unwrap();
2227 assert_eq!(ty, Type::record_with_attributes(None));
2228 }
2229
2230 #[test]
2231 fn get_namespaces() {
2232 let fragment: SchemaFragment = serde_json::from_value(json!({
2233 "Foo::Bar::Baz": {
2234 "entityTypes": {},
2235 "actions": {}
2236 },
2237 "Foo": {
2238 "entityTypes": {},
2239 "actions": {}
2240 },
2241 "Bar": {
2242 "entityTypes": {},
2243 "actions": {}
2244 },
2245 }))
2246 .unwrap();
2247
2248 let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
2249 assert_eq!(
2250 schema_fragment
2251 .0
2252 .iter()
2253 .map(|f| f.namespace())
2254 .collect::<HashSet<_>>(),
2255 HashSet::from([
2256 &Some("Foo::Bar::Baz".parse().unwrap()),
2257 &Some("Foo".parse().unwrap()),
2258 &Some("Bar".parse().unwrap())
2259 ])
2260 );
2261 }
2262
2263 #[test]
2264 fn schema_no_fragments() {
2265 let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
2266 assert!(schema.entity_types.is_empty());
2267 assert!(schema.action_ids.is_empty());
2268 }
2269
2270 #[test]
2271 fn same_action_different_namespace() {
2272 let fragment: SchemaFragment = serde_json::from_value(json!({
2273 "Foo::Bar": {
2274 "entityTypes": {},
2275 "actions": {
2276 "Baz": {}
2277 }
2278 },
2279 "Bar::Foo": {
2280 "entityTypes": {},
2281 "actions": {
2282 "Baz": { }
2283 }
2284 },
2285 "Biz": {
2286 "entityTypes": {},
2287 "actions": {
2288 "Baz": { }
2289 }
2290 }
2291 }))
2292 .unwrap();
2293
2294 let schema: ValidatorSchema = fragment.try_into().unwrap();
2295 assert!(schema
2296 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2297 .is_some());
2298 assert!(schema
2299 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
2300 .is_some());
2301 assert!(schema
2302 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2303 .is_some());
2304 }
2305
2306 #[test]
2307 fn same_type_different_namespace() {
2308 let fragment: SchemaFragment = serde_json::from_value(json!({
2309 "Foo::Bar": {
2310 "entityTypes": {"Baz" : {}},
2311 "actions": { }
2312 },
2313 "Bar::Foo": {
2314 "entityTypes": {"Baz" : {}},
2315 "actions": { }
2316 },
2317 "Biz": {
2318 "entityTypes": {"Baz" : {}},
2319 "actions": { }
2320 }
2321 }))
2322 .unwrap();
2323 let schema: ValidatorSchema = fragment.try_into().unwrap();
2324
2325 assert!(schema
2326 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
2327 .is_some());
2328 assert!(schema
2329 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
2330 .is_some());
2331 assert!(schema
2332 .get_entity_type(&"Biz::Baz".parse().unwrap())
2333 .is_some());
2334 }
2335
2336 #[test]
2337 fn member_of_different_namespace() {
2338 let fragment: SchemaFragment = serde_json::from_value(json!({
2339 "Bar": {
2340 "entityTypes": {
2341 "Baz": {
2342 "memberOfTypes": ["Foo::Buz"]
2343 }
2344 },
2345 "actions": {}
2346 },
2347 "Foo": {
2348 "entityTypes": { "Buz": {} },
2349 "actions": { }
2350 }
2351 }))
2352 .unwrap();
2353 let schema: ValidatorSchema = fragment.try_into().unwrap();
2354
2355 let buz = schema
2356 .get_entity_type(&"Foo::Buz".parse().unwrap())
2357 .unwrap();
2358 assert_eq!(
2359 buz.descendants,
2360 HashSet::from(["Bar::Baz".parse().unwrap()])
2361 );
2362 }
2363
2364 #[test]
2365 fn attribute_different_namespace() {
2366 let fragment: SchemaFragment = serde_json::from_value(json!({
2367 "Bar": {
2368 "entityTypes": {
2369 "Baz": {
2370 "shape": {
2371 "type": "Record",
2372 "attributes": {
2373 "fiz": {
2374 "type": "Entity",
2375 "name": "Foo::Buz"
2376 }
2377 }
2378 }
2379 }
2380 },
2381 "actions": {}
2382 },
2383 "Foo": {
2384 "entityTypes": { "Buz": {} },
2385 "actions": { }
2386 }
2387 }))
2388 .unwrap();
2389
2390 let schema: ValidatorSchema = fragment.try_into().unwrap();
2391 let baz = schema
2392 .get_entity_type(&"Bar::Baz".parse().unwrap())
2393 .unwrap();
2394 assert_eq!(
2395 baz.attr("fiz").unwrap().attr_type,
2396 Type::named_entity_reference_from_str("Foo::Buz"),
2397 );
2398 }
2399
2400 #[test]
2401 fn applies_to_different_namespace() {
2402 let fragment: SchemaFragment = serde_json::from_value(json!({
2403 "Foo::Bar": {
2404 "entityTypes": { },
2405 "actions": {
2406 "Baz": {
2407 "appliesTo": {
2408 "principalTypes": [ "Fiz::Buz" ],
2409 "resourceTypes": [ "Fiz::Baz" ],
2410 }
2411 }
2412 }
2413 },
2414 "Fiz": {
2415 "entityTypes": {
2416 "Buz": {},
2417 "Baz": {}
2418 },
2419 "actions": { }
2420 }
2421 }))
2422 .unwrap();
2423 let schema: ValidatorSchema = fragment.try_into().unwrap();
2424
2425 let baz = schema
2426 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2427 .unwrap();
2428 assert_eq!(
2429 baz.applies_to
2430 .applicable_principal_types()
2431 .collect::<HashSet<_>>(),
2432 HashSet::from([&EntityType::Concrete("Fiz::Buz".parse().unwrap())])
2433 );
2434 assert_eq!(
2435 baz.applies_to
2436 .applicable_resource_types()
2437 .collect::<HashSet<_>>(),
2438 HashSet::from([&EntityType::Concrete("Fiz::Baz".parse().unwrap())])
2439 );
2440 }
2441
2442 #[test]
2443 fn simple_defined_type() {
2444 let fragment: SchemaFragment = serde_json::from_value(json!({
2445 "": {
2446 "commonTypes": {
2447 "MyLong": {"type": "Long"}
2448 },
2449 "entityTypes": {
2450 "User": {
2451 "shape": {
2452 "type": "Record",
2453 "attributes": {
2454 "a": {"type": "MyLong"}
2455 }
2456 }
2457 }
2458 },
2459 "actions": {}
2460 }
2461 }))
2462 .unwrap();
2463 let schema: ValidatorSchema = fragment.try_into().unwrap();
2464 assert_eq!(
2465 schema.entity_types.iter().next().unwrap().1.attributes,
2466 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2467 );
2468 }
2469
2470 #[test]
2471 fn defined_record_as_attrs() {
2472 let fragment: SchemaFragment = serde_json::from_value(json!({
2473 "": {
2474 "commonTypes": {
2475 "MyRecord": {
2476 "type": "Record",
2477 "attributes": {
2478 "a": {"type": "Long"}
2479 }
2480 }
2481 },
2482 "entityTypes": {
2483 "User": { "shape": { "type": "MyRecord", } }
2484 },
2485 "actions": {}
2486 }
2487 }))
2488 .unwrap();
2489 let schema: ValidatorSchema = fragment.try_into().unwrap();
2490 assert_eq!(
2491 schema.entity_types.iter().next().unwrap().1.attributes,
2492 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2493 );
2494 }
2495
2496 #[test]
2497 fn cross_namespace_type() {
2498 let fragment: SchemaFragment = serde_json::from_value(json!({
2499 "A": {
2500 "commonTypes": {
2501 "MyLong": {"type": "Long"}
2502 },
2503 "entityTypes": { },
2504 "actions": {}
2505 },
2506 "B": {
2507 "entityTypes": {
2508 "User": {
2509 "shape": {
2510 "type": "Record",
2511 "attributes": {
2512 "a": {"type": "A::MyLong"}
2513 }
2514 }
2515 }
2516 },
2517 "actions": {}
2518 }
2519 }))
2520 .unwrap();
2521 let schema: ValidatorSchema = fragment.try_into().unwrap();
2522 assert_eq!(
2523 schema.entity_types.iter().next().unwrap().1.attributes,
2524 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2525 );
2526 }
2527
2528 #[test]
2529 fn cross_fragment_type() {
2530 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2531 "A": {
2532 "commonTypes": {
2533 "MyLong": {"type": "Long"}
2534 },
2535 "entityTypes": { },
2536 "actions": {}
2537 }
2538 }))
2539 .unwrap()
2540 .try_into()
2541 .unwrap();
2542 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2543 "A": {
2544 "entityTypes": {
2545 "User": {
2546 "shape": {
2547 "type": "Record",
2548 "attributes": {
2549 "a": {"type": "MyLong"}
2550 }
2551 }
2552 }
2553 },
2554 "actions": {}
2555 }
2556 }))
2557 .unwrap()
2558 .try_into()
2559 .unwrap();
2560 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2561
2562 assert_eq!(
2563 schema.entity_types.iter().next().unwrap().1.attributes,
2564 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2565 );
2566 }
2567
2568 #[test]
2569 #[should_panic]
2570 fn cross_fragment_duplicate_type() {
2571 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2572 "A": {
2573 "commonTypes": {
2574 "MyLong": {"type": "Long"}
2575 },
2576 "entityTypes": {},
2577 "actions": {}
2578 }
2579 }))
2580 .unwrap()
2581 .try_into()
2582 .unwrap();
2583 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2584 "A": {
2585 "commonTypes": {
2586 "MyLong": {"type": "Long"}
2587 },
2588 "entityTypes": {},
2589 "actions": {}
2590 }
2591 }))
2592 .unwrap()
2593 .try_into()
2594 .unwrap();
2595 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2596
2597 assert_eq!(
2598 schema.entity_types.iter().next().unwrap().1.attributes,
2599 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2600 );
2601 }
2602
2603 #[test]
2604 fn undeclared_type_in_attr() {
2605 let fragment: SchemaFragment = serde_json::from_value(json!({
2606 "": {
2607 "commonTypes": { },
2608 "entityTypes": {
2609 "User": {
2610 "shape": {
2611 "type": "Record",
2612 "attributes": {
2613 "a": {"type": "MyLong"}
2614 }
2615 }
2616 }
2617 },
2618 "actions": {}
2619 }
2620 }))
2621 .unwrap();
2622 match TryInto::<ValidatorSchema>::try_into(fragment) {
2623 Err(SchemaError::UndeclaredCommonType(_)) => (),
2624 s => panic!(
2625 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2626 s
2627 ),
2628 }
2629 }
2630
2631 #[test]
2632 fn undeclared_type_in_type_def() {
2633 let fragment: SchemaFragment = serde_json::from_value(json!({
2634 "": {
2635 "commonTypes": {
2636 "a": { "type": "b" }
2637 },
2638 "entityTypes": { },
2639 "actions": {}
2640 }
2641 }))
2642 .unwrap();
2643 match TryInto::<ValidatorSchema>::try_into(fragment) {
2644 Err(SchemaError::UndeclaredCommonType(_)) => (),
2645 s => panic!(
2646 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2647 s
2648 ),
2649 }
2650 }
2651
2652 #[test]
2653 fn shape_not_record() {
2654 let fragment: SchemaFragment = serde_json::from_value(json!({
2655 "": {
2656 "commonTypes": {
2657 "MyLong": { "type": "Long" }
2658 },
2659 "entityTypes": {
2660 "User": {
2661 "shape": { "type": "MyLong" }
2662 }
2663 },
2664 "actions": {}
2665 }
2666 }))
2667 .unwrap();
2668 match TryInto::<ValidatorSchema>::try_into(fragment) {
2669 Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
2670 s => panic!(
2671 "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
2672 s
2673 ),
2674 }
2675 }
2676
2677 #[test]
2681 fn counterexamples_from_cedar_134() {
2682 let bad1 = json!({
2684 "": {
2685 "entityTypes": {
2686 "User // comment": {
2687 "memberOfTypes": [
2688 "UserGroup"
2689 ]
2690 },
2691 "User": {
2692 "memberOfTypes": [
2693 "UserGroup"
2694 ]
2695 },
2696 "UserGroup": {}
2697 },
2698 "actions": {}
2699 }
2700 });
2701 let fragment = serde_json::from_value::<SchemaFragment>(bad1)
2702 .expect("constructing the fragment itself should succeed"); let err = ValidatorSchema::try_from(fragment)
2704 .expect_err("should error due to invalid entity type name");
2705 assert!(
2706 err.to_string()
2707 .contains("needs to be normalized (e.g., whitespace removed): User // comment"),
2708 "actual error message was {err}"
2709 );
2710
2711 let bad2 = json!({
2713 "ABC :: //comment \n XYZ ": {
2714 "entityTypes": {
2715 "User": {
2716 "memberOfTypes": []
2717 }
2718 },
2719 "actions": {}
2720 }
2721 });
2722 let fragment = serde_json::from_value::<SchemaFragment>(bad2)
2723 .expect("constructing the fragment itself should succeed"); let err = ValidatorSchema::try_from(fragment)
2725 .expect_err("should error due to invalid schema namespace");
2726 assert!(
2727 err.to_string().contains(
2728 "needs to be normalized (e.g., whitespace removed): ABC :: //comment "
2729 ),
2730 "actual error message was {err}"
2731 );
2732 }
2733
2734 #[test]
2735 fn simple_action_entity() {
2736 let src = json!(
2737 {
2738 "entityTypes": { },
2739 "actions": {
2740 "view_photo": { },
2741 }
2742 });
2743
2744 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2745 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2746 let actions = schema.action_entities().expect("Entity Construct Error");
2747
2748 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2749 let view_photo = actions.entity(&action_uid);
2750 assert_eq!(
2751 view_photo.unwrap(),
2752 &Entity::new(action_uid, HashMap::new(), HashSet::new())
2753 );
2754 }
2755
2756 #[test]
2757 fn action_entity_hierarchy() {
2758 let src = json!(
2759 {
2760 "entityTypes": { },
2761 "actions": {
2762 "read": {},
2763 "view": {
2764 "memberOf": [{"id": "read"}]
2765 },
2766 "view_photo": {
2767 "memberOf": [{"id": "view"}]
2768 },
2769 }
2770 });
2771
2772 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2773 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2774 let actions = schema.action_entities().expect("Entity Construct Error");
2775
2776 let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2777 let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2778 let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2779
2780 let view_photo_entity = actions.entity(&view_photo_uid);
2781 assert_eq!(
2782 view_photo_entity.unwrap(),
2783 &Entity::new(
2784 view_photo_uid,
2785 HashMap::new(),
2786 HashSet::from([view_uid.clone(), read_uid.clone()])
2787 )
2788 );
2789
2790 let view_entity = actions.entity(&view_uid);
2791 assert_eq!(
2792 view_entity.unwrap(),
2793 &Entity::new(view_uid, HashMap::new(), HashSet::from([read_uid.clone()]))
2794 );
2795
2796 let read_entity = actions.entity(&read_uid);
2797 assert_eq!(
2798 read_entity.unwrap(),
2799 &Entity::new(read_uid, HashMap::new(), HashSet::new())
2800 );
2801 }
2802
2803 #[test]
2804 fn action_entity_attribute() {
2805 let src = json!(
2806 {
2807 "entityTypes": { },
2808 "actions": {
2809 "view_photo": {
2810 "attributes": { "attr": "foo" }
2811 },
2812 }
2813 });
2814
2815 let schema_file: NamespaceDefinitionWithActionAttributes =
2816 serde_json::from_value(src).expect("Parse Error");
2817 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2818 let actions = schema.action_entities().expect("Entity Construct Error");
2819
2820 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2821 let view_photo = actions.entity(&action_uid);
2822 assert_eq!(
2823 view_photo.unwrap(),
2824 &Entity::new(
2825 action_uid,
2826 HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2827 HashSet::new()
2828 )
2829 );
2830 }
2831
2832 #[test]
2833 fn test_action_namespace_inference_multi_success() {
2834 let src = json!({
2835 "Foo" : {
2836 "entityTypes" : {},
2837 "actions" : {
2838 "read" : {}
2839 }
2840 },
2841 "ExampleCo::Personnel" : {
2842 "entityTypes" : {},
2843 "actions" : {
2844 "viewPhoto" : {
2845 "memberOf" : [
2846 {
2847 "id" : "read",
2848 "type" : "Foo::Action"
2849 }
2850 ]
2851 }
2852 }
2853 },
2854 });
2855 let schema_fragment =
2856 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2857 let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2858 let view_photo = schema
2859 .action_entities_iter()
2860 .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2861 .unwrap();
2862 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2863 let read = ancestors[0];
2864 assert_eq!(read.eid().to_string(), "read");
2865 assert_eq!(read.entity_type().to_string(), "Foo::Action");
2866 }
2867
2868 #[test]
2869 fn test_action_namespace_inference_multi() {
2870 let src = json!({
2871 "ExampleCo::Personnel::Foo" : {
2872 "entityTypes" : {},
2873 "actions" : {
2874 "read" : {}
2875 }
2876 },
2877 "ExampleCo::Personnel" : {
2878 "entityTypes" : {},
2879 "actions" : {
2880 "viewPhoto" : {
2881 "memberOf" : [
2882 {
2883 "id" : "read",
2884 "type" : "Foo::Action"
2885 }
2886 ]
2887 }
2888 }
2889 },
2890 });
2891 let schema_fragment =
2892 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2893 let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2894 schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2895 }
2896
2897 #[test]
2898 fn test_action_namespace_inference() {
2899 let src = json!({
2900 "ExampleCo::Personnel" : {
2901 "entityTypes" : { },
2902 "actions" : {
2903 "read" : {},
2904 "viewPhoto" : {
2905 "memberOf" : [
2906 {
2907 "id" : "read",
2908 "type" : "Action"
2909 }
2910 ]
2911 }
2912 }
2913 }
2914 });
2915 let schema_fragment =
2916 serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2917 let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2918 let view_photo = schema
2919 .action_entities_iter()
2920 .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2921 .unwrap();
2922 let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2923 let read = ancestors[0];
2924 assert_eq!(read.eid().to_string(), "read");
2925 assert_eq!(
2926 read.entity_type().to_string(),
2927 "ExampleCo::Personnel::Action"
2928 );
2929 }
2930}