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