1use std::collections::{hash_map::Entry, HashMap, HashSet};
24
25use cedar_policy_core::{
26 ast::{Eid, Entity, EntityType, EntityUID, Id, Name, RestrictedExpr},
27 entities::{Entities, JSONValue, TCComputation},
28 parser::{err::ParseError, parse_name, parse_namespace},
29 transitive_closure::{compute_tc, TCNode},
30};
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33use smol_str::SmolStr;
34
35use crate::{
36 schema_file_format,
37 types::{AttributeType, Attributes, EntityRecordKind, Type},
38 ActionEntityUID, ActionType, SchemaFragment, SchemaType, SchemaTypeVariant, TypeOfAttribute,
39 SCHEMA_TYPE_VARIANT_TAGS,
40};
41
42use super::err::*;
43use super::NamespaceDefinition;
44
45pub(crate) static ACTION_ENTITY_TYPE: &str = "Action";
50
51pub(crate) fn is_action_entity_type(ty: &Name) -> bool {
55 ty.basename().as_ref() == ACTION_ENTITY_TYPE
56}
57
58#[derive(Eq, PartialEq, Copy, Clone, Default)]
60pub enum ActionBehavior {
61 #[default]
64 ProhibitAttributes,
65 PermitAttributes,
67}
68
69#[derive(Debug)]
77pub struct ValidatorNamespaceDef {
78 namespace: Option<Name>,
83 type_defs: TypeDefs,
86 entity_types: EntityTypesDef,
88 actions: ActionsDef,
90}
91
92#[derive(Debug)]
95pub struct TypeDefs {
96 type_defs: HashMap<Name, Type>,
97}
98
99#[derive(Debug)]
102pub struct EntityTypesDef {
103 attributes: HashMap<Name, WithUnresolvedTypeDefs<Type>>,
110 children: HashMap<Name, HashSet<Name>>,
120}
121
122#[derive(Debug)]
125pub struct ActionsDef {
126 context_applies_to: HashMap<EntityUID, (WithUnresolvedTypeDefs<Type>, ValidatorApplySpec)>,
133 children: HashMap<EntityUID, HashSet<EntityUID>>,
137 attribute_types: HashMap<EntityUID, Attributes>,
139 attributes: HashMap<EntityUID, HashMap<SmolStr, RestrictedExpr>>,
141}
142
143type ResolveFunc<T> = dyn FnOnce(&HashMap<Name, Type>) -> Result<T>;
144pub enum WithUnresolvedTypeDefs<T> {
147 WithUnresolved(Box<ResolveFunc<T>>),
148 WithoutUnresolved(T),
149}
150
151impl<T: 'static> WithUnresolvedTypeDefs<T> {
152 pub fn new(f: impl FnOnce(&HashMap<Name, Type>) -> Result<T> + 'static) -> Self {
153 Self::WithUnresolved(Box::new(f))
154 }
155
156 pub fn map<U: 'static>(self, f: impl FnOnce(T) -> U + 'static) -> WithUnresolvedTypeDefs<U> {
157 match self {
158 Self::WithUnresolved(_) => {
159 WithUnresolvedTypeDefs::new(|type_defs| self.resolve_type_defs(type_defs).map(f))
160 }
161 Self::WithoutUnresolved(v) => WithUnresolvedTypeDefs::WithoutUnresolved(f(v)),
162 }
163 }
164
165 pub fn resolve_type_defs(self, type_defs: &HashMap<Name, Type>) -> Result<T> {
168 match self {
169 WithUnresolvedTypeDefs::WithUnresolved(f) => f(type_defs),
170 WithUnresolvedTypeDefs::WithoutUnresolved(v) => Ok(v),
171 }
172 }
173}
174
175impl<T: 'static> From<T> for WithUnresolvedTypeDefs<T> {
176 fn from(value: T) -> Self {
177 Self::WithoutUnresolved(value)
178 }
179}
180
181impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedTypeDefs<T> {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 match self {
184 WithUnresolvedTypeDefs::WithUnresolved(_) => f.debug_tuple("WithUnresolved").finish(),
185 WithUnresolvedTypeDefs::WithoutUnresolved(v) => {
186 f.debug_tuple("WithoutUnresolved").field(v).finish()
187 }
188 }
189 }
190}
191
192impl TryInto<ValidatorNamespaceDef> for NamespaceDefinition {
193 type Error = SchemaError;
194
195 fn try_into(self) -> Result<ValidatorNamespaceDef> {
196 ValidatorNamespaceDef::from_namespace_definition(None, self, ActionBehavior::default())
197 }
198}
199
200impl ValidatorNamespaceDef {
201 pub fn from_namespace_definition(
205 namespace: Option<SmolStr>,
206 namespace_def: NamespaceDefinition,
207 action_behavior: ActionBehavior,
208 ) -> Result<ValidatorNamespaceDef> {
209 let mut e_types_ids: HashSet<SmolStr> = HashSet::new();
211 for name in namespace_def.entity_types.keys() {
212 if !e_types_ids.insert(name.clone()) {
213 return Err(SchemaError::DuplicateEntityType(name.to_string()));
215 }
216 }
217 let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
218 for name in namespace_def.actions.keys() {
219 if !a_name_eids.insert(name.clone()) {
220 return Err(SchemaError::DuplicateAction(name.to_string()));
222 }
223 }
224
225 let schema_namespace = namespace
226 .as_ref()
227 .map(|ns| parse_namespace(ns).map_err(SchemaError::NamespaceParseError))
228 .transpose()?
229 .unwrap_or_default();
230
231 Self::check_action_behavior(&namespace_def, action_behavior)?;
234
235 let type_defs =
238 Self::build_type_defs(namespace_def.common_types, schema_namespace.as_slice())?;
239 let actions = Self::build_action_ids(namespace_def.actions, schema_namespace.as_slice())?;
240 let entity_types =
241 Self::build_entity_types(namespace_def.entity_types, schema_namespace.as_slice())?;
242
243 Ok(ValidatorNamespaceDef {
244 namespace: {
245 let mut schema_namespace = schema_namespace;
246 schema_namespace
247 .pop()
248 .map(|last| Name::new(last, schema_namespace))
249 },
250 type_defs,
251 entity_types,
252 actions,
253 })
254 }
255
256 fn is_builtin_type_name(name: &SmolStr) -> bool {
257 SCHEMA_TYPE_VARIANT_TAGS
258 .iter()
259 .any(|type_name| name == type_name)
260 }
261
262 fn build_type_defs(
263 schema_file_type_def: HashMap<SmolStr, SchemaType>,
264 schema_namespace: &[Id],
265 ) -> Result<TypeDefs> {
266 let type_defs = schema_file_type_def
267 .into_iter()
268 .map(|(name_str, schema_ty)| -> Result<_> {
269 if Self::is_builtin_type_name(&name_str) {
270 return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
271 }
272 let name = Self::parse_unqualified_name_with_namespace(
273 &name_str,
274 schema_namespace.to_vec(),
275 )
276 .map_err(SchemaError::CommonTypeParseError)?;
277 let ty = Self::try_schema_type_into_validator_type(schema_namespace, schema_ty)?
278 .resolve_type_defs(&HashMap::new())?;
279 Ok((name, ty))
280 })
281 .collect::<Result<HashMap<_, _>>>()?;
282 Ok(TypeDefs { type_defs })
283 }
284
285 fn build_entity_types(
291 schema_files_types: HashMap<SmolStr, schema_file_format::EntityType>,
292 schema_namespace: &[Id],
293 ) -> Result<EntityTypesDef> {
294 let mut children: HashMap<Name, HashSet<Name>> = HashMap::new();
297 for (name, e) in &schema_files_types {
298 for parent in &e.member_of_types {
299 let parent_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
300 parent,
301 schema_namespace,
302 )
303 .map_err(SchemaError::EntityTypeParseError)?;
304 children
305 .entry(parent_type_name)
306 .or_insert_with(HashSet::new)
307 .insert(
308 Self::parse_unqualified_name_with_namespace(
309 name,
310 schema_namespace.to_vec(),
311 )
312 .map_err(SchemaError::EntityTypeParseError)?,
313 );
314 }
315 }
316
317 let attributes = schema_files_types
318 .into_iter()
319 .map(|(name, e)| -> Result<_> {
320 let name: Name =
321 Self::parse_unqualified_name_with_namespace(&name, schema_namespace.to_vec())
322 .map_err(SchemaError::EntityTypeParseError)?;
323
324 let attributes = Self::try_schema_type_into_validator_type(
325 schema_namespace,
326 e.shape.into_inner(),
327 )?;
328
329 Ok((name, attributes))
330 })
331 .collect::<Result<HashMap<_, _>>>()?;
332
333 Ok(EntityTypesDef {
334 attributes,
335 children,
336 })
337 }
338
339 fn jsonval_to_type_helper(v: &JSONValue) -> Result<Type> {
345 match v {
346 JSONValue::Bool(_) => Ok(Type::primitive_boolean()),
347 JSONValue::Long(_) => Ok(Type::primitive_long()),
348 JSONValue::String(_) => Ok(Type::primitive_string()),
349 JSONValue::Record(r) => {
350 let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
351 for (k, v_prime) in r {
352 let t = Self::jsonval_to_type_helper(v_prime);
353 match t {
354 Ok(ty) => required_attrs.insert(k.clone(), ty),
355 Err(e) => return Err(e),
356 };
357 }
358 Ok(Type::EntityOrRecord(EntityRecordKind::Record {
359 attrs: Attributes::with_required_attributes(required_attrs),
360 }))
361 }
362 JSONValue::Set(v) => match v.get(0) {
363 None => Err(SchemaError::ActionEntityAttributeEmptySet),
365 Some(element) => {
366 let element_type = Self::jsonval_to_type_helper(element);
367 match element_type {
368 Ok(t) => Ok(Type::Set {
369 element_type: Some(Box::new(t)),
370 }),
371 Err(_) => element_type,
372 }
373 }
374 },
375 _ => Err(SchemaError::ActionEntityAttributeUnsupportedType),
376 }
377 }
378
379 fn convert_attr_jsonval_map_to_attributes(
381 m: HashMap<SmolStr, JSONValue>,
382 ) -> Result<(Attributes, HashMap<SmolStr, RestrictedExpr>)> {
383 let mut attr_types: HashMap<SmolStr, Type> = HashMap::new();
384 let mut attr_values: HashMap<SmolStr, RestrictedExpr> = HashMap::new();
385
386 for (k, v) in m {
387 let t = Self::jsonval_to_type_helper(&v);
388 match t {
389 Ok(ty) => attr_types.insert(k.clone(), ty),
390 Err(e) => return Err(e),
391 };
392
393 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`");
402 attr_values.insert(k.clone(), e);
403 }
404 Ok((
405 Attributes::with_required_attributes(attr_types),
406 attr_values,
407 ))
408 }
409
410 fn build_action_ids(
416 schema_file_actions: HashMap<SmolStr, ActionType>,
417 schema_namespace: &[Id],
418 ) -> Result<ActionsDef> {
419 let mut children: HashMap<EntityUID, HashSet<EntityUID>> = HashMap::new();
422 for (name, a) in &schema_file_actions {
423 let parents = match &a.member_of {
424 Some(parents) => parents,
425 None => continue,
426 };
427 for parent in parents {
428 let parent_euid =
429 Self::parse_action_id_with_namespace(parent, schema_namespace.to_vec())?;
430 children
431 .entry(parent_euid)
432 .or_insert_with(HashSet::new)
433 .insert(Self::parse_action_id_with_namespace(
434 &ActionEntityUID::default_type(name.clone()),
435 schema_namespace.to_vec(),
436 )?);
437 }
438 }
439
440 let context_applies_to = schema_file_actions
441 .clone()
442 .into_iter()
443 .map(|(name, a)| -> Result<_> {
444 let action_euid = Self::parse_action_id_with_namespace(
445 &ActionEntityUID::default_type(name),
446 schema_namespace.to_vec(),
447 )?;
448
449 let (principal_types, resource_types, context) = a
450 .applies_to
451 .map(|applies_to| {
452 (
453 applies_to.principal_types,
454 applies_to.resource_types,
455 applies_to.context,
456 )
457 })
458 .unwrap_or_default();
459
460 let action_applies_to = ValidatorApplySpec::new(
464 Self::parse_apply_spec_type_list(principal_types, schema_namespace)?,
465 Self::parse_apply_spec_type_list(resource_types, schema_namespace)?,
466 );
467
468 let action_context = Self::try_schema_type_into_validator_type(
469 schema_namespace,
470 context.into_inner(),
471 )?;
472
473 Ok((action_euid, (action_context, action_applies_to)))
474 })
475 .collect::<Result<HashMap<_, _>>>()?;
476
477 let mut attributes = HashMap::new();
478 let mut attribute_types = HashMap::new();
479 for (name, a) in schema_file_actions {
480 let action_euid = Self::parse_action_id_with_namespace(
481 &ActionEntityUID::default_type(name),
482 schema_namespace.to_vec(),
483 )?;
484
485 match Self::convert_attr_jsonval_map_to_attributes(a.attributes.unwrap_or_default()) {
486 Ok((curr_attribute_types, curr_attributes)) => {
488 attributes.insert(action_euid.clone(), curr_attributes);
489 attribute_types.insert(action_euid, curr_attribute_types);
490 }
491 Err(e) => return Err(e),
492 }
493 }
494
495 Ok(ActionsDef {
496 context_applies_to,
497 children,
498 attributes,
499 attribute_types,
500 })
501 }
502
503 fn check_action_behavior(
509 schema_file: &NamespaceDefinition,
510 action_behavior: ActionBehavior,
511 ) -> Result<()> {
512 if schema_file
513 .entity_types
514 .iter()
515 .any(|(name, _)| name == ACTION_ENTITY_TYPE)
519 {
520 return Err(SchemaError::ActionEntityTypeDeclared);
521 }
522 if action_behavior == ActionBehavior::ProhibitAttributes {
523 let mut actions_with_attributes: Vec<String> = Vec::new();
524 for (name, a) in &schema_file.actions {
525 if a.attributes.is_some() {
526 actions_with_attributes.push(name.to_string());
527 }
528 }
529 if !actions_with_attributes.is_empty() {
530 return Err(SchemaError::ActionEntityAttributes(actions_with_attributes));
531 }
532 }
533
534 Ok(())
535 }
536
537 fn parse_record_attributes(
542 schema_namespace: &[Id],
543 attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
544 ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
545 let attrs_with_type_defs = attrs
546 .into_iter()
547 .map(|(attr, ty)| -> Result<_> {
548 Ok((
549 attr,
550 (
551 Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
552 ty.required,
553 ),
554 ))
555 })
556 .collect::<Result<Vec<_>>>()?;
557 Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
558 attrs_with_type_defs
559 .into_iter()
560 .map(|(s, (attr_ty, is_req))| {
561 attr_ty
562 .resolve_type_defs(typ_defs)
563 .map(|ty| (s, AttributeType::new(ty, is_req)))
564 })
565 .collect::<Result<Vec<_>>>()
566 .map(Attributes::with_attributes)
567 }))
568 }
569
570 fn parse_apply_spec_type_list(
575 types: Option<Vec<SmolStr>>,
576 namespace: &[Id],
577 ) -> Result<HashSet<EntityType>> {
578 types
579 .map(|types| {
580 types
581 .iter()
582 .map(|ty_str| {
586 Ok(EntityType::Concrete(
587 Self::parse_possibly_qualified_name_with_default_namespace(
588 ty_str, namespace,
589 )
590 .map_err(SchemaError::EntityTypeParseError)?,
591 ))
592 })
593 .collect::<Result<HashSet<_>>>()
595 })
596 .unwrap_or_else(|| Ok(HashSet::from([EntityType::Unspecified])))
597 }
598
599 pub(crate) fn parse_possibly_qualified_name_with_default_namespace(
604 name_str: &SmolStr,
605 default_namespace: &[Id],
606 ) -> std::result::Result<Name, Vec<ParseError>> {
607 let name = parse_name(name_str)?;
608
609 let qualified_name =
610 if name.namespace_components().next().is_none() && !default_namespace.is_empty() {
611 Name::new(name.basename().clone(), default_namespace.to_vec())
614 } else {
615 name
617 };
618
619 Ok(qualified_name)
620 }
621
622 fn parse_unqualified_name_with_namespace(
626 type_name: &SmolStr,
627 namespace: Vec<Id>,
628 ) -> std::result::Result<Name, Vec<ParseError>> {
629 Ok(Name::new(type_name.parse()?, namespace))
630 }
631
632 fn parse_action_id_with_namespace(
638 action_id: &ActionEntityUID,
639 namespace: Vec<Id>,
640 ) -> Result<EntityUID> {
641 let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
642 action_ty
643 .parse()
644 .map_err(SchemaError::EntityTypeParseError)?
645 } else {
646 Name::new(
647 ACTION_ENTITY_TYPE.parse().expect(
648 "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
649 ),
650 namespace,
651 )
652 };
653 Ok(EntityUID::from_components(
654 namespaced_action_type,
655 Eid::new(action_id.id.clone()),
656 ))
657 }
658
659 pub(crate) fn try_schema_type_into_validator_type(
665 default_namespace: &[Id],
666 schema_ty: SchemaType,
667 ) -> Result<WithUnresolvedTypeDefs<Type>> {
668 match schema_ty {
669 SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
670 SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
671 SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
672 SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
673 Self::try_schema_type_into_validator_type(default_namespace, *element)?
674 .map(Type::set),
675 ),
676 SchemaType::Type(SchemaTypeVariant::Record {
677 attributes,
678 additional_attributes,
679 }) => {
680 if additional_attributes {
681 Err(SchemaError::UnsupportedSchemaFeature(
682 UnsupportedFeature::OpenRecordsAndEntities,
683 ))
684 } else {
685 Ok(
686 Self::parse_record_attributes(default_namespace, attributes)?
687 .map(Type::record_with_attributes),
688 )
689 }
690 }
691 SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
692 let entity_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
693 &name,
694 default_namespace,
695 )
696 .map_err(SchemaError::EntityTypeParseError)?;
697 Ok(Type::named_entity_reference(entity_type_name).into())
698 }
699 SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
700 let extension_type_name =
701 name.parse().map_err(SchemaError::ExtensionTypeParseError)?;
702 Ok(Type::extension(extension_type_name).into())
703 }
704 SchemaType::TypeDef { type_name } => {
705 let defined_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
706 &type_name,
707 default_namespace,
708 )
709 .map_err(SchemaError::CommonTypeParseError)?;
710 Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
711 typ_defs.get(&defined_type_name).cloned().ok_or(
712 SchemaError::UndeclaredCommonType(HashSet::from([type_name.to_string()])),
713 )
714 }))
715 }
716 }
717 }
718
719 pub fn namespace(&self) -> &Option<Name> {
721 &self.namespace
722 }
723}
724
725#[derive(Debug)]
726pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
727
728impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
729 type Error = SchemaError;
730
731 fn try_into(self) -> Result<ValidatorSchemaFragment> {
732 ValidatorSchemaFragment::from_schema_fragment(self, ActionBehavior::default())
733 }
734}
735
736impl ValidatorSchemaFragment {
737 pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
738 Self(namespaces.into_iter().collect())
739 }
740
741 pub fn from_schema_fragment(
742 fragment: SchemaFragment,
743 action_behavior: ActionBehavior,
744 ) -> Result<Self> {
745 Ok(Self(
746 fragment
747 .0
748 .into_iter()
749 .map(|(fragment_ns, ns_def)| {
750 ValidatorNamespaceDef::from_namespace_definition(
751 Some(fragment_ns),
752 ns_def,
753 action_behavior,
754 )
755 })
756 .collect::<Result<Vec<_>>>()?,
757 ))
758 }
759
760 pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
762 self.0.iter().map(|d| d.namespace())
763 }
764}
765
766#[serde_as]
767#[derive(Clone, Debug, Serialize)]
768pub struct ValidatorSchema {
769 #[serde(rename = "entityTypes")]
771 #[serde_as(as = "Vec<(_, _)>")]
772 entity_types: HashMap<Name, ValidatorEntityType>,
773
774 #[serde(rename = "actionIds")]
776 #[serde_as(as = "Vec<(_, _)>")]
777 action_ids: HashMap<EntityUID, ValidatorActionId>,
778}
779
780impl std::str::FromStr for ValidatorSchema {
781 type Err = SchemaError;
782
783 fn from_str(s: &str) -> Result<Self> {
784 serde_json::from_str::<SchemaFragment>(s)?.try_into()
785 }
786}
787
788impl TryInto<ValidatorSchema> for NamespaceDefinition {
789 type Error = SchemaError;
790
791 fn try_into(self) -> Result<ValidatorSchema> {
792 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([self
793 .try_into(
794 )?])])
795 }
796}
797
798impl TryInto<ValidatorSchema> for SchemaFragment {
799 type Error = SchemaError;
800
801 fn try_into(self) -> Result<ValidatorSchema> {
802 ValidatorSchema::from_schema_fragments([self.try_into()?])
803 }
804}
805
806impl ValidatorSchema {
807 pub fn empty() -> ValidatorSchema {
809 Self {
810 entity_types: HashMap::new(),
811 action_ids: HashMap::new(),
812 }
813 }
814
815 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
818 Self::from_schema_file(
819 SchemaFragment::from_json_value(json)?,
820 ActionBehavior::default(),
821 )
822 }
823
824 pub fn from_file(file: impl std::io::Read) -> Result<Self> {
826 Self::from_schema_file(SchemaFragment::from_file(file)?, ActionBehavior::default())
827 }
828
829 pub fn from_schema_file(
830 schema_file: SchemaFragment,
831 action_behavior: ActionBehavior,
832 ) -> Result<ValidatorSchema> {
833 Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
834 schema_file,
835 action_behavior,
836 )?])
837 }
838
839 pub fn from_schema_fragments(
841 fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
842 ) -> Result<ValidatorSchema> {
843 let mut type_defs = HashMap::new();
844 let mut entity_attributes = HashMap::new();
845 let mut entity_children = HashMap::new();
846 let mut action_context_applies_to = HashMap::new();
847 let mut action_children = HashMap::new();
848 let mut action_attribute_types = HashMap::new();
849 let mut action_attributes = HashMap::new();
850
851 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
852 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, attrs) in ns_def.entity_types.attributes {
868 match entity_attributes.entry(name) {
869 Entry::Vacant(v) => v.insert(attrs),
870 Entry::Occupied(o) => {
871 return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
872 }
873 };
874 }
875 for (id, context_applies_to) in ns_def.actions.context_applies_to {
876 match action_context_applies_to.entry(id) {
877 Entry::Vacant(v) => v.insert(context_applies_to),
878 Entry::Occupied(o) => {
879 return Err(SchemaError::DuplicateAction(o.key().to_string()))
880 }
881 };
882 }
883 for (id, attrs) in ns_def.actions.attribute_types {
884 match action_attribute_types.entry(id) {
885 Entry::Vacant(v) => v.insert(attrs),
886 Entry::Occupied(o) => {
887 return Err(SchemaError::DuplicateAction(o.key().to_string()))
888 }
889 };
890 }
891 for (id, attr_vals) in ns_def.actions.attributes {
892 match action_attributes.entry(id) {
893 Entry::Vacant(v) => v.insert(attr_vals),
894 Entry::Occupied(o) => {
895 return Err(SchemaError::DuplicateAction(o.key().to_string()))
896 }
897 };
898 }
899
900 for (name, children) in ns_def.entity_types.children {
904 let current_children: &mut HashSet<_> = entity_children.entry(name).or_default();
905 for child in children {
906 current_children.insert(child);
907 }
908 }
909
910 for (id, children) in ns_def.actions.children {
911 let current_children: &mut HashSet<_> = action_children.entry(id).or_default();
912 for child in children {
913 current_children.insert(child);
914 }
915 }
916 }
917
918 let mut entity_types = entity_attributes
919 .into_iter()
920 .map(|(name, attributes)| -> Result<_> {
921 let descendants = entity_children.remove(&name).unwrap_or_default();
931 Ok((
932 name.clone(),
933 ValidatorEntityType {
934 name,
935 descendants,
936 attributes: Self::record_attributes_or_error(
937 attributes.resolve_type_defs(&type_defs)?,
938 )?,
939 },
940 ))
941 })
942 .collect::<Result<HashMap<_, _>>>()?;
943
944 let mut action_ids = action_context_applies_to
945 .into_iter()
946 .map(|(name, (context, applies_to))| -> Result<_> {
947 let descendants = action_children.remove(&name).unwrap_or_default();
948
949 let attribute_types = match action_attribute_types.remove(&name) {
950 Some(t) => t,
951 None => Attributes::with_attributes([]),
952 };
953
954 let attributes = action_attributes.remove(&name).unwrap_or_default();
955
956 Ok((
957 name.clone(),
958 ValidatorActionId {
959 name,
960 applies_to,
961 descendants,
962 context: Self::record_attributes_or_error(
963 context.resolve_type_defs(&type_defs)?,
964 )?,
965 attribute_types,
966 attributes,
967 },
968 ))
969 })
970 .collect::<Result<HashMap<_, _>>>()?;
971
972 compute_tc(&mut entity_types, false)?;
975 compute_tc(&mut action_ids, true)?;
978
979 Self::check_for_undeclared(
986 &entity_types,
987 entity_children.into_keys(),
988 &action_ids,
989 action_children.into_keys(),
990 )?;
991
992 Ok(ValidatorSchema {
993 entity_types,
994 action_ids,
995 })
996 }
997
998 fn check_for_undeclared(
1003 entity_types: &HashMap<Name, ValidatorEntityType>,
1004 undeclared_parent_entities: impl IntoIterator<Item = Name>,
1005 action_ids: &HashMap<EntityUID, ValidatorActionId>,
1006 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
1007 ) -> Result<()> {
1008 let mut undeclared_e = undeclared_parent_entities
1013 .into_iter()
1014 .map(|n| n.to_string())
1015 .collect::<HashSet<_>>();
1016 for entity_type in entity_types.values() {
1022 for (_, attr_typ) in entity_type.attributes() {
1023 Self::check_undeclared_in_type(
1024 &attr_typ.attr_type,
1025 entity_types,
1026 &mut undeclared_e,
1027 );
1028 }
1029 }
1030
1031 let undeclared_a = undeclared_parent_actions
1033 .into_iter()
1034 .map(|n| n.to_string())
1035 .collect::<HashSet<_>>();
1036 for action in action_ids.values() {
1040 for (_, attr_typ) in action.context.iter() {
1041 Self::check_undeclared_in_type(
1042 &attr_typ.attr_type,
1043 entity_types,
1044 &mut undeclared_e,
1045 );
1046 }
1047
1048 for p_entity in action.applies_to.applicable_principal_types() {
1049 match p_entity {
1050 EntityType::Concrete(p_entity) => {
1051 if !entity_types.contains_key(p_entity) {
1052 undeclared_e.insert(p_entity.to_string());
1053 }
1054 }
1055 EntityType::Unspecified => (),
1056 }
1057 }
1058
1059 for r_entity in action.applies_to.applicable_resource_types() {
1060 match r_entity {
1061 EntityType::Concrete(r_entity) => {
1062 if !entity_types.contains_key(r_entity) {
1063 undeclared_e.insert(r_entity.to_string());
1064 }
1065 }
1066 EntityType::Unspecified => (),
1067 }
1068 }
1069 }
1070 if !undeclared_e.is_empty() {
1071 return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
1072 }
1073 if !undeclared_a.is_empty() {
1074 return Err(SchemaError::UndeclaredActions(undeclared_a));
1075 }
1076
1077 Ok(())
1078 }
1079
1080 fn record_attributes_or_error(ty: Type) -> Result<Attributes> {
1081 match ty {
1082 Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => Ok(attrs),
1083 _ => Err(SchemaError::ContextOrShapeNotRecord),
1084 }
1085 }
1086
1087 fn check_undeclared_in_type(
1091 ty: &Type,
1092 entity_types: &HashMap<Name, ValidatorEntityType>,
1093 undeclared_types: &mut HashSet<String>,
1094 ) {
1095 match ty {
1096 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
1097 for name in lub.iter() {
1098 if !entity_types.contains_key(name) {
1099 undeclared_types.insert(name.to_string());
1100 }
1101 }
1102 }
1103
1104 Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => {
1105 for (_, attr_ty) in attrs.iter() {
1106 Self::check_undeclared_in_type(
1107 &attr_ty.attr_type,
1108 entity_types,
1109 undeclared_types,
1110 );
1111 }
1112 }
1113
1114 Type::Set {
1115 element_type: Some(element_type),
1116 } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
1117
1118 _ => (),
1119 }
1120 }
1121
1122 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
1124 self.action_ids.get(action_id)
1125 }
1126
1127 pub fn get_entity_type(&self, entity_type_id: &Name) -> Option<&ValidatorEntityType> {
1129 self.entity_types.get(entity_type_id)
1130 }
1131
1132 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
1134 self.action_ids.contains_key(action_id)
1135 }
1136
1137 pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
1139 self.entity_types.contains_key(entity_type)
1140 }
1141
1142 pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
1144 self.action_ids.keys()
1145 }
1146
1147 pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
1149 self.entity_types.keys()
1150 }
1151
1152 pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
1154 self.entity_types.iter()
1155 }
1156
1157 pub(crate) fn get_entity_eq<'a, H, K>(&self, var: H, euid: EntityUID) -> Option<K>
1160 where
1161 H: 'a + HeadVar<K>,
1162 K: 'a,
1163 {
1164 var.get_euid_component(euid)
1165 }
1166
1167 pub(crate) fn get_entities_in<'a, H, K>(
1170 &'a self,
1171 var: H,
1172 euid: EntityUID,
1173 ) -> impl Iterator<Item = K> + 'a
1174 where
1175 H: 'a + HeadVar<K>,
1176 K: 'a + Clone,
1177 {
1178 var.get_descendants_if_present(self, euid.clone())
1179 .into_iter()
1180 .flatten()
1181 .map(Clone::clone)
1182 .chain(var.get_euid_component_if_present(self, euid).into_iter())
1183 }
1184
1185 pub(crate) fn get_entities_in_set<'a, H, K>(
1188 &'a self,
1189 var: H,
1190 euids: impl IntoIterator<Item = EntityUID> + 'a,
1191 ) -> impl Iterator<Item = K> + 'a
1192 where
1193 H: 'a + HeadVar<K>,
1194 K: 'a + Clone,
1195 {
1196 euids
1197 .into_iter()
1198 .flat_map(move |e| self.get_entities_in(var, e))
1199 }
1200
1201 pub fn get_context_schema(
1206 &self,
1207 action: &EntityUID,
1208 ) -> Option<impl cedar_policy_core::entities::ContextSchema> {
1209 self.get_action_id(action).map(|action_id| {
1210 crate::types::Type::record_with_attributes(
1211 action_id
1212 .context
1213 .iter()
1214 .map(|(k, v)| (k.clone(), v.clone())),
1215 )
1216 })
1217 }
1218
1219 pub fn action_entities(&self) -> cedar_policy_core::entities::Result<Entities> {
1220 let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
1228 for (action_euid, action_def) in &self.action_ids {
1229 for descendant in &action_def.descendants {
1230 action_ancestors
1231 .entry(descendant)
1232 .or_default()
1233 .insert(action_euid.clone());
1234 }
1235 }
1236
1237 Entities::from_entities(
1238 self.action_ids.iter().map(|(action_id, action)| {
1239 Entity::new(
1240 action_id.clone(),
1241 action.attributes.clone(),
1242 action_ancestors.remove(action_id).unwrap_or_default(),
1243 )
1244 }),
1245 TCComputation::AssumeAlreadyComputed,
1246 )
1247 }
1248}
1249
1250impl cedar_policy_core::entities::Schema for ValidatorSchema {
1251 fn attr_type(
1252 &self,
1253 entity_type: &cedar_policy_core::ast::EntityType,
1254 attr: &str,
1255 ) -> Option<cedar_policy_core::entities::SchemaType> {
1256 match entity_type {
1257 cedar_policy_core::ast::EntityType::Unspecified => None, cedar_policy_core::ast::EntityType::Concrete(name) => {
1259 let entity_type: &ValidatorEntityType = self.get_entity_type(name)?;
1260 let validator_type: &crate::types::Type = &entity_type.attr(attr)?.attr_type;
1261 let core_schema_type: cedar_policy_core::entities::SchemaType = validator_type
1262 .clone()
1263 .try_into()
1264 .expect("failed to convert validator type into Core SchemaType");
1265 debug_assert!(validator_type.is_consistent_with(&core_schema_type));
1266 Some(core_schema_type)
1267 }
1268 }
1269 }
1270
1271 fn required_attrs<'s>(
1272 &'s self,
1273 entity_type: &cedar_policy_core::ast::EntityType,
1274 ) -> Box<dyn Iterator<Item = SmolStr> + 's> {
1275 match entity_type {
1276 cedar_policy_core::ast::EntityType::Unspecified => Box::new(std::iter::empty()), cedar_policy_core::ast::EntityType::Concrete(name) => {
1278 match self.get_entity_type(name) {
1279 None => Box::new(std::iter::empty()),
1280 Some(entity_type) => Box::new(
1281 entity_type
1282 .attributes
1283 .iter()
1284 .filter(|(_, ty)| ty.is_required)
1285 .map(|(attr, _)| attr.clone()),
1286 ),
1287 }
1288 }
1289 }
1290 }
1291}
1292
1293impl cedar_policy_core::entities::ContextSchema for crate::types::Type {
1295 fn context_type(&self) -> cedar_policy_core::entities::SchemaType {
1296 self.clone()
1297 .try_into()
1298 .expect("failed to convert validator type into Core SchemaType")
1299 }
1300}
1301
1302#[derive(Clone, Debug, Serialize)]
1306pub struct ValidatorEntityType {
1307 pub(crate) name: Name,
1309
1310 pub descendants: HashSet<Name>,
1315
1316 pub(crate) attributes: Attributes,
1319}
1320
1321impl ValidatorEntityType {
1322 pub fn attr(&self, attr: &str) -> Option<&AttributeType> {
1324 self.attributes.get_attr(attr)
1325 }
1326
1327 pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1329 self.attributes.iter()
1330 }
1331}
1332
1333impl TCNode<Name> for ValidatorEntityType {
1334 fn get_key(&self) -> Name {
1335 self.name.clone()
1336 }
1337
1338 fn add_edge_to(&mut self, k: Name) {
1339 self.descendants.insert(k);
1340 }
1341
1342 fn out_edges(&self) -> Box<dyn Iterator<Item = &Name> + '_> {
1343 Box::new(self.descendants.iter())
1344 }
1345
1346 fn has_edge_to(&self, e: &Name) -> bool {
1347 self.descendants.contains(e)
1348 }
1349}
1350
1351#[derive(Clone, Debug, Serialize)]
1355pub struct ValidatorActionId {
1356 pub(crate) name: EntityUID,
1358
1359 #[serde(rename = "appliesTo")]
1361 pub(crate) applies_to: ValidatorApplySpec,
1362
1363 pub(crate) descendants: HashSet<EntityUID>,
1368
1369 pub(crate) context: Attributes,
1372
1373 pub(crate) attribute_types: Attributes,
1375
1376 pub(crate) attributes: HashMap<SmolStr, RestrictedExpr>,
1380}
1381
1382impl ValidatorActionId {
1383 pub fn context(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1385 self.context.iter()
1386 }
1387}
1388
1389impl TCNode<EntityUID> for ValidatorActionId {
1390 fn get_key(&self) -> EntityUID {
1391 self.name.clone()
1392 }
1393
1394 fn add_edge_to(&mut self, k: EntityUID) {
1395 self.descendants.insert(k);
1396 }
1397
1398 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
1399 Box::new(self.descendants.iter())
1400 }
1401
1402 fn has_edge_to(&self, e: &EntityUID) -> bool {
1403 self.descendants.contains(e)
1404 }
1405}
1406
1407#[derive(Clone, Debug, Serialize)]
1409pub(crate) struct ValidatorApplySpec {
1410 #[serde(rename = "principalApplySpec")]
1417 principal_apply_spec: HashSet<EntityType>,
1418
1419 #[serde(rename = "resourceApplySpec")]
1422 resource_apply_spec: HashSet<EntityType>,
1423}
1424
1425impl ValidatorApplySpec {
1426 pub(crate) fn new(
1429 principal_apply_spec: HashSet<EntityType>,
1430 resource_apply_spec: HashSet<EntityType>,
1431 ) -> Self {
1432 Self {
1433 principal_apply_spec,
1434 resource_apply_spec,
1435 }
1436 }
1437
1438 pub(crate) fn applicable_principal_types(&self) -> impl Iterator<Item = &EntityType> {
1440 self.principal_apply_spec.iter()
1441 }
1442
1443 pub(crate) fn applicable_resource_types(&self) -> impl Iterator<Item = &EntityType> {
1445 self.resource_apply_spec.iter()
1446 }
1447}
1448
1449pub(crate) trait HeadVar<K>: Copy {
1452 fn get_known_vars<'a>(
1456 &self,
1457 schema: &'a ValidatorSchema,
1458 ) -> Box<dyn Iterator<Item = &'a K> + 'a>;
1459
1460 fn get_euid_component(&self, euid: EntityUID) -> Option<K>;
1463
1464 fn get_euid_component_if_present(&self, schema: &ValidatorSchema, euid: EntityUID)
1467 -> Option<K>;
1468
1469 fn get_descendants_if_present<'a>(
1472 &self,
1473 schema: &'a ValidatorSchema,
1474 euid: EntityUID,
1475 ) -> Option<Box<dyn Iterator<Item = &'a K> + 'a>>;
1476}
1477
1478#[derive(Debug, Clone, Copy)]
1482pub(crate) enum PrincipalOrResourceHeadVar {
1483 PrincipalOrResource,
1484}
1485
1486impl HeadVar<Name> for PrincipalOrResourceHeadVar {
1487 fn get_known_vars<'a>(
1488 &self,
1489 schema: &'a ValidatorSchema,
1490 ) -> Box<dyn Iterator<Item = &'a Name> + 'a> {
1491 Box::new(schema.known_entity_types())
1492 }
1493
1494 fn get_euid_component(&self, euid: EntityUID) -> Option<Name> {
1495 let (ty, _) = euid.components();
1496 match ty {
1497 EntityType::Unspecified => None,
1498 EntityType::Concrete(name) => Some(name),
1499 }
1500 }
1501
1502 fn get_euid_component_if_present(
1503 &self,
1504 schema: &ValidatorSchema,
1505 euid: EntityUID,
1506 ) -> Option<Name> {
1507 let euid_component = self.get_euid_component(euid)?;
1508 if schema.is_known_entity_type(&euid_component) {
1509 Some(euid_component)
1510 } else {
1511 None
1512 }
1513 }
1514
1515 fn get_descendants_if_present<'a>(
1516 &self,
1517 schema: &'a ValidatorSchema,
1518 euid: EntityUID,
1519 ) -> Option<Box<dyn Iterator<Item = &'a Name> + 'a>> {
1520 let euid_component = self.get_euid_component(euid)?;
1521 match schema.get_entity_type(&euid_component) {
1522 Some(entity_type) => Some(Box::new(entity_type.descendants.iter())),
1523 None => None,
1524 }
1525 }
1526}
1527
1528#[derive(Debug, Clone, Copy)]
1531pub(crate) enum ActionHeadVar {
1532 Action,
1533}
1534
1535impl HeadVar<EntityUID> for ActionHeadVar {
1536 fn get_known_vars<'a>(
1537 &self,
1538 schema: &'a ValidatorSchema,
1539 ) -> Box<dyn Iterator<Item = &'a EntityUID> + 'a> {
1540 Box::new(schema.known_action_ids())
1541 }
1542
1543 fn get_euid_component(&self, euid: EntityUID) -> Option<EntityUID> {
1544 Some(euid)
1545 }
1546
1547 fn get_euid_component_if_present(
1548 &self,
1549 schema: &ValidatorSchema,
1550 euid: EntityUID,
1551 ) -> Option<EntityUID> {
1552 let euid_component = self.get_euid_component(euid)?;
1553 if schema.is_known_action_id(&euid_component) {
1554 Some(euid_component)
1555 } else {
1556 None
1557 }
1558 }
1559
1560 fn get_descendants_if_present<'a>(
1561 &self,
1562 schema: &'a ValidatorSchema,
1563 euid: EntityUID,
1564 ) -> Option<Box<dyn Iterator<Item = &'a EntityUID> + 'a>> {
1565 let euid_component = self.get_euid_component(euid)?;
1566 match schema.get_action_id(&euid_component) {
1567 Some(action_id) => Some(Box::new(action_id.descendants.iter())),
1568 None => None,
1569 }
1570 }
1571}
1572
1573#[derive(Debug, Clone, Deserialize)]
1576#[serde(transparent)]
1577pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
1578
1579impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
1580 type Error = SchemaError;
1581
1582 fn try_into(self) -> Result<ValidatorSchema> {
1583 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
1584 ValidatorNamespaceDef::from_namespace_definition(
1585 None,
1586 self.0,
1587 crate::ActionBehavior::PermitAttributes,
1588 )?,
1589 ])])
1590 }
1591}
1592
1593#[cfg(test)]
1594mod test {
1595 use std::{collections::BTreeMap, str::FromStr};
1596
1597 use crate::types::Type;
1598
1599 use serde_json::json;
1600
1601 use super::*;
1602
1603 #[test]
1605 fn test_from_schema_file() {
1606 let src = json!(
1607 {
1608 "entityTypes": {
1609 "User": {
1610 "memberOfTypes": [ "Group" ]
1611 },
1612 "Group": {
1613 "memberOfTypes": []
1614 },
1615 "Photo": {
1616 "memberOfTypes": [ "Album" ]
1617 },
1618 "Album": {
1619 "memberOfTypes": []
1620 }
1621 },
1622 "actions": {
1623 "view_photo": {
1624 "appliesTo": {
1625 "principalTypes": ["User", "Group"],
1626 "resourceTypes": ["Photo"]
1627 }
1628 }
1629 }
1630 });
1631 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1632 let schema: Result<ValidatorSchema> = schema_file.try_into();
1633 assert!(schema.is_ok());
1634 }
1635
1636 #[test]
1638 fn test_from_schema_file_duplicate_entity() {
1639 let src = r#"
1642 {"": {
1643 "entityTypes": {
1644 "User": {
1645 "memberOfTypes": [ "Group" ]
1646 },
1647 "Group": {
1648 "memberOfTypes": []
1649 },
1650 "Photo": {
1651 "memberOfTypes": [ "Album" ]
1652 },
1653 "Photo": {
1654 "memberOfTypes": []
1655 }
1656 },
1657 "actions": {
1658 "view_photo": {
1659 "memberOf": [],
1660 "appliesTo": {
1661 "principalTypes": ["User", "Group"],
1662 "resourceTypes": ["Photo"]
1663 }
1664 }
1665 }
1666 }}"#;
1667
1668 match ValidatorSchema::from_str(src) {
1669 Err(SchemaError::ParseFileFormat(_)) => (),
1670 _ => panic!("Expected serde error due to duplicate entity type."),
1671 }
1672 }
1673
1674 #[test]
1676 fn test_from_schema_file_duplicate_action() {
1677 let src = r#"
1680 {"": {
1681 "entityTypes": {
1682 "User": {
1683 "memberOfTypes": [ "Group" ]
1684 },
1685 "Group": {
1686 "memberOfTypes": []
1687 },
1688 "Photo": {
1689 "memberOfTypes": []
1690 }
1691 },
1692 "actions": {
1693 "view_photo": {
1694 "memberOf": [],
1695 "appliesTo": {
1696 "principalTypes": ["User", "Group"],
1697 "resourceTypes": ["Photo"]
1698 }
1699 },
1700 "view_photo": { }
1701 }
1702 }"#;
1703 match ValidatorSchema::from_str(src) {
1704 Err(SchemaError::ParseFileFormat(_)) => (),
1705 _ => panic!("Expected serde error due to duplicate action type."),
1706 }
1707 }
1708
1709 #[test]
1711 fn test_from_schema_file_undefined_entities() {
1712 let src = json!(
1713 {
1714 "entityTypes": {
1715 "User": {
1716 "memberOfTypes": [ "Grop" ]
1717 },
1718 "Group": {
1719 "memberOfTypes": []
1720 },
1721 "Photo": {
1722 "memberOfTypes": []
1723 }
1724 },
1725 "actions": {
1726 "view_photo": {
1727 "appliesTo": {
1728 "principalTypes": ["Usr", "Group"],
1729 "resourceTypes": ["Phoot"]
1730 }
1731 }
1732 }
1733 });
1734 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1735 let schema: Result<ValidatorSchema> = schema_file.try_into();
1736 match schema {
1737 Ok(_) => panic!("from_schema_file should have failed"),
1738 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1739 assert_eq!(v.len(), 3)
1740 }
1741 _ => panic!("Unexpected error from from_schema_file"),
1742 }
1743 }
1744
1745 #[test]
1746 fn undefined_entity_namespace_member_of() {
1747 let src = json!(
1748 {"Foo": {
1749 "entityTypes": {
1750 "User": {
1751 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1752 },
1753 "Group": { }
1754 },
1755 "actions": {}
1756 }});
1757 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1758 let schema: Result<ValidatorSchema> = schema_file.try_into();
1759 match schema {
1760 Ok(_) => panic!("try_into should have failed"),
1761 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1762 assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1763 }
1764 _ => panic!("Unexpected error from try_into"),
1765 }
1766 }
1767
1768 #[test]
1769 fn undefined_entity_namespace_applies_to() {
1770 let src = json!(
1771 {"Foo": {
1772 "entityTypes": { "User": { }, "Photo": { } },
1773 "actions": {
1774 "view_photo": {
1775 "appliesTo": {
1776 "principalTypes": ["Foo::User", "Bar::User"],
1777 "resourceTypes": ["Photo", "Bar::Photo"],
1778 }
1779 }
1780 }
1781 }});
1782 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1783 let schema: Result<ValidatorSchema> = schema_file.try_into();
1784 match schema {
1785 Ok(_) => panic!("try_into should have failed"),
1786 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1787 assert_eq!(
1788 v,
1789 HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1790 )
1791 }
1792 _ => panic!("Unexpected error from try_into"),
1793 }
1794 }
1795
1796 #[test]
1798 fn test_from_schema_file_undefined_action() {
1799 let src = json!(
1800 {
1801 "entityTypes": {
1802 "User": {
1803 "memberOfTypes": [ "Group" ]
1804 },
1805 "Group": {
1806 "memberOfTypes": []
1807 },
1808 "Photo": {
1809 "memberOfTypes": []
1810 }
1811 },
1812 "actions": {
1813 "view_photo": {
1814 "memberOf": [ {"id": "photo_action"} ],
1815 "appliesTo": {
1816 "principalTypes": ["User", "Group"],
1817 "resourceTypes": ["Photo"]
1818 }
1819 }
1820 }
1821 });
1822 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1823 let schema: Result<ValidatorSchema> = schema_file.try_into();
1824 match schema {
1825 Ok(_) => panic!("from_schema_file should have failed"),
1826 Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1827 _ => panic!("Unexpected error from from_schema_file"),
1828 }
1829 }
1830
1831 #[test]
1834 fn test_from_schema_file_action_cycle1() {
1835 let src = json!(
1836 {
1837 "entityTypes": {},
1838 "actions": {
1839 "view_photo": {
1840 "memberOf": [ {"id": "view_photo"} ]
1841 }
1842 }
1843 });
1844 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1845 let schema: Result<ValidatorSchema> = schema_file.try_into();
1846 match schema {
1847 Ok(_) => panic!("from_schema_file should have failed"),
1848 Err(SchemaError::CycleInActionHierarchy) => (), e => panic!("Unexpected error from from_schema_file: {:?}", e),
1850 }
1851 }
1852
1853 #[test]
1856 fn test_from_schema_file_action_cycle2() {
1857 let src = json!(
1858 {
1859 "entityTypes": {},
1860 "actions": {
1861 "view_photo": {
1862 "memberOf": [ {"id": "edit_photo"} ]
1863 },
1864 "edit_photo": {
1865 "memberOf": [ {"id": "delete_photo"} ]
1866 },
1867 "delete_photo": {
1868 "memberOf": [ {"id": "view_photo"} ]
1869 },
1870 "other_action": {
1871 "memberOf": [ {"id": "edit_photo"} ]
1872 }
1873 }
1874 });
1875 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1876 let schema: Result<ValidatorSchema> = schema_file.try_into();
1877 match schema {
1878 Ok(x) => {
1879 println!("{:?}", x);
1880 panic!("from_schema_file should have failed");
1881 }
1882 Err(SchemaError::CycleInActionHierarchy) => (), e => panic!("Unexpected error from from_schema_file: {:?}", e),
1884 }
1885 }
1886
1887 #[test]
1888 fn namespaced_schema() {
1889 let src = r#"
1890 { "N::S": {
1891 "entityTypes": {
1892 "User": {},
1893 "Photo": {}
1894 },
1895 "actions": {
1896 "view_photo": {
1897 "appliesTo": {
1898 "principalTypes": ["User"],
1899 "resourceTypes": ["Photo"]
1900 }
1901 }
1902 }
1903 } }
1904 "#;
1905 let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1906 let schema: ValidatorSchema = schema_file
1907 .try_into()
1908 .expect("Namespaced schema failed to convert.");
1909 dbg!(&schema);
1910 let user_entity_type = &"N::S::User"
1911 .parse()
1912 .expect("Namespaced entity type should have parsed");
1913 let photo_entity_type = &"N::S::Photo"
1914 .parse()
1915 .expect("Namespaced entity type should have parsed");
1916 assert!(
1917 schema.entity_types.contains_key(user_entity_type),
1918 "Expected and entity type User."
1919 );
1920 assert!(
1921 schema.entity_types.contains_key(photo_entity_type),
1922 "Expected an entity type Photo."
1923 );
1924 assert_eq!(
1925 schema.entity_types.len(),
1926 2,
1927 "Expected exactly 2 entity types."
1928 );
1929 assert!(
1930 schema.action_ids.contains_key(
1931 &"N::S::Action::\"view_photo\""
1932 .parse()
1933 .expect("Namespaced action should have parsed")
1934 ),
1935 "Expected an action \"view_photo\"."
1936 );
1937 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1938
1939 let apply_spec = &schema
1940 .action_ids
1941 .values()
1942 .next()
1943 .expect("Expected Action")
1944 .applies_to;
1945 assert_eq!(
1946 apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1947 vec![&EntityType::Concrete(user_entity_type.clone())]
1948 );
1949 assert_eq!(
1950 apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1951 vec![&EntityType::Concrete(photo_entity_type.clone())]
1952 );
1953 }
1954
1955 #[test]
1956 fn cant_use_namespace_in_entity_type() {
1957 let src = r#"
1958 {
1959 "entityTypes": { "NS::User": {} },
1960 "actions": {}
1961 }
1962 "#;
1963 let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
1964 assert!(
1965 matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::EntityTypeParseError(_))),
1966 "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
1967 }
1968
1969 #[test]
1970 fn entity_attribute_entity_type_with_namespace() {
1971 let schema_json: SchemaFragment = serde_json::from_str(
1972 r#"
1973 {"A::B": {
1974 "entityTypes": {
1975 "Foo": {
1976 "shape": {
1977 "type": "Record",
1978 "attributes": {
1979 "name": { "type": "Entity", "name": "C::D::Foo" }
1980 }
1981 }
1982 }
1983 },
1984 "actions": {}
1985 }}
1986 "#,
1987 )
1988 .expect("Expected valid schema");
1989
1990 let schema: Result<ValidatorSchema> = schema_json.try_into();
1991 match schema {
1992 Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1993 assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1994 }
1995 _ => panic!("Schema construction should have failed due to undeclared entity type."),
1996 }
1997 }
1998
1999 #[test]
2000 fn entity_attribute_entity_type_with_declared_namespace() {
2001 let schema_json: SchemaFragment = serde_json::from_str(
2002 r#"
2003 {"A::B": {
2004 "entityTypes": {
2005 "Foo": {
2006 "shape": {
2007 "type": "Record",
2008 "attributes": {
2009 "name": { "type": "Entity", "name": "A::B::Foo" }
2010 }
2011 }
2012 }
2013 },
2014 "actions": {}
2015 }}
2016 "#,
2017 )
2018 .expect("Expected valid schema");
2019
2020 let schema: ValidatorSchema = schema_json
2021 .try_into()
2022 .expect("Expected schema to construct without error.");
2023
2024 let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
2025 let foo_type = schema
2026 .entity_types
2027 .get(&foo_name)
2028 .expect("Expected to find entity");
2029 let name_type = foo_type
2030 .attr("name")
2031 .expect("Expected attribute name")
2032 .attr_type
2033 .clone();
2034 let expected_name_type = Type::named_entity_reference(foo_name);
2035 assert_eq!(name_type, expected_name_type);
2036 }
2037
2038 #[test]
2039 fn cannot_declare_action_type_when_prohibited() {
2040 let schema_json: NamespaceDefinition = serde_json::from_str(
2041 r#"
2042 {
2043 "entityTypes": { "Action": {} },
2044 "actions": {}
2045 }
2046 "#,
2047 )
2048 .expect("Expected valid schema");
2049
2050 let schema: Result<ValidatorSchema> = schema_json.try_into();
2051 assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
2052 }
2053
2054 #[test]
2055 fn can_declare_other_type_when_action_type_prohibited() {
2056 let schema_json: NamespaceDefinition = serde_json::from_str(
2057 r#"
2058 {
2059 "entityTypes": { "Foo": { } },
2060 "actions": {}
2061 }
2062 "#,
2063 )
2064 .expect("Expected valid schema");
2065
2066 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
2067 }
2068
2069 #[test]
2070 fn cannot_declare_action_in_group_when_prohibited() {
2071 let schema_json: SchemaFragment = serde_json::from_str(
2072 r#"
2073 {"": {
2074 "entityTypes": {},
2075 "actions": {
2076 "universe": { },
2077 "view_photo": {
2078 "attributes": {"id": "universe"}
2079 },
2080 "edit_photo": {
2081 "attributes": {"id": "universe"}
2082 },
2083 "delete_photo": {
2084 "attributes": {"id": "universe"}
2085 }
2086 }
2087 }}
2088 "#,
2089 )
2090 .expect("Expected valid schema");
2091
2092 let schema = ValidatorSchemaFragment::from_schema_fragment(
2093 schema_json,
2094 ActionBehavior::ProhibitAttributes,
2095 );
2096 match schema {
2097 Err(SchemaError::ActionEntityAttributes(actions)) => {
2098 assert_eq!(
2099 actions.into_iter().collect::<HashSet<_>>(),
2100 HashSet::from([
2101 "view_photo".to_string(),
2102 "edit_photo".to_string(),
2103 "delete_photo".to_string(),
2104 ])
2105 )
2106 }
2107 _ => panic!("Did not see expected error."),
2108 }
2109 }
2110
2111 #[test]
2112 fn test_entity_type_no_namespace() {
2113 let src = json!({"type": "Entity", "name": "Foo"});
2114 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2115 assert_eq!(
2116 schema_ty,
2117 SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
2118 );
2119 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2120 &parse_namespace("NS").expect("Expected namespace."),
2121 schema_ty,
2122 )
2123 .expect("Error converting schema type to type.")
2124 .resolve_type_defs(&HashMap::new())
2125 .unwrap();
2126 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2127 }
2128
2129 #[test]
2130 fn test_entity_type_namespace() {
2131 let src = json!({"type": "Entity", "name": "NS::Foo"});
2132 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2133 assert_eq!(
2134 schema_ty,
2135 SchemaType::Type(SchemaTypeVariant::Entity {
2136 name: "NS::Foo".into()
2137 })
2138 );
2139 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2140 &parse_namespace("NS").expect("Expected namespace."),
2141 schema_ty,
2142 )
2143 .expect("Error converting schema type to type.")
2144 .resolve_type_defs(&HashMap::new())
2145 .unwrap();
2146 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2147 }
2148
2149 #[test]
2150 fn test_entity_type_namespace_parse_error() {
2151 let src = json!({"type": "Entity", "name": "::Foo"});
2152 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2153 assert_eq!(
2154 schema_ty,
2155 SchemaType::Type(SchemaTypeVariant::Entity {
2156 name: "::Foo".into()
2157 })
2158 );
2159 match ValidatorNamespaceDef::try_schema_type_into_validator_type(
2160 &parse_namespace("NS").expect("Expected namespace."),
2161 schema_ty,
2162 ) {
2163 Err(SchemaError::EntityTypeParseError(_)) => (),
2164 _ => panic!("Did not see expected EntityTypeParseError."),
2165 }
2166 }
2167
2168 #[test]
2169 fn schema_type_record_is_validator_type_record() {
2170 let src = json!({"type": "Record", "attributes": {}});
2171 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2172 assert_eq!(
2173 schema_ty,
2174 SchemaType::Type(SchemaTypeVariant::Record {
2175 attributes: BTreeMap::new(),
2176 additional_attributes: false,
2177 }),
2178 );
2179 let ty: Type =
2180 ValidatorNamespaceDef::try_schema_type_into_validator_type(&Vec::new(), schema_ty)
2181 .expect("Error converting schema type to type.")
2182 .resolve_type_defs(&HashMap::new())
2183 .unwrap();
2184 assert_eq!(ty, Type::record_with_attributes(None));
2185 }
2186
2187 #[test]
2188 fn get_namespaces() {
2189 let fragment: SchemaFragment = serde_json::from_value(json!({
2190 "Foo::Bar::Baz": {
2191 "entityTypes": {},
2192 "actions": {}
2193 },
2194 "Foo": {
2195 "entityTypes": {},
2196 "actions": {}
2197 },
2198 "Bar": {
2199 "entityTypes": {},
2200 "actions": {}
2201 },
2202 }))
2203 .unwrap();
2204
2205 let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
2206 assert_eq!(
2207 schema_fragment
2208 .0
2209 .iter()
2210 .map(|f| f.namespace())
2211 .collect::<HashSet<_>>(),
2212 HashSet::from([
2213 &Some("Foo::Bar::Baz".parse().unwrap()),
2214 &Some("Foo".parse().unwrap()),
2215 &Some("Bar".parse().unwrap())
2216 ])
2217 );
2218 }
2219
2220 #[test]
2221 fn schema_no_fragments() {
2222 let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
2223 assert!(schema.entity_types.is_empty());
2224 assert!(schema.action_ids.is_empty());
2225 }
2226
2227 #[test]
2228 fn same_action_different_namespace() {
2229 let fragment: SchemaFragment = serde_json::from_value(json!({
2230 "Foo::Bar": {
2231 "entityTypes": {},
2232 "actions": {
2233 "Baz": {}
2234 }
2235 },
2236 "Bar::Foo": {
2237 "entityTypes": {},
2238 "actions": {
2239 "Baz": { }
2240 }
2241 },
2242 "Biz": {
2243 "entityTypes": {},
2244 "actions": {
2245 "Baz": { }
2246 }
2247 }
2248 }))
2249 .unwrap();
2250
2251 let schema: ValidatorSchema = fragment.try_into().unwrap();
2252 assert!(schema
2253 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2254 .is_some());
2255 assert!(schema
2256 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
2257 .is_some());
2258 assert!(schema
2259 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2260 .is_some());
2261 }
2262
2263 #[test]
2264 fn same_type_different_namespace() {
2265 let fragment: SchemaFragment = serde_json::from_value(json!({
2266 "Foo::Bar": {
2267 "entityTypes": {"Baz" : {}},
2268 "actions": { }
2269 },
2270 "Bar::Foo": {
2271 "entityTypes": {"Baz" : {}},
2272 "actions": { }
2273 },
2274 "Biz": {
2275 "entityTypes": {"Baz" : {}},
2276 "actions": { }
2277 }
2278 }))
2279 .unwrap();
2280 let schema: ValidatorSchema = fragment.try_into().unwrap();
2281
2282 assert!(schema
2283 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
2284 .is_some());
2285 assert!(schema
2286 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
2287 .is_some());
2288 assert!(schema
2289 .get_entity_type(&"Biz::Baz".parse().unwrap())
2290 .is_some());
2291 }
2292
2293 #[test]
2294 fn member_of_different_namespace() {
2295 let fragment: SchemaFragment = serde_json::from_value(json!({
2296 "Bar": {
2297 "entityTypes": {
2298 "Baz": {
2299 "memberOfTypes": ["Foo::Buz"]
2300 }
2301 },
2302 "actions": {}
2303 },
2304 "Foo": {
2305 "entityTypes": { "Buz": {} },
2306 "actions": { }
2307 }
2308 }))
2309 .unwrap();
2310 let schema: ValidatorSchema = fragment.try_into().unwrap();
2311
2312 let buz = schema
2313 .get_entity_type(&"Foo::Buz".parse().unwrap())
2314 .unwrap();
2315 assert_eq!(
2316 buz.descendants,
2317 HashSet::from(["Bar::Baz".parse().unwrap()])
2318 );
2319 }
2320
2321 #[test]
2322 fn attribute_different_namespace() {
2323 let fragment: SchemaFragment = serde_json::from_value(json!({
2324 "Bar": {
2325 "entityTypes": {
2326 "Baz": {
2327 "shape": {
2328 "type": "Record",
2329 "attributes": {
2330 "fiz": {
2331 "type": "Entity",
2332 "name": "Foo::Buz"
2333 }
2334 }
2335 }
2336 }
2337 },
2338 "actions": {}
2339 },
2340 "Foo": {
2341 "entityTypes": { "Buz": {} },
2342 "actions": { }
2343 }
2344 }))
2345 .unwrap();
2346
2347 let schema: ValidatorSchema = fragment.try_into().unwrap();
2348 let baz = schema
2349 .get_entity_type(&"Bar::Baz".parse().unwrap())
2350 .unwrap();
2351 assert_eq!(
2352 baz.attr("fiz").unwrap().attr_type,
2353 Type::named_entity_reference_from_str("Foo::Buz"),
2354 );
2355 }
2356
2357 #[test]
2358 fn applies_to_different_namespace() {
2359 let fragment: SchemaFragment = serde_json::from_value(json!({
2360 "Foo::Bar": {
2361 "entityTypes": { },
2362 "actions": {
2363 "Baz": {
2364 "appliesTo": {
2365 "principalTypes": [ "Fiz::Buz" ],
2366 "resourceTypes": [ "Fiz::Baz" ],
2367 }
2368 }
2369 }
2370 },
2371 "Fiz": {
2372 "entityTypes": {
2373 "Buz": {},
2374 "Baz": {}
2375 },
2376 "actions": { }
2377 }
2378 }))
2379 .unwrap();
2380 let schema: ValidatorSchema = fragment.try_into().unwrap();
2381
2382 let baz = schema
2383 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2384 .unwrap();
2385 assert_eq!(
2386 baz.applies_to
2387 .applicable_principal_types()
2388 .collect::<HashSet<_>>(),
2389 HashSet::from([&EntityType::Concrete("Fiz::Buz".parse().unwrap())])
2390 );
2391 assert_eq!(
2392 baz.applies_to
2393 .applicable_resource_types()
2394 .collect::<HashSet<_>>(),
2395 HashSet::from([&EntityType::Concrete("Fiz::Baz".parse().unwrap())])
2396 );
2397 }
2398
2399 #[test]
2400 fn simple_defined_type() {
2401 let fragment: SchemaFragment = serde_json::from_value(json!({
2402 "": {
2403 "commonTypes": {
2404 "MyLong": {"type": "Long"}
2405 },
2406 "entityTypes": {
2407 "User": {
2408 "shape": {
2409 "type": "Record",
2410 "attributes": {
2411 "a": {"type": "MyLong"}
2412 }
2413 }
2414 }
2415 },
2416 "actions": {}
2417 }
2418 }))
2419 .unwrap();
2420 let schema: ValidatorSchema = fragment.try_into().unwrap();
2421 assert_eq!(
2422 schema.entity_types.iter().next().unwrap().1.attributes,
2423 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2424 );
2425 }
2426
2427 #[test]
2428 fn defined_record_as_attrs() {
2429 let fragment: SchemaFragment = serde_json::from_value(json!({
2430 "": {
2431 "commonTypes": {
2432 "MyRecord": {
2433 "type": "Record",
2434 "attributes": {
2435 "a": {"type": "Long"}
2436 }
2437 }
2438 },
2439 "entityTypes": {
2440 "User": { "shape": { "type": "MyRecord", } }
2441 },
2442 "actions": {}
2443 }
2444 }))
2445 .unwrap();
2446 let schema: ValidatorSchema = fragment.try_into().unwrap();
2447 assert_eq!(
2448 schema.entity_types.iter().next().unwrap().1.attributes,
2449 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2450 );
2451 }
2452
2453 #[test]
2454 fn cross_namespace_type() {
2455 let fragment: SchemaFragment = serde_json::from_value(json!({
2456 "A": {
2457 "commonTypes": {
2458 "MyLong": {"type": "Long"}
2459 },
2460 "entityTypes": { },
2461 "actions": {}
2462 },
2463 "B": {
2464 "entityTypes": {
2465 "User": {
2466 "shape": {
2467 "type": "Record",
2468 "attributes": {
2469 "a": {"type": "A::MyLong"}
2470 }
2471 }
2472 }
2473 },
2474 "actions": {}
2475 }
2476 }))
2477 .unwrap();
2478 let schema: ValidatorSchema = fragment.try_into().unwrap();
2479 assert_eq!(
2480 schema.entity_types.iter().next().unwrap().1.attributes,
2481 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2482 );
2483 }
2484
2485 #[test]
2486 fn cross_fragment_type() {
2487 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2488 "A": {
2489 "commonTypes": {
2490 "MyLong": {"type": "Long"}
2491 },
2492 "entityTypes": { },
2493 "actions": {}
2494 }
2495 }))
2496 .unwrap()
2497 .try_into()
2498 .unwrap();
2499 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2500 "A": {
2501 "entityTypes": {
2502 "User": {
2503 "shape": {
2504 "type": "Record",
2505 "attributes": {
2506 "a": {"type": "MyLong"}
2507 }
2508 }
2509 }
2510 },
2511 "actions": {}
2512 }
2513 }))
2514 .unwrap()
2515 .try_into()
2516 .unwrap();
2517 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2518
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 #[should_panic]
2527 fn cross_fragment_duplicate_type() {
2528 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2529 "A": {
2530 "commonTypes": {
2531 "MyLong": {"type": "Long"}
2532 },
2533 "entityTypes": {},
2534 "actions": {}
2535 }
2536 }))
2537 .unwrap()
2538 .try_into()
2539 .unwrap();
2540 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2541 "A": {
2542 "commonTypes": {
2543 "MyLong": {"type": "Long"}
2544 },
2545 "entityTypes": {},
2546 "actions": {}
2547 }
2548 }))
2549 .unwrap()
2550 .try_into()
2551 .unwrap();
2552 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2553
2554 assert_eq!(
2555 schema.entity_types.iter().next().unwrap().1.attributes,
2556 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2557 );
2558 }
2559
2560 #[test]
2561 fn undeclared_type_in_attr() {
2562 let fragment: SchemaFragment = serde_json::from_value(json!({
2563 "": {
2564 "commonTypes": { },
2565 "entityTypes": {
2566 "User": {
2567 "shape": {
2568 "type": "Record",
2569 "attributes": {
2570 "a": {"type": "MyLong"}
2571 }
2572 }
2573 }
2574 },
2575 "actions": {}
2576 }
2577 }))
2578 .unwrap();
2579 match TryInto::<ValidatorSchema>::try_into(fragment) {
2580 Err(SchemaError::UndeclaredCommonType(_)) => (),
2581 s => panic!(
2582 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2583 s
2584 ),
2585 }
2586 }
2587
2588 #[test]
2589 fn undeclared_type_in_type_def() {
2590 let fragment: SchemaFragment = serde_json::from_value(json!({
2591 "": {
2592 "commonTypes": {
2593 "a": { "type": "b" }
2594 },
2595 "entityTypes": { },
2596 "actions": {}
2597 }
2598 }))
2599 .unwrap();
2600 match TryInto::<ValidatorSchema>::try_into(fragment) {
2601 Err(SchemaError::UndeclaredCommonType(_)) => (),
2602 s => panic!(
2603 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2604 s
2605 ),
2606 }
2607 }
2608
2609 #[test]
2610 fn shape_not_record() {
2611 let fragment: SchemaFragment = serde_json::from_value(json!({
2612 "": {
2613 "commonTypes": {
2614 "MyLong": { "type": "Long" }
2615 },
2616 "entityTypes": {
2617 "User": {
2618 "shape": { "type": "MyLong" }
2619 }
2620 },
2621 "actions": {}
2622 }
2623 }))
2624 .unwrap();
2625 match TryInto::<ValidatorSchema>::try_into(fragment) {
2626 Err(SchemaError::ContextOrShapeNotRecord) => (),
2627 s => panic!(
2628 "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
2629 s
2630 ),
2631 }
2632 }
2633
2634 #[test]
2635 fn simple_action_entity() {
2636 let src = json!(
2637 {
2638 "entityTypes": { },
2639 "actions": {
2640 "view_photo": { },
2641 }
2642 });
2643
2644 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2645 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2646 let actions = schema.action_entities().expect("Entity Construct Error");
2647
2648 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2649 let view_photo = actions.entity(&action_uid);
2650 assert_eq!(
2651 view_photo.unwrap(),
2652 &Entity::new(action_uid, HashMap::new(), HashSet::new())
2653 );
2654 }
2655
2656 #[test]
2657 fn action_entity_hierarchy() {
2658 let src = json!(
2659 {
2660 "entityTypes": { },
2661 "actions": {
2662 "read": {},
2663 "view": {
2664 "memberOf": [{"id": "read"}]
2665 },
2666 "view_photo": {
2667 "memberOf": [{"id": "view"}]
2668 },
2669 }
2670 });
2671
2672 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2673 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2674 let actions = schema.action_entities().expect("Entity Construct Error");
2675
2676 let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2677 let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2678 let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2679
2680 let view_photo_entity = actions.entity(&view_photo_uid);
2681 assert_eq!(
2682 view_photo_entity.unwrap(),
2683 &Entity::new(
2684 view_photo_uid,
2685 HashMap::new(),
2686 HashSet::from([view_uid.clone(), read_uid.clone()])
2687 )
2688 );
2689
2690 let view_entity = actions.entity(&view_uid);
2691 assert_eq!(
2692 view_entity.unwrap(),
2693 &Entity::new(view_uid, HashMap::new(), HashSet::from([read_uid.clone()]))
2694 );
2695
2696 let read_entity = actions.entity(&read_uid);
2697 assert_eq!(
2698 read_entity.unwrap(),
2699 &Entity::new(read_uid, HashMap::new(), HashSet::new())
2700 );
2701 }
2702
2703 #[test]
2704 fn action_entity_attribute() {
2705 let src = json!(
2706 {
2707 "entityTypes": { },
2708 "actions": {
2709 "view_photo": {
2710 "attributes": { "attr": "foo" }
2711 },
2712 }
2713 });
2714
2715 let schema_file: NamespaceDefinitionWithActionAttributes =
2716 serde_json::from_value(src).expect("Parse Error");
2717 let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2718 let actions = schema.action_entities().expect("Entity Construct Error");
2719
2720 let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2721 let view_photo = actions.entity(&action_uid);
2722 assert_eq!(
2723 view_photo.unwrap(),
2724 &Entity::new(
2725 action_uid,
2726 HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2727 HashSet::new()
2728 )
2729 );
2730 }
2731}