1use std::collections::{hash_map::Entry, HashMap, HashSet};
24
25use cedar_policy_core::{
26 ast::{Eid, EntityType, EntityUID, Id, Name},
27 entities::JSONValue,
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 attributes: HashMap<EntityUID, Attributes>,
139}
140
141type ResolveFunc<T> = dyn FnOnce(&HashMap<Name, Type>) -> Result<T>;
142pub enum WithUnresolvedTypeDefs<T> {
145 WithUnresolved(Box<ResolveFunc<T>>),
146 WithoutUnresolved(T),
147}
148
149impl<T: 'static> WithUnresolvedTypeDefs<T> {
150 pub fn new(f: impl FnOnce(&HashMap<Name, Type>) -> Result<T> + 'static) -> Self {
151 Self::WithUnresolved(Box::new(f))
152 }
153
154 pub fn map<U: 'static>(self, f: impl FnOnce(T) -> U + 'static) -> WithUnresolvedTypeDefs<U> {
155 match self {
156 Self::WithUnresolved(_) => {
157 WithUnresolvedTypeDefs::new(|type_defs| self.resolve_type_defs(type_defs).map(f))
158 }
159 Self::WithoutUnresolved(v) => WithUnresolvedTypeDefs::WithoutUnresolved(f(v)),
160 }
161 }
162
163 pub fn resolve_type_defs(self, type_defs: &HashMap<Name, Type>) -> Result<T> {
166 match self {
167 WithUnresolvedTypeDefs::WithUnresolved(f) => f(type_defs),
168 WithUnresolvedTypeDefs::WithoutUnresolved(v) => Ok(v),
169 }
170 }
171}
172
173impl<T: 'static> From<T> for WithUnresolvedTypeDefs<T> {
174 fn from(value: T) -> Self {
175 Self::WithoutUnresolved(value)
176 }
177}
178
179impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedTypeDefs<T> {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 match self {
182 WithUnresolvedTypeDefs::WithUnresolved(_) => f.debug_tuple("WithUnresolved").finish(),
183 WithUnresolvedTypeDefs::WithoutUnresolved(v) => {
184 f.debug_tuple("WithoutUnresolved").field(v).finish()
185 }
186 }
187 }
188}
189
190impl TryInto<ValidatorNamespaceDef> for NamespaceDefinition {
191 type Error = SchemaError;
192
193 fn try_into(self) -> Result<ValidatorNamespaceDef> {
194 ValidatorNamespaceDef::from_namespace_definition(None, self, ActionBehavior::default())
195 }
196}
197
198impl ValidatorNamespaceDef {
199 pub fn from_namespace_definition(
203 namespace: Option<SmolStr>,
204 namespace_def: NamespaceDefinition,
205 action_behavior: ActionBehavior,
206 ) -> Result<ValidatorNamespaceDef> {
207 let mut e_types_ids: HashSet<SmolStr> = HashSet::new();
209 for name in namespace_def.entity_types.keys() {
210 if !e_types_ids.insert(name.clone()) {
211 return Err(SchemaError::DuplicateEntityType(name.to_string()));
213 }
214 }
215 let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
216 for name in namespace_def.actions.keys() {
217 if !a_name_eids.insert(name.clone()) {
218 return Err(SchemaError::DuplicateAction(name.to_string()));
220 }
221 }
222
223 let schema_namespace = namespace
224 .as_ref()
225 .map(|ns| parse_namespace(ns).map_err(SchemaError::NamespaceParseError))
226 .transpose()?
227 .unwrap_or_default();
228
229 Self::check_action_behavior(&namespace_def, action_behavior)?;
232
233 let type_defs =
236 Self::build_type_defs(namespace_def.common_types, schema_namespace.as_slice())?;
237 let actions = Self::build_action_ids(namespace_def.actions, schema_namespace.as_slice())?;
238 let entity_types =
239 Self::build_entity_types(namespace_def.entity_types, schema_namespace.as_slice())?;
240
241 Ok(ValidatorNamespaceDef {
242 namespace: {
243 let mut schema_namespace = schema_namespace;
244 schema_namespace
245 .pop()
246 .map(|last| Name::new(last, schema_namespace))
247 },
248 type_defs,
249 entity_types,
250 actions,
251 })
252 }
253
254 fn is_builtin_type_name(name: &SmolStr) -> bool {
255 SCHEMA_TYPE_VARIANT_TAGS
256 .iter()
257 .any(|type_name| name == type_name)
258 }
259
260 fn build_type_defs(
261 schema_file_type_def: HashMap<SmolStr, SchemaType>,
262 schema_namespace: &[Id],
263 ) -> Result<TypeDefs> {
264 let type_defs = schema_file_type_def
265 .into_iter()
266 .map(|(name_str, schema_ty)| -> Result<_> {
267 if Self::is_builtin_type_name(&name_str) {
268 return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
269 }
270 let name = Self::parse_unqualified_name_with_namespace(
271 &name_str,
272 schema_namespace.to_vec(),
273 )
274 .map_err(SchemaError::CommonTypeParseError)?;
275 let ty = Self::try_schema_type_into_validator_type(schema_namespace, schema_ty)?
276 .resolve_type_defs(&HashMap::new())?;
277 Ok((name, ty))
278 })
279 .collect::<Result<HashMap<_, _>>>()?;
280 Ok(TypeDefs { type_defs })
281 }
282
283 fn build_entity_types(
289 schema_files_types: HashMap<SmolStr, schema_file_format::EntityType>,
290 schema_namespace: &[Id],
291 ) -> Result<EntityTypesDef> {
292 let mut children: HashMap<Name, HashSet<Name>> = HashMap::new();
295 for (name, e) in &schema_files_types {
296 for parent in &e.member_of_types {
297 let parent_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
298 parent,
299 schema_namespace,
300 )
301 .map_err(SchemaError::EntityTypeParseError)?;
302 children
303 .entry(parent_type_name)
304 .or_insert_with(HashSet::new)
305 .insert(
306 Self::parse_unqualified_name_with_namespace(
307 name,
308 schema_namespace.to_vec(),
309 )
310 .map_err(SchemaError::EntityTypeParseError)?,
311 );
312 }
313 }
314
315 let attributes = schema_files_types
316 .into_iter()
317 .map(|(name, e)| -> Result<_> {
318 let name: Name =
319 Self::parse_unqualified_name_with_namespace(&name, schema_namespace.to_vec())
320 .map_err(SchemaError::EntityTypeParseError)?;
321
322 let attributes = Self::try_schema_type_into_validator_type(
323 schema_namespace,
324 e.shape.into_inner(),
325 )?;
326
327 Ok((name, attributes))
328 })
329 .collect::<Result<HashMap<_, _>>>()?;
330
331 Ok(EntityTypesDef {
332 attributes,
333 children,
334 })
335 }
336
337 fn jsonval_to_type_helper(v: &JSONValue) -> Result<Type> {
340 match v {
341 JSONValue::Bool(_) => Ok(Type::primitive_boolean()),
342 JSONValue::Long(_) => Ok(Type::primitive_long()),
343 JSONValue::String(_) => Ok(Type::primitive_string()),
344 JSONValue::Record(r) => {
345 let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
346 for (k, v_prime) in r {
347 let t = Self::jsonval_to_type_helper(v_prime);
348 match t {
349 Ok(ty) => required_attrs.insert(k.clone(), ty),
350 Err(e) => return Err(e),
351 };
352 }
353 Ok(Type::EntityOrRecord(EntityRecordKind::Record {
354 attrs: Attributes::with_required_attributes(required_attrs),
355 }))
356 }
357 JSONValue::Set(v) => match v.get(0) {
358 None => Err(SchemaError::ActionEntityAttributeEmptySet),
360 Some(element) => {
361 let element_type = Self::jsonval_to_type_helper(element);
362 match element_type {
363 Ok(t) => Ok(Type::Set {
364 element_type: Some(Box::new(t)),
365 }),
366 Err(_) => element_type,
367 }
368 }
369 },
370 _ => Err(SchemaError::ActionEntityAttributeUnsupportedType),
371 }
372 }
373
374 fn convert_attr_jsonval_map_to_attributes(
376 m: HashMap<SmolStr, JSONValue>,
377 ) -> Result<Attributes> {
378 let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
379
380 for (k, v) in m {
381 let t = Self::jsonval_to_type_helper(&v);
382 match t {
383 Ok(ty) => required_attrs.insert(k.clone(), ty),
384 Err(e) => return Err(e),
385 };
386 }
387 Ok(Attributes::with_required_attributes(required_attrs))
388 }
389
390 fn build_action_ids(
396 schema_file_actions: HashMap<SmolStr, ActionType>,
397 schema_namespace: &[Id],
398 ) -> Result<ActionsDef> {
399 let mut children: HashMap<EntityUID, HashSet<EntityUID>> = HashMap::new();
402 for (name, a) in &schema_file_actions {
403 let parents = match &a.member_of {
404 Some(parents) => parents,
405 None => continue,
406 };
407 for parent in parents {
408 let parent_euid =
409 Self::parse_action_id_with_namespace(parent, schema_namespace.to_vec())?;
410 children
411 .entry(parent_euid)
412 .or_insert_with(HashSet::new)
413 .insert(Self::parse_action_id_with_namespace(
414 &ActionEntityUID::default_type(name.clone()),
415 schema_namespace.to_vec(),
416 )?);
417 }
418 }
419
420 let context_applies_to = schema_file_actions
421 .clone()
422 .into_iter()
423 .map(|(name, a)| -> Result<_> {
424 let action_euid = Self::parse_action_id_with_namespace(
425 &ActionEntityUID::default_type(name),
426 schema_namespace.to_vec(),
427 )?;
428
429 let (principal_types, resource_types, context) = a
430 .applies_to
431 .map(|applies_to| {
432 (
433 applies_to.principal_types,
434 applies_to.resource_types,
435 applies_to.context,
436 )
437 })
438 .unwrap_or_default();
439
440 let action_applies_to = ValidatorApplySpec::new(
444 Self::parse_apply_spec_type_list(principal_types, schema_namespace)?,
445 Self::parse_apply_spec_type_list(resource_types, schema_namespace)?,
446 );
447
448 let action_context = Self::try_schema_type_into_validator_type(
449 schema_namespace,
450 context.into_inner(),
451 )?;
452
453 Ok((action_euid, (action_context, action_applies_to)))
454 })
455 .collect::<Result<HashMap<_, _>>>()?;
456
457 let attributes = schema_file_actions
458 .into_iter()
459 .map(|(name, a)| -> Result<_> {
460 let action_euid = Self::parse_action_id_with_namespace(
461 &ActionEntityUID::default_type(name),
462 schema_namespace.to_vec(),
463 )?;
464
465 let action_attributes =
466 Self::convert_attr_jsonval_map_to_attributes(a.attributes.unwrap_or_default());
467 match action_attributes {
468 Ok(attrs) => Ok((action_euid, attrs)),
470 Err(e) => Err(e),
471 }
472 })
473 .collect::<Result<HashMap<_, _>>>()?;
474
475 Ok(ActionsDef {
476 context_applies_to,
477 children,
478 attributes,
479 })
480 }
481
482 fn check_action_behavior(
488 schema_file: &NamespaceDefinition,
489 action_behavior: ActionBehavior,
490 ) -> Result<()> {
491 if schema_file
492 .entity_types
493 .iter()
494 .any(|(name, _)| name == ACTION_ENTITY_TYPE)
498 {
499 return Err(SchemaError::ActionEntityTypeDeclared);
500 }
501 if action_behavior == ActionBehavior::ProhibitAttributes {
502 let mut actions_with_attributes: Vec<String> = Vec::new();
503 for (name, a) in &schema_file.actions {
504 if a.attributes.is_some() {
505 actions_with_attributes.push(name.to_string());
506 }
507 }
508 if !actions_with_attributes.is_empty() {
509 return Err(SchemaError::ActionEntityAttributes(actions_with_attributes));
510 }
511 }
512
513 Ok(())
514 }
515
516 fn parse_record_attributes(
521 schema_namespace: &[Id],
522 attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
523 ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
524 let attrs_with_type_defs = attrs
525 .into_iter()
526 .map(|(attr, ty)| -> Result<_> {
527 Ok((
528 attr,
529 (
530 Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
531 ty.required,
532 ),
533 ))
534 })
535 .collect::<Result<Vec<_>>>()?;
536 Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
537 attrs_with_type_defs
538 .into_iter()
539 .map(|(s, (attr_ty, is_req))| {
540 attr_ty
541 .resolve_type_defs(typ_defs)
542 .map(|ty| (s, AttributeType::new(ty, is_req)))
543 })
544 .collect::<Result<Vec<_>>>()
545 .map(Attributes::with_attributes)
546 }))
547 }
548
549 fn parse_apply_spec_type_list(
554 types: Option<Vec<SmolStr>>,
555 namespace: &[Id],
556 ) -> Result<HashSet<EntityType>> {
557 types
558 .map(|types| {
559 types
560 .iter()
561 .map(|ty_str| {
565 Ok(EntityType::Concrete(
566 Self::parse_possibly_qualified_name_with_default_namespace(
567 ty_str, namespace,
568 )
569 .map_err(SchemaError::EntityTypeParseError)?,
570 ))
571 })
572 .collect::<Result<HashSet<_>>>()
574 })
575 .unwrap_or_else(|| Ok(HashSet::from([EntityType::Unspecified])))
576 }
577
578 pub(crate) fn parse_possibly_qualified_name_with_default_namespace(
583 name_str: &SmolStr,
584 default_namespace: &[Id],
585 ) -> std::result::Result<Name, Vec<ParseError>> {
586 let name = parse_name(name_str)?;
587
588 let qualified_name =
589 if name.namespace_components().next().is_none() && !default_namespace.is_empty() {
590 Name::new(name.basename().clone(), default_namespace.to_vec())
593 } else {
594 name
596 };
597
598 Ok(qualified_name)
599 }
600
601 fn parse_unqualified_name_with_namespace(
605 type_name: &SmolStr,
606 namespace: Vec<Id>,
607 ) -> std::result::Result<Name, Vec<ParseError>> {
608 Ok(Name::new(type_name.parse()?, namespace))
609 }
610
611 fn parse_action_id_with_namespace(
617 action_id: &ActionEntityUID,
618 namespace: Vec<Id>,
619 ) -> Result<EntityUID> {
620 let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
621 action_ty
622 .parse()
623 .map_err(SchemaError::EntityTypeParseError)?
624 } else {
625 Name::new(
626 ACTION_ENTITY_TYPE.parse().expect(
627 "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
628 ),
629 namespace,
630 )
631 };
632 Ok(EntityUID::from_components(
633 namespaced_action_type,
634 Eid::new(action_id.id.clone()),
635 ))
636 }
637
638 pub(crate) fn try_schema_type_into_validator_type(
644 default_namespace: &[Id],
645 schema_ty: SchemaType,
646 ) -> Result<WithUnresolvedTypeDefs<Type>> {
647 match schema_ty {
648 SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
649 SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
650 SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
651 SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
652 Self::try_schema_type_into_validator_type(default_namespace, *element)?
653 .map(Type::set),
654 ),
655 SchemaType::Type(SchemaTypeVariant::Record {
656 attributes,
657 additional_attributes,
658 }) => {
659 if additional_attributes {
660 Err(SchemaError::UnsupportedSchemaFeature(
661 UnsupportedFeature::OpenRecordsAndEntities,
662 ))
663 } else {
664 Ok(
665 Self::parse_record_attributes(default_namespace, attributes)?
666 .map(Type::record_with_attributes),
667 )
668 }
669 }
670 SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
671 let entity_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
672 &name,
673 default_namespace,
674 )
675 .map_err(SchemaError::EntityTypeParseError)?;
676 Ok(Type::named_entity_reference(entity_type_name).into())
677 }
678 SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
679 let extension_type_name =
680 name.parse().map_err(SchemaError::ExtensionTypeParseError)?;
681 Ok(Type::extension(extension_type_name).into())
682 }
683 SchemaType::TypeDef { type_name } => {
684 let defined_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
685 &type_name,
686 default_namespace,
687 )
688 .map_err(SchemaError::CommonTypeParseError)?;
689 Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
690 typ_defs.get(&defined_type_name).cloned().ok_or(
691 SchemaError::UndeclaredCommonType(HashSet::from([type_name.to_string()])),
692 )
693 }))
694 }
695 }
696 }
697
698 pub fn namespace(&self) -> &Option<Name> {
700 &self.namespace
701 }
702}
703
704#[derive(Debug)]
705pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
706
707impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
708 type Error = SchemaError;
709
710 fn try_into(self) -> Result<ValidatorSchemaFragment> {
711 ValidatorSchemaFragment::from_schema_fragment(self, ActionBehavior::default())
712 }
713}
714
715impl ValidatorSchemaFragment {
716 pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
717 Self(namespaces.into_iter().collect())
718 }
719
720 pub fn from_schema_fragment(
721 fragment: SchemaFragment,
722 action_behavior: ActionBehavior,
723 ) -> Result<Self> {
724 Ok(Self(
725 fragment
726 .0
727 .into_iter()
728 .map(|(fragment_ns, ns_def)| {
729 ValidatorNamespaceDef::from_namespace_definition(
730 Some(fragment_ns),
731 ns_def,
732 action_behavior,
733 )
734 })
735 .collect::<Result<Vec<_>>>()?,
736 ))
737 }
738
739 pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
741 self.0.iter().map(|d| d.namespace())
742 }
743}
744
745#[serde_as]
746#[derive(Clone, Debug, Serialize)]
747pub struct ValidatorSchema {
748 #[serde(rename = "entityTypes")]
750 #[serde_as(as = "Vec<(_, _)>")]
751 entity_types: HashMap<Name, ValidatorEntityType>,
752
753 #[serde(rename = "actionIds")]
755 #[serde_as(as = "Vec<(_, _)>")]
756 action_ids: HashMap<EntityUID, ValidatorActionId>,
757}
758
759impl std::str::FromStr for ValidatorSchema {
760 type Err = SchemaError;
761
762 fn from_str(s: &str) -> Result<Self> {
763 serde_json::from_str::<SchemaFragment>(s)?.try_into()
764 }
765}
766
767impl TryInto<ValidatorSchema> for NamespaceDefinition {
768 type Error = SchemaError;
769
770 fn try_into(self) -> Result<ValidatorSchema> {
771 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([self
772 .try_into(
773 )?])])
774 }
775}
776
777impl TryInto<ValidatorSchema> for SchemaFragment {
778 type Error = SchemaError;
779
780 fn try_into(self) -> Result<ValidatorSchema> {
781 ValidatorSchema::from_schema_fragments([self.try_into()?])
782 }
783}
784
785impl ValidatorSchema {
786 pub fn empty() -> ValidatorSchema {
788 Self {
789 entity_types: HashMap::new(),
790 action_ids: HashMap::new(),
791 }
792 }
793
794 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
797 Self::from_schema_file(
798 SchemaFragment::from_json_value(json)?,
799 ActionBehavior::default(),
800 )
801 }
802
803 pub fn from_file(file: impl std::io::Read) -> Result<Self> {
805 Self::from_schema_file(SchemaFragment::from_file(file)?, ActionBehavior::default())
806 }
807
808 pub fn from_schema_file(
809 schema_file: SchemaFragment,
810 action_behavior: ActionBehavior,
811 ) -> Result<ValidatorSchema> {
812 Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
813 schema_file,
814 action_behavior,
815 )?])
816 }
817
818 pub fn from_schema_fragments(
820 fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
821 ) -> Result<ValidatorSchema> {
822 let mut type_defs = HashMap::new();
823 let mut entity_attributes = HashMap::new();
824 let mut entity_children = HashMap::new();
825 let mut action_context_applies_to = HashMap::new();
826 let mut action_children = HashMap::new();
827 let mut action_attributes = HashMap::new();
828
829 for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
830 for (name, ty) in ns_def.type_defs.type_defs {
831 match type_defs.entry(name) {
832 Entry::Vacant(v) => v.insert(ty),
833 Entry::Occupied(o) => {
834 return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
835 }
836 };
837 }
838
839 for (name, attrs) in ns_def.entity_types.attributes {
846 match entity_attributes.entry(name) {
847 Entry::Vacant(v) => v.insert(attrs),
848 Entry::Occupied(o) => {
849 return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
850 }
851 };
852 }
853 for (id, context_applies_to) in ns_def.actions.context_applies_to {
854 match action_context_applies_to.entry(id) {
855 Entry::Vacant(v) => v.insert(context_applies_to),
856 Entry::Occupied(o) => {
857 return Err(SchemaError::DuplicateAction(o.key().to_string()))
858 }
859 };
860 }
861 for (id, attrs) in ns_def.actions.attributes {
862 match action_attributes.entry(id) {
863 Entry::Vacant(v) => v.insert(attrs),
864 Entry::Occupied(o) => {
865 return Err(SchemaError::DuplicateAction(o.key().to_string()))
866 }
867 };
868 }
869
870 for (name, children) in ns_def.entity_types.children {
874 let current_children: &mut HashSet<_> = entity_children.entry(name).or_default();
875 for child in children {
876 current_children.insert(child);
877 }
878 }
879
880 for (id, children) in ns_def.actions.children {
881 let current_children: &mut HashSet<_> = action_children.entry(id).or_default();
882 for child in children {
883 current_children.insert(child);
884 }
885 }
886 }
887
888 let mut entity_types = entity_attributes
889 .into_iter()
890 .map(|(name, attributes)| -> Result<_> {
891 let descendants = entity_children.remove(&name).unwrap_or_default();
901 Ok((
902 name.clone(),
903 ValidatorEntityType {
904 name,
905 descendants,
906 attributes: Self::record_attributes_or_error(
907 attributes.resolve_type_defs(&type_defs)?,
908 )?,
909 },
910 ))
911 })
912 .collect::<Result<HashMap<_, _>>>()?;
913
914 let mut action_ids = action_context_applies_to
915 .into_iter()
916 .map(|(name, (context, applies_to))| -> Result<_> {
917 let descendants = action_children.remove(&name).unwrap_or_default();
918
919 let attributes = match action_attributes.get(&name) {
920 Some(t) => t.clone(),
921 None => Attributes::with_attributes([]),
922 };
923
924 Ok((
925 name.clone(),
926 ValidatorActionId {
927 name,
928 applies_to,
929 descendants,
930 context: Self::record_attributes_or_error(
931 context.resolve_type_defs(&type_defs)?,
932 )?,
933 attributes,
934 },
935 ))
936 })
937 .collect::<Result<HashMap<_, _>>>()?;
938
939 compute_tc(&mut entity_types, false)?;
942 compute_tc(&mut action_ids, true)?;
945
946 Self::check_for_undeclared(
953 &entity_types,
954 entity_children.into_keys(),
955 &action_ids,
956 action_children.into_keys(),
957 )?;
958
959 Ok(ValidatorSchema {
960 entity_types,
961 action_ids,
962 })
963 }
964
965 fn check_for_undeclared(
970 entity_types: &HashMap<Name, ValidatorEntityType>,
971 undeclared_parent_entities: impl IntoIterator<Item = Name>,
972 action_ids: &HashMap<EntityUID, ValidatorActionId>,
973 undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
974 ) -> Result<()> {
975 let mut undeclared_e = undeclared_parent_entities
980 .into_iter()
981 .map(|n| n.to_string())
982 .collect::<HashSet<_>>();
983 for entity_type in entity_types.values() {
989 for (_, attr_typ) in entity_type.attributes() {
990 Self::check_undeclared_in_type(
991 &attr_typ.attr_type,
992 entity_types,
993 &mut undeclared_e,
994 );
995 }
996 }
997
998 let undeclared_a = undeclared_parent_actions
1000 .into_iter()
1001 .map(|n| n.to_string())
1002 .collect::<HashSet<_>>();
1003 for action in action_ids.values() {
1007 for (_, attr_typ) in action.context.iter() {
1008 Self::check_undeclared_in_type(
1009 &attr_typ.attr_type,
1010 entity_types,
1011 &mut undeclared_e,
1012 );
1013 }
1014
1015 for p_entity in action.applies_to.applicable_principal_types() {
1016 match p_entity {
1017 EntityType::Concrete(p_entity) => {
1018 if !entity_types.contains_key(p_entity) {
1019 undeclared_e.insert(p_entity.to_string());
1020 }
1021 }
1022 EntityType::Unspecified => (),
1023 }
1024 }
1025
1026 for r_entity in action.applies_to.applicable_resource_types() {
1027 match r_entity {
1028 EntityType::Concrete(r_entity) => {
1029 if !entity_types.contains_key(r_entity) {
1030 undeclared_e.insert(r_entity.to_string());
1031 }
1032 }
1033 EntityType::Unspecified => (),
1034 }
1035 }
1036 }
1037 if !undeclared_e.is_empty() {
1038 return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
1039 }
1040 if !undeclared_a.is_empty() {
1041 return Err(SchemaError::UndeclaredActions(undeclared_a));
1042 }
1043
1044 Ok(())
1045 }
1046
1047 fn record_attributes_or_error(ty: Type) -> Result<Attributes> {
1048 match ty {
1049 Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => Ok(attrs),
1050 _ => Err(SchemaError::ContextOrShapeNotRecord),
1051 }
1052 }
1053
1054 fn check_undeclared_in_type(
1058 ty: &Type,
1059 entity_types: &HashMap<Name, ValidatorEntityType>,
1060 undeclared_types: &mut HashSet<String>,
1061 ) {
1062 match ty {
1063 Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
1064 for name in lub.iter() {
1065 if !entity_types.contains_key(name) {
1066 undeclared_types.insert(name.to_string());
1067 }
1068 }
1069 }
1070
1071 Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => {
1072 for (_, attr_ty) in attrs.iter() {
1073 Self::check_undeclared_in_type(
1074 &attr_ty.attr_type,
1075 entity_types,
1076 undeclared_types,
1077 );
1078 }
1079 }
1080
1081 Type::Set {
1082 element_type: Some(element_type),
1083 } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
1084
1085 _ => (),
1086 }
1087 }
1088
1089 pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
1091 self.action_ids.get(action_id)
1092 }
1093
1094 pub fn get_entity_type(&self, entity_type_id: &Name) -> Option<&ValidatorEntityType> {
1096 self.entity_types.get(entity_type_id)
1097 }
1098
1099 pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
1101 self.action_ids.contains_key(action_id)
1102 }
1103
1104 pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
1106 self.entity_types.contains_key(entity_type)
1107 }
1108
1109 pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
1111 self.action_ids.keys()
1112 }
1113
1114 pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
1116 self.entity_types.keys()
1117 }
1118
1119 pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
1121 self.entity_types.iter()
1122 }
1123
1124 pub(crate) fn get_entity_eq<'a, H, K>(&self, var: H, euid: EntityUID) -> Option<K>
1127 where
1128 H: 'a + HeadVar<K>,
1129 K: 'a,
1130 {
1131 var.get_euid_component(euid)
1132 }
1133
1134 pub(crate) fn get_entities_in<'a, H, K>(
1137 &'a self,
1138 var: H,
1139 euid: EntityUID,
1140 ) -> impl Iterator<Item = K> + 'a
1141 where
1142 H: 'a + HeadVar<K>,
1143 K: 'a + Clone,
1144 {
1145 var.get_descendants_if_present(self, euid.clone())
1146 .into_iter()
1147 .flatten()
1148 .map(Clone::clone)
1149 .chain(var.get_euid_component_if_present(self, euid).into_iter())
1150 }
1151
1152 pub(crate) fn get_entities_in_set<'a, H, K>(
1155 &'a self,
1156 var: H,
1157 euids: impl IntoIterator<Item = EntityUID> + 'a,
1158 ) -> impl Iterator<Item = K> + 'a
1159 where
1160 H: 'a + HeadVar<K>,
1161 K: 'a + Clone,
1162 {
1163 euids
1164 .into_iter()
1165 .flat_map(move |e| self.get_entities_in(var, e))
1166 }
1167
1168 pub fn get_context_schema(
1173 &self,
1174 action: &EntityUID,
1175 ) -> Option<impl cedar_policy_core::entities::ContextSchema> {
1176 self.get_action_id(action).map(|action_id| {
1177 crate::types::Type::record_with_attributes(
1178 action_id
1179 .context
1180 .iter()
1181 .map(|(k, v)| (k.clone(), v.clone())),
1182 )
1183 })
1184 }
1185}
1186
1187impl cedar_policy_core::entities::Schema for ValidatorSchema {
1188 fn attr_type(
1189 &self,
1190 entity_type: &cedar_policy_core::ast::EntityType,
1191 attr: &str,
1192 ) -> Option<cedar_policy_core::entities::SchemaType> {
1193 match entity_type {
1194 cedar_policy_core::ast::EntityType::Unspecified => None, cedar_policy_core::ast::EntityType::Concrete(name) => {
1196 let entity_type: &ValidatorEntityType = self.get_entity_type(name)?;
1197 let validator_type: &crate::types::Type = &entity_type.attr(attr)?.attr_type;
1198 let core_schema_type: cedar_policy_core::entities::SchemaType = validator_type
1199 .clone()
1200 .try_into()
1201 .expect("failed to convert validator type into Core SchemaType");
1202 debug_assert!(validator_type.is_consistent_with(&core_schema_type));
1203 Some(core_schema_type)
1204 }
1205 }
1206 }
1207
1208 fn required_attrs<'s>(
1209 &'s self,
1210 entity_type: &cedar_policy_core::ast::EntityType,
1211 ) -> Box<dyn Iterator<Item = SmolStr> + 's> {
1212 match entity_type {
1213 cedar_policy_core::ast::EntityType::Unspecified => Box::new(std::iter::empty()), cedar_policy_core::ast::EntityType::Concrete(name) => {
1215 match self.get_entity_type(name) {
1216 None => Box::new(std::iter::empty()),
1217 Some(entity_type) => Box::new(
1218 entity_type
1219 .attributes
1220 .iter()
1221 .filter(|(_, ty)| ty.is_required)
1222 .map(|(attr, _)| attr.clone()),
1223 ),
1224 }
1225 }
1226 }
1227 }
1228}
1229
1230impl cedar_policy_core::entities::ContextSchema for crate::types::Type {
1232 fn context_type(&self) -> cedar_policy_core::entities::SchemaType {
1233 self.clone()
1234 .try_into()
1235 .expect("failed to convert validator type into Core SchemaType")
1236 }
1237}
1238
1239#[derive(Clone, Debug, Serialize)]
1243pub struct ValidatorEntityType {
1244 pub(crate) name: Name,
1246
1247 pub descendants: HashSet<Name>,
1252
1253 pub(crate) attributes: Attributes,
1256}
1257
1258impl ValidatorEntityType {
1259 pub fn attr(&self, attr: &str) -> Option<&AttributeType> {
1261 self.attributes.get_attr(attr)
1262 }
1263
1264 pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1266 self.attributes.iter()
1267 }
1268}
1269
1270impl TCNode<Name> for ValidatorEntityType {
1271 fn get_key(&self) -> Name {
1272 self.name.clone()
1273 }
1274
1275 fn add_edge_to(&mut self, k: Name) {
1276 self.descendants.insert(k);
1277 }
1278
1279 fn out_edges(&self) -> Box<dyn Iterator<Item = &Name> + '_> {
1280 Box::new(self.descendants.iter())
1281 }
1282
1283 fn has_edge_to(&self, e: &Name) -> bool {
1284 self.descendants.contains(e)
1285 }
1286}
1287
1288#[derive(Clone, Debug, Serialize)]
1292pub struct ValidatorActionId {
1293 pub(crate) name: EntityUID,
1295
1296 #[serde(rename = "appliesTo")]
1298 pub(crate) applies_to: ValidatorApplySpec,
1299
1300 pub(crate) descendants: HashSet<EntityUID>,
1305
1306 pub(crate) context: Attributes,
1309
1310 pub(crate) attributes: Attributes,
1312}
1313
1314impl ValidatorActionId {
1315 pub fn context(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1317 self.context.iter()
1318 }
1319}
1320
1321impl TCNode<EntityUID> for ValidatorActionId {
1322 fn get_key(&self) -> EntityUID {
1323 self.name.clone()
1324 }
1325
1326 fn add_edge_to(&mut self, k: EntityUID) {
1327 self.descendants.insert(k);
1328 }
1329
1330 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
1331 Box::new(self.descendants.iter())
1332 }
1333
1334 fn has_edge_to(&self, e: &EntityUID) -> bool {
1335 self.descendants.contains(e)
1336 }
1337}
1338
1339#[derive(Clone, Debug, Serialize)]
1341pub(crate) struct ValidatorApplySpec {
1342 #[serde(rename = "principalApplySpec")]
1349 principal_apply_spec: HashSet<EntityType>,
1350
1351 #[serde(rename = "resourceApplySpec")]
1354 resource_apply_spec: HashSet<EntityType>,
1355}
1356
1357impl ValidatorApplySpec {
1358 pub(crate) fn new(
1361 principal_apply_spec: HashSet<EntityType>,
1362 resource_apply_spec: HashSet<EntityType>,
1363 ) -> Self {
1364 Self {
1365 principal_apply_spec,
1366 resource_apply_spec,
1367 }
1368 }
1369
1370 pub(crate) fn applicable_principal_types(&self) -> impl Iterator<Item = &EntityType> {
1372 self.principal_apply_spec.iter()
1373 }
1374
1375 pub(crate) fn applicable_resource_types(&self) -> impl Iterator<Item = &EntityType> {
1377 self.resource_apply_spec.iter()
1378 }
1379}
1380
1381pub(crate) trait HeadVar<K>: Copy {
1384 fn get_known_vars<'a>(
1388 &self,
1389 schema: &'a ValidatorSchema,
1390 ) -> Box<dyn Iterator<Item = &'a K> + 'a>;
1391
1392 fn get_euid_component(&self, euid: EntityUID) -> Option<K>;
1395
1396 fn get_euid_component_if_present(&self, schema: &ValidatorSchema, euid: EntityUID)
1399 -> Option<K>;
1400
1401 fn get_descendants_if_present<'a>(
1404 &self,
1405 schema: &'a ValidatorSchema,
1406 euid: EntityUID,
1407 ) -> Option<Box<dyn Iterator<Item = &'a K> + 'a>>;
1408}
1409
1410#[derive(Debug, Clone, Copy)]
1414pub(crate) enum PrincipalOrResourceHeadVar {
1415 PrincipalOrResource,
1416}
1417
1418impl HeadVar<Name> for PrincipalOrResourceHeadVar {
1419 fn get_known_vars<'a>(
1420 &self,
1421 schema: &'a ValidatorSchema,
1422 ) -> Box<dyn Iterator<Item = &'a Name> + 'a> {
1423 Box::new(schema.known_entity_types())
1424 }
1425
1426 fn get_euid_component(&self, euid: EntityUID) -> Option<Name> {
1427 let (ty, _) = euid.components();
1428 match ty {
1429 EntityType::Unspecified => None,
1430 EntityType::Concrete(name) => Some(name),
1431 }
1432 }
1433
1434 fn get_euid_component_if_present(
1435 &self,
1436 schema: &ValidatorSchema,
1437 euid: EntityUID,
1438 ) -> Option<Name> {
1439 let euid_component = self.get_euid_component(euid)?;
1440 if schema.is_known_entity_type(&euid_component) {
1441 Some(euid_component)
1442 } else {
1443 None
1444 }
1445 }
1446
1447 fn get_descendants_if_present<'a>(
1448 &self,
1449 schema: &'a ValidatorSchema,
1450 euid: EntityUID,
1451 ) -> Option<Box<dyn Iterator<Item = &'a Name> + 'a>> {
1452 let euid_component = self.get_euid_component(euid)?;
1453 match schema.get_entity_type(&euid_component) {
1454 Some(entity_type) => Some(Box::new(entity_type.descendants.iter())),
1455 None => None,
1456 }
1457 }
1458}
1459
1460#[derive(Debug, Clone, Copy)]
1463pub(crate) enum ActionHeadVar {
1464 Action,
1465}
1466
1467impl HeadVar<EntityUID> for ActionHeadVar {
1468 fn get_known_vars<'a>(
1469 &self,
1470 schema: &'a ValidatorSchema,
1471 ) -> Box<dyn Iterator<Item = &'a EntityUID> + 'a> {
1472 Box::new(schema.known_action_ids())
1473 }
1474
1475 fn get_euid_component(&self, euid: EntityUID) -> Option<EntityUID> {
1476 Some(euid)
1477 }
1478
1479 fn get_euid_component_if_present(
1480 &self,
1481 schema: &ValidatorSchema,
1482 euid: EntityUID,
1483 ) -> Option<EntityUID> {
1484 let euid_component = self.get_euid_component(euid)?;
1485 if schema.is_known_action_id(&euid_component) {
1486 Some(euid_component)
1487 } else {
1488 None
1489 }
1490 }
1491
1492 fn get_descendants_if_present<'a>(
1493 &self,
1494 schema: &'a ValidatorSchema,
1495 euid: EntityUID,
1496 ) -> Option<Box<dyn Iterator<Item = &'a EntityUID> + 'a>> {
1497 let euid_component = self.get_euid_component(euid)?;
1498 match schema.get_action_id(&euid_component) {
1499 Some(action_id) => Some(Box::new(action_id.descendants.iter())),
1500 None => None,
1501 }
1502 }
1503}
1504
1505#[derive(Debug, Clone, Deserialize)]
1508#[serde(transparent)]
1509pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
1510
1511impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
1512 type Error = SchemaError;
1513
1514 fn try_into(self) -> Result<ValidatorSchema> {
1515 ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
1516 ValidatorNamespaceDef::from_namespace_definition(
1517 None,
1518 self.0,
1519 crate::ActionBehavior::PermitAttributes,
1520 )?,
1521 ])])
1522 }
1523}
1524
1525#[cfg(test)]
1526mod test {
1527 use std::{collections::BTreeMap, str::FromStr};
1528
1529 use crate::types::Type;
1530
1531 use serde_json::json;
1532
1533 use super::*;
1534
1535 #[test]
1537 fn test_from_schema_file() {
1538 let src = json!(
1539 {
1540 "entityTypes": {
1541 "User": {
1542 "memberOfTypes": [ "Group" ]
1543 },
1544 "Group": {
1545 "memberOfTypes": []
1546 },
1547 "Photo": {
1548 "memberOfTypes": [ "Album" ]
1549 },
1550 "Album": {
1551 "memberOfTypes": []
1552 }
1553 },
1554 "actions": {
1555 "view_photo": {
1556 "appliesTo": {
1557 "principalTypes": ["User", "Group"],
1558 "resourceTypes": ["Photo"]
1559 }
1560 }
1561 }
1562 });
1563 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1564 let schema: Result<ValidatorSchema> = schema_file.try_into();
1565 assert!(schema.is_ok());
1566 }
1567
1568 #[test]
1570 fn test_from_schema_file_duplicate_entity() {
1571 let src = r#"
1574 {"": {
1575 "entityTypes": {
1576 "User": {
1577 "memberOfTypes": [ "Group" ]
1578 },
1579 "Group": {
1580 "memberOfTypes": []
1581 },
1582 "Photo": {
1583 "memberOfTypes": [ "Album" ]
1584 },
1585 "Photo": {
1586 "memberOfTypes": []
1587 }
1588 },
1589 "actions": {
1590 "view_photo": {
1591 "memberOf": [],
1592 "appliesTo": {
1593 "principalTypes": ["User", "Group"],
1594 "resourceTypes": ["Photo"]
1595 }
1596 }
1597 }
1598 }}"#;
1599
1600 match ValidatorSchema::from_str(src) {
1601 Err(SchemaError::ParseFileFormat(_)) => (),
1602 _ => panic!("Expected serde error due to duplicate entity type."),
1603 }
1604 }
1605
1606 #[test]
1608 fn test_from_schema_file_duplicate_action() {
1609 let src = r#"
1612 {"": {
1613 "entityTypes": {
1614 "User": {
1615 "memberOfTypes": [ "Group" ]
1616 },
1617 "Group": {
1618 "memberOfTypes": []
1619 },
1620 "Photo": {
1621 "memberOfTypes": []
1622 }
1623 },
1624 "actions": {
1625 "view_photo": {
1626 "memberOf": [],
1627 "appliesTo": {
1628 "principalTypes": ["User", "Group"],
1629 "resourceTypes": ["Photo"]
1630 }
1631 },
1632 "view_photo": { }
1633 }
1634 }"#;
1635 match ValidatorSchema::from_str(src) {
1636 Err(SchemaError::ParseFileFormat(_)) => (),
1637 _ => panic!("Expected serde error due to duplicate action type."),
1638 }
1639 }
1640
1641 #[test]
1643 fn test_from_schema_file_undefined_entities() {
1644 let src = json!(
1645 {
1646 "entityTypes": {
1647 "User": {
1648 "memberOfTypes": [ "Grop" ]
1649 },
1650 "Group": {
1651 "memberOfTypes": []
1652 },
1653 "Photo": {
1654 "memberOfTypes": []
1655 }
1656 },
1657 "actions": {
1658 "view_photo": {
1659 "appliesTo": {
1660 "principalTypes": ["Usr", "Group"],
1661 "resourceTypes": ["Phoot"]
1662 }
1663 }
1664 }
1665 });
1666 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1667 let schema: Result<ValidatorSchema> = schema_file.try_into();
1668 match schema {
1669 Ok(_) => panic!("from_schema_file should have failed"),
1670 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1671 assert_eq!(v.len(), 3)
1672 }
1673 _ => panic!("Unexpected error from from_schema_file"),
1674 }
1675 }
1676
1677 #[test]
1678 fn undefined_entity_namespace_member_of() {
1679 let src = json!(
1680 {"Foo": {
1681 "entityTypes": {
1682 "User": {
1683 "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1684 },
1685 "Group": { }
1686 },
1687 "actions": {}
1688 }});
1689 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1690 let schema: Result<ValidatorSchema> = schema_file.try_into();
1691 match schema {
1692 Ok(_) => panic!("try_into should have failed"),
1693 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1694 assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1695 }
1696 _ => panic!("Unexpected error from try_into"),
1697 }
1698 }
1699
1700 #[test]
1701 fn undefined_entity_namespace_applies_to() {
1702 let src = json!(
1703 {"Foo": {
1704 "entityTypes": { "User": { }, "Photo": { } },
1705 "actions": {
1706 "view_photo": {
1707 "appliesTo": {
1708 "principalTypes": ["Foo::User", "Bar::User"],
1709 "resourceTypes": ["Photo", "Bar::Photo"],
1710 }
1711 }
1712 }
1713 }});
1714 let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1715 let schema: Result<ValidatorSchema> = schema_file.try_into();
1716 match schema {
1717 Ok(_) => panic!("try_into should have failed"),
1718 Err(SchemaError::UndeclaredEntityTypes(v)) => {
1719 assert_eq!(
1720 v,
1721 HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1722 )
1723 }
1724 _ => panic!("Unexpected error from try_into"),
1725 }
1726 }
1727
1728 #[test]
1730 fn test_from_schema_file_undefined_action() {
1731 let src = json!(
1732 {
1733 "entityTypes": {
1734 "User": {
1735 "memberOfTypes": [ "Group" ]
1736 },
1737 "Group": {
1738 "memberOfTypes": []
1739 },
1740 "Photo": {
1741 "memberOfTypes": []
1742 }
1743 },
1744 "actions": {
1745 "view_photo": {
1746 "memberOf": [ {"id": "photo_action"} ],
1747 "appliesTo": {
1748 "principalTypes": ["User", "Group"],
1749 "resourceTypes": ["Photo"]
1750 }
1751 }
1752 }
1753 });
1754 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1755 let schema: Result<ValidatorSchema> = schema_file.try_into();
1756 match schema {
1757 Ok(_) => panic!("from_schema_file should have failed"),
1758 Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1759 _ => panic!("Unexpected error from from_schema_file"),
1760 }
1761 }
1762
1763 #[test]
1766 fn test_from_schema_file_action_cycle1() {
1767 let src = json!(
1768 {
1769 "entityTypes": {},
1770 "actions": {
1771 "view_photo": {
1772 "memberOf": [ {"id": "view_photo"} ]
1773 }
1774 }
1775 });
1776 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1777 let schema: Result<ValidatorSchema> = schema_file.try_into();
1778 match schema {
1779 Ok(_) => panic!("from_schema_file should have failed"),
1780 Err(SchemaError::CycleInActionHierarchy) => (), e => panic!("Unexpected error from from_schema_file: {:?}", e),
1782 }
1783 }
1784
1785 #[test]
1788 fn test_from_schema_file_action_cycle2() {
1789 let src = json!(
1790 {
1791 "entityTypes": {},
1792 "actions": {
1793 "view_photo": {
1794 "memberOf": [ {"id": "edit_photo"} ]
1795 },
1796 "edit_photo": {
1797 "memberOf": [ {"id": "delete_photo"} ]
1798 },
1799 "delete_photo": {
1800 "memberOf": [ {"id": "view_photo"} ]
1801 },
1802 "other_action": {
1803 "memberOf": [ {"id": "edit_photo"} ]
1804 }
1805 }
1806 });
1807 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1808 let schema: Result<ValidatorSchema> = schema_file.try_into();
1809 match schema {
1810 Ok(x) => {
1811 println!("{:?}", x);
1812 panic!("from_schema_file should have failed");
1813 }
1814 Err(SchemaError::CycleInActionHierarchy) => (), e => panic!("Unexpected error from from_schema_file: {:?}", e),
1816 }
1817 }
1818
1819 #[test]
1820 fn namespaced_schema() {
1821 let src = r#"
1822 { "N::S": {
1823 "entityTypes": {
1824 "User": {},
1825 "Photo": {}
1826 },
1827 "actions": {
1828 "view_photo": {
1829 "appliesTo": {
1830 "principalTypes": ["User"],
1831 "resourceTypes": ["Photo"]
1832 }
1833 }
1834 }
1835 } }
1836 "#;
1837 let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1838 let schema: ValidatorSchema = schema_file
1839 .try_into()
1840 .expect("Namespaced schema failed to convert.");
1841 dbg!(&schema);
1842 let user_entity_type = &"N::S::User"
1843 .parse()
1844 .expect("Namespaced entity type should have parsed");
1845 let photo_entity_type = &"N::S::Photo"
1846 .parse()
1847 .expect("Namespaced entity type should have parsed");
1848 assert!(
1849 schema.entity_types.contains_key(user_entity_type),
1850 "Expected and entity type User."
1851 );
1852 assert!(
1853 schema.entity_types.contains_key(photo_entity_type),
1854 "Expected an entity type Photo."
1855 );
1856 assert_eq!(
1857 schema.entity_types.len(),
1858 2,
1859 "Expected exactly 2 entity types."
1860 );
1861 assert!(
1862 schema.action_ids.contains_key(
1863 &"N::S::Action::\"view_photo\""
1864 .parse()
1865 .expect("Namespaced action should have parsed")
1866 ),
1867 "Expected an action \"view_photo\"."
1868 );
1869 assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1870
1871 let apply_spec = &schema
1872 .action_ids
1873 .values()
1874 .next()
1875 .expect("Expected Action")
1876 .applies_to;
1877 assert_eq!(
1878 apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1879 vec![&EntityType::Concrete(user_entity_type.clone())]
1880 );
1881 assert_eq!(
1882 apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1883 vec![&EntityType::Concrete(photo_entity_type.clone())]
1884 );
1885 }
1886
1887 #[test]
1888 fn cant_use_namespace_in_entity_type() {
1889 let src = r#"
1890 {
1891 "entityTypes": { "NS::User": {} },
1892 "actions": {}
1893 }
1894 "#;
1895 let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
1896 assert!(
1897 matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::EntityTypeParseError(_))),
1898 "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
1899 }
1900
1901 #[test]
1902 fn entity_attribute_entity_type_with_namespace() {
1903 let schema_json: SchemaFragment = serde_json::from_str(
1904 r#"
1905 {"A::B": {
1906 "entityTypes": {
1907 "Foo": {
1908 "shape": {
1909 "type": "Record",
1910 "attributes": {
1911 "name": { "type": "Entity", "name": "C::D::Foo" }
1912 }
1913 }
1914 }
1915 },
1916 "actions": {}
1917 }}
1918 "#,
1919 )
1920 .expect("Expected valid schema");
1921
1922 let schema: Result<ValidatorSchema> = schema_json.try_into();
1923 match schema {
1924 Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1925 assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1926 }
1927 _ => panic!("Schema construction should have failed due to undeclared entity type."),
1928 }
1929 }
1930
1931 #[test]
1932 fn entity_attribute_entity_type_with_declared_namespace() {
1933 let schema_json: SchemaFragment = serde_json::from_str(
1934 r#"
1935 {"A::B": {
1936 "entityTypes": {
1937 "Foo": {
1938 "shape": {
1939 "type": "Record",
1940 "attributes": {
1941 "name": { "type": "Entity", "name": "A::B::Foo" }
1942 }
1943 }
1944 }
1945 },
1946 "actions": {}
1947 }}
1948 "#,
1949 )
1950 .expect("Expected valid schema");
1951
1952 let schema: ValidatorSchema = schema_json
1953 .try_into()
1954 .expect("Expected schema to construct without error.");
1955
1956 let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1957 let foo_type = schema
1958 .entity_types
1959 .get(&foo_name)
1960 .expect("Expected to find entity");
1961 let name_type = foo_type
1962 .attr("name")
1963 .expect("Expected attribute name")
1964 .attr_type
1965 .clone();
1966 let expected_name_type = Type::named_entity_reference(foo_name);
1967 assert_eq!(name_type, expected_name_type);
1968 }
1969
1970 #[test]
1971 fn cannot_declare_action_type_when_prohibited() {
1972 let schema_json: NamespaceDefinition = serde_json::from_str(
1973 r#"
1974 {
1975 "entityTypes": { "Action": {} },
1976 "actions": {}
1977 }
1978 "#,
1979 )
1980 .expect("Expected valid schema");
1981
1982 let schema: Result<ValidatorSchema> = schema_json.try_into();
1983 assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1984 }
1985
1986 #[test]
1987 fn can_declare_other_type_when_action_type_prohibited() {
1988 let schema_json: NamespaceDefinition = serde_json::from_str(
1989 r#"
1990 {
1991 "entityTypes": { "Foo": { } },
1992 "actions": {}
1993 }
1994 "#,
1995 )
1996 .expect("Expected valid schema");
1997
1998 TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1999 }
2000
2001 #[test]
2002 fn cannot_declare_action_in_group_when_prohibited() {
2003 let schema_json: SchemaFragment = serde_json::from_str(
2004 r#"
2005 {"": {
2006 "entityTypes": {},
2007 "actions": {
2008 "universe": { },
2009 "view_photo": {
2010 "attributes": {"id": "universe"}
2011 },
2012 "edit_photo": {
2013 "attributes": {"id": "universe"}
2014 },
2015 "delete_photo": {
2016 "attributes": {"id": "universe"}
2017 }
2018 }
2019 }}
2020 "#,
2021 )
2022 .expect("Expected valid schema");
2023
2024 let schema = ValidatorSchemaFragment::from_schema_fragment(
2025 schema_json,
2026 ActionBehavior::ProhibitAttributes,
2027 );
2028 match schema {
2029 Err(SchemaError::ActionEntityAttributes(actions)) => {
2030 assert_eq!(
2031 actions.into_iter().collect::<HashSet<_>>(),
2032 HashSet::from([
2033 "view_photo".to_string(),
2034 "edit_photo".to_string(),
2035 "delete_photo".to_string(),
2036 ])
2037 )
2038 }
2039 _ => panic!("Did not see expected error."),
2040 }
2041 }
2042
2043 #[test]
2044 fn test_entity_type_no_namespace() {
2045 let src = json!({"type": "Entity", "name": "Foo"});
2046 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2047 assert_eq!(
2048 schema_ty,
2049 SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
2050 );
2051 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2052 &parse_namespace("NS").expect("Expected namespace."),
2053 schema_ty,
2054 )
2055 .expect("Error converting schema type to type.")
2056 .resolve_type_defs(&HashMap::new())
2057 .unwrap();
2058 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2059 }
2060
2061 #[test]
2062 fn test_entity_type_namespace() {
2063 let src = json!({"type": "Entity", "name": "NS::Foo"});
2064 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2065 assert_eq!(
2066 schema_ty,
2067 SchemaType::Type(SchemaTypeVariant::Entity {
2068 name: "NS::Foo".into()
2069 })
2070 );
2071 let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2072 &parse_namespace("NS").expect("Expected namespace."),
2073 schema_ty,
2074 )
2075 .expect("Error converting schema type to type.")
2076 .resolve_type_defs(&HashMap::new())
2077 .unwrap();
2078 assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2079 }
2080
2081 #[test]
2082 fn test_entity_type_namespace_parse_error() {
2083 let src = json!({"type": "Entity", "name": "::Foo"});
2084 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2085 assert_eq!(
2086 schema_ty,
2087 SchemaType::Type(SchemaTypeVariant::Entity {
2088 name: "::Foo".into()
2089 })
2090 );
2091 match ValidatorNamespaceDef::try_schema_type_into_validator_type(
2092 &parse_namespace("NS").expect("Expected namespace."),
2093 schema_ty,
2094 ) {
2095 Err(SchemaError::EntityTypeParseError(_)) => (),
2096 _ => panic!("Did not see expected EntityTypeParseError."),
2097 }
2098 }
2099
2100 #[test]
2101 fn schema_type_record_is_validator_type_record() {
2102 let src = json!({"type": "Record", "attributes": {}});
2103 let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2104 assert_eq!(
2105 schema_ty,
2106 SchemaType::Type(SchemaTypeVariant::Record {
2107 attributes: BTreeMap::new(),
2108 additional_attributes: false,
2109 }),
2110 );
2111 let ty: Type =
2112 ValidatorNamespaceDef::try_schema_type_into_validator_type(&Vec::new(), schema_ty)
2113 .expect("Error converting schema type to type.")
2114 .resolve_type_defs(&HashMap::new())
2115 .unwrap();
2116 assert_eq!(ty, Type::record_with_attributes(None));
2117 }
2118
2119 #[test]
2120 fn get_namespaces() {
2121 let fragment: SchemaFragment = serde_json::from_value(json!({
2122 "Foo::Bar::Baz": {
2123 "entityTypes": {},
2124 "actions": {}
2125 },
2126 "Foo": {
2127 "entityTypes": {},
2128 "actions": {}
2129 },
2130 "Bar": {
2131 "entityTypes": {},
2132 "actions": {}
2133 },
2134 }))
2135 .unwrap();
2136
2137 let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
2138 assert_eq!(
2139 schema_fragment
2140 .0
2141 .iter()
2142 .map(|f| f.namespace())
2143 .collect::<HashSet<_>>(),
2144 HashSet::from([
2145 &Some("Foo::Bar::Baz".parse().unwrap()),
2146 &Some("Foo".parse().unwrap()),
2147 &Some("Bar".parse().unwrap())
2148 ])
2149 );
2150 }
2151
2152 #[test]
2153 fn schema_no_fragments() {
2154 let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
2155 assert!(schema.entity_types.is_empty());
2156 assert!(schema.action_ids.is_empty());
2157 }
2158
2159 #[test]
2160 fn same_action_different_namespace() {
2161 let fragment: SchemaFragment = serde_json::from_value(json!({
2162 "Foo::Bar": {
2163 "entityTypes": {},
2164 "actions": {
2165 "Baz": {}
2166 }
2167 },
2168 "Bar::Foo": {
2169 "entityTypes": {},
2170 "actions": {
2171 "Baz": { }
2172 }
2173 },
2174 "Biz": {
2175 "entityTypes": {},
2176 "actions": {
2177 "Baz": { }
2178 }
2179 }
2180 }))
2181 .unwrap();
2182
2183 let schema: ValidatorSchema = fragment.try_into().unwrap();
2184 assert!(schema
2185 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2186 .is_some());
2187 assert!(schema
2188 .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
2189 .is_some());
2190 assert!(schema
2191 .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2192 .is_some());
2193 }
2194
2195 #[test]
2196 fn same_type_different_namespace() {
2197 let fragment: SchemaFragment = serde_json::from_value(json!({
2198 "Foo::Bar": {
2199 "entityTypes": {"Baz" : {}},
2200 "actions": { }
2201 },
2202 "Bar::Foo": {
2203 "entityTypes": {"Baz" : {}},
2204 "actions": { }
2205 },
2206 "Biz": {
2207 "entityTypes": {"Baz" : {}},
2208 "actions": { }
2209 }
2210 }))
2211 .unwrap();
2212 let schema: ValidatorSchema = fragment.try_into().unwrap();
2213
2214 assert!(schema
2215 .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
2216 .is_some());
2217 assert!(schema
2218 .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
2219 .is_some());
2220 assert!(schema
2221 .get_entity_type(&"Biz::Baz".parse().unwrap())
2222 .is_some());
2223 }
2224
2225 #[test]
2226 fn member_of_different_namespace() {
2227 let fragment: SchemaFragment = serde_json::from_value(json!({
2228 "Bar": {
2229 "entityTypes": {
2230 "Baz": {
2231 "memberOfTypes": ["Foo::Buz"]
2232 }
2233 },
2234 "actions": {}
2235 },
2236 "Foo": {
2237 "entityTypes": { "Buz": {} },
2238 "actions": { }
2239 }
2240 }))
2241 .unwrap();
2242 let schema: ValidatorSchema = fragment.try_into().unwrap();
2243
2244 let buz = schema
2245 .get_entity_type(&"Foo::Buz".parse().unwrap())
2246 .unwrap();
2247 assert_eq!(
2248 buz.descendants,
2249 HashSet::from(["Bar::Baz".parse().unwrap()])
2250 );
2251 }
2252
2253 #[test]
2254 fn attribute_different_namespace() {
2255 let fragment: SchemaFragment = serde_json::from_value(json!({
2256 "Bar": {
2257 "entityTypes": {
2258 "Baz": {
2259 "shape": {
2260 "type": "Record",
2261 "attributes": {
2262 "fiz": {
2263 "type": "Entity",
2264 "name": "Foo::Buz"
2265 }
2266 }
2267 }
2268 }
2269 },
2270 "actions": {}
2271 },
2272 "Foo": {
2273 "entityTypes": { "Buz": {} },
2274 "actions": { }
2275 }
2276 }))
2277 .unwrap();
2278
2279 let schema: ValidatorSchema = fragment.try_into().unwrap();
2280 let baz = schema
2281 .get_entity_type(&"Bar::Baz".parse().unwrap())
2282 .unwrap();
2283 assert_eq!(
2284 baz.attr("fiz").unwrap().attr_type,
2285 Type::named_entity_reference_from_str("Foo::Buz"),
2286 );
2287 }
2288
2289 #[test]
2290 fn applies_to_different_namespace() {
2291 let fragment: SchemaFragment = serde_json::from_value(json!({
2292 "Foo::Bar": {
2293 "entityTypes": { },
2294 "actions": {
2295 "Baz": {
2296 "appliesTo": {
2297 "principalTypes": [ "Fiz::Buz" ],
2298 "resourceTypes": [ "Fiz::Baz" ],
2299 }
2300 }
2301 }
2302 },
2303 "Fiz": {
2304 "entityTypes": {
2305 "Buz": {},
2306 "Baz": {}
2307 },
2308 "actions": { }
2309 }
2310 }))
2311 .unwrap();
2312 let schema: ValidatorSchema = fragment.try_into().unwrap();
2313
2314 let baz = schema
2315 .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2316 .unwrap();
2317 assert_eq!(
2318 baz.applies_to
2319 .applicable_principal_types()
2320 .collect::<HashSet<_>>(),
2321 HashSet::from([&EntityType::Concrete("Fiz::Buz".parse().unwrap())])
2322 );
2323 assert_eq!(
2324 baz.applies_to
2325 .applicable_resource_types()
2326 .collect::<HashSet<_>>(),
2327 HashSet::from([&EntityType::Concrete("Fiz::Baz".parse().unwrap())])
2328 );
2329 }
2330
2331 #[test]
2332 fn simple_defined_type() {
2333 let fragment: SchemaFragment = serde_json::from_value(json!({
2334 "": {
2335 "commonTypes": {
2336 "MyLong": {"type": "Long"}
2337 },
2338 "entityTypes": {
2339 "User": {
2340 "shape": {
2341 "type": "Record",
2342 "attributes": {
2343 "a": {"type": "MyLong"}
2344 }
2345 }
2346 }
2347 },
2348 "actions": {}
2349 }
2350 }))
2351 .unwrap();
2352 let schema: ValidatorSchema = fragment.try_into().unwrap();
2353 assert_eq!(
2354 schema.entity_types.iter().next().unwrap().1.attributes,
2355 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2356 );
2357 }
2358
2359 #[test]
2360 fn defined_record_as_attrs() {
2361 let fragment: SchemaFragment = serde_json::from_value(json!({
2362 "": {
2363 "commonTypes": {
2364 "MyRecord": {
2365 "type": "Record",
2366 "attributes": {
2367 "a": {"type": "Long"}
2368 }
2369 }
2370 },
2371 "entityTypes": {
2372 "User": { "shape": { "type": "MyRecord", } }
2373 },
2374 "actions": {}
2375 }
2376 }))
2377 .unwrap();
2378 let schema: ValidatorSchema = fragment.try_into().unwrap();
2379 assert_eq!(
2380 schema.entity_types.iter().next().unwrap().1.attributes,
2381 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2382 );
2383 }
2384
2385 #[test]
2386 fn cross_namespace_type() {
2387 let fragment: SchemaFragment = serde_json::from_value(json!({
2388 "A": {
2389 "commonTypes": {
2390 "MyLong": {"type": "Long"}
2391 },
2392 "entityTypes": { },
2393 "actions": {}
2394 },
2395 "B": {
2396 "entityTypes": {
2397 "User": {
2398 "shape": {
2399 "type": "Record",
2400 "attributes": {
2401 "a": {"type": "A::MyLong"}
2402 }
2403 }
2404 }
2405 },
2406 "actions": {}
2407 }
2408 }))
2409 .unwrap();
2410 let schema: ValidatorSchema = fragment.try_into().unwrap();
2411 assert_eq!(
2412 schema.entity_types.iter().next().unwrap().1.attributes,
2413 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2414 );
2415 }
2416
2417 #[test]
2418 fn cross_fragment_type() {
2419 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2420 "A": {
2421 "commonTypes": {
2422 "MyLong": {"type": "Long"}
2423 },
2424 "entityTypes": { },
2425 "actions": {}
2426 }
2427 }))
2428 .unwrap()
2429 .try_into()
2430 .unwrap();
2431 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2432 "A": {
2433 "entityTypes": {
2434 "User": {
2435 "shape": {
2436 "type": "Record",
2437 "attributes": {
2438 "a": {"type": "MyLong"}
2439 }
2440 }
2441 }
2442 },
2443 "actions": {}
2444 }
2445 }))
2446 .unwrap()
2447 .try_into()
2448 .unwrap();
2449 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2450
2451 assert_eq!(
2452 schema.entity_types.iter().next().unwrap().1.attributes,
2453 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2454 );
2455 }
2456
2457 #[test]
2458 #[should_panic]
2459 fn cross_fragment_duplicate_type() {
2460 let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2461 "A": {
2462 "commonTypes": {
2463 "MyLong": {"type": "Long"}
2464 },
2465 "entityTypes": {},
2466 "actions": {}
2467 }
2468 }))
2469 .unwrap()
2470 .try_into()
2471 .unwrap();
2472 let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2473 "A": {
2474 "commonTypes": {
2475 "MyLong": {"type": "Long"}
2476 },
2477 "entityTypes": {},
2478 "actions": {}
2479 }
2480 }))
2481 .unwrap()
2482 .try_into()
2483 .unwrap();
2484 let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2485
2486 assert_eq!(
2487 schema.entity_types.iter().next().unwrap().1.attributes,
2488 Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2489 );
2490 }
2491
2492 #[test]
2493 fn undeclared_type_in_attr() {
2494 let fragment: SchemaFragment = serde_json::from_value(json!({
2495 "": {
2496 "commonTypes": { },
2497 "entityTypes": {
2498 "User": {
2499 "shape": {
2500 "type": "Record",
2501 "attributes": {
2502 "a": {"type": "MyLong"}
2503 }
2504 }
2505 }
2506 },
2507 "actions": {}
2508 }
2509 }))
2510 .unwrap();
2511 match TryInto::<ValidatorSchema>::try_into(fragment) {
2512 Err(SchemaError::UndeclaredCommonType(_)) => (),
2513 s => panic!(
2514 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2515 s
2516 ),
2517 }
2518 }
2519
2520 #[test]
2521 fn undeclared_type_in_type_def() {
2522 let fragment: SchemaFragment = serde_json::from_value(json!({
2523 "": {
2524 "commonTypes": {
2525 "a": { "type": "b" }
2526 },
2527 "entityTypes": { },
2528 "actions": {}
2529 }
2530 }))
2531 .unwrap();
2532 match TryInto::<ValidatorSchema>::try_into(fragment) {
2533 Err(SchemaError::UndeclaredCommonType(_)) => (),
2534 s => panic!(
2535 "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2536 s
2537 ),
2538 }
2539 }
2540
2541 #[test]
2542 fn shape_not_record() {
2543 let fragment: SchemaFragment = serde_json::from_value(json!({
2544 "": {
2545 "commonTypes": {
2546 "MyLong": { "type": "Long" }
2547 },
2548 "entityTypes": {
2549 "User": {
2550 "shape": { "type": "MyLong" }
2551 }
2552 },
2553 "actions": {}
2554 }
2555 }))
2556 .unwrap();
2557 match TryInto::<ValidatorSchema>::try_into(fragment) {
2558 Err(SchemaError::ContextOrShapeNotRecord) => (),
2559 s => panic!(
2560 "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
2561 s
2562 ),
2563 }
2564 }
2565}