1use crate::{ValidatorEntityType, ValidatorSchema};
2use cedar_policy_core::entities::GetSchemaTypeError;
3use cedar_policy_core::extensions::Extensions;
4use cedar_policy_core::{ast, entities};
5use miette::Diagnostic;
6use smol_str::SmolStr;
7use std::collections::{HashMap, HashSet};
8use std::sync::Arc;
9use thiserror::Error;
10
11pub struct CoreSchema<'a> {
13 schema: &'a ValidatorSchema,
15 actions: HashMap<ast::EntityUID, Arc<ast::Entity>>,
21}
22
23impl<'a> CoreSchema<'a> {
24 pub fn new(schema: &'a ValidatorSchema) -> Self {
25 Self {
26 actions: schema
27 .action_entities_iter()
28 .map(|e| (e.uid().clone(), Arc::new(e)))
29 .collect(),
30 schema,
31 }
32 }
33}
34
35impl<'a> entities::Schema for CoreSchema<'a> {
36 type EntityTypeDescription = EntityTypeDescription;
37 type ActionEntityIterator = Vec<Arc<ast::Entity>>;
38
39 fn entity_type(&self, entity_type: &ast::EntityType) -> Option<EntityTypeDescription> {
40 match entity_type {
41 ast::EntityType::Unspecified => None, ast::EntityType::Specified(name) => EntityTypeDescription::new(self.schema, name),
43 }
44 }
45
46 fn action(&self, action: &ast::EntityUID) -> Option<Arc<ast::Entity>> {
47 self.actions.get(action).map(Arc::clone)
48 }
49
50 fn entity_types_with_basename<'b>(
51 &'b self,
52 basename: &'b ast::Id,
53 ) -> Box<dyn Iterator<Item = ast::EntityType> + 'b> {
54 Box::new(self.schema.entity_types().filter_map(move |(name, _)| {
55 if name.basename() == basename {
56 Some(ast::EntityType::Specified(name.clone()))
57 } else {
58 None
59 }
60 }))
61 }
62
63 fn action_entities(&self) -> Self::ActionEntityIterator {
64 self.actions.values().map(Arc::clone).collect()
65 }
66}
67
68pub struct EntityTypeDescription {
70 core_type: ast::EntityType,
72 validator_type: ValidatorEntityType,
74 allowed_parent_types: Arc<HashSet<ast::EntityType>>,
77}
78
79impl EntityTypeDescription {
80 pub fn new(schema: &ValidatorSchema, type_name: &ast::Name) -> Option<Self> {
83 Some(Self {
84 core_type: ast::EntityType::Specified(type_name.clone()),
85 validator_type: schema.get_entity_type(type_name).cloned()?,
86 allowed_parent_types: {
87 let mut set = HashSet::new();
88 for (possible_parent_typename, possible_parent_et) in schema.entity_types() {
89 if possible_parent_et.descendants.contains(type_name) {
90 set.insert(ast::EntityType::Specified(possible_parent_typename.clone()));
91 }
92 }
93 Arc::new(set)
94 },
95 })
96 }
97}
98
99impl entities::EntityTypeDescription for EntityTypeDescription {
100 fn entity_type(&self) -> ast::EntityType {
101 self.core_type.clone()
102 }
103
104 fn attr_type(&self, attr: &str) -> Option<entities::SchemaType> {
105 let attr_type: &crate::types::Type = &self.validator_type.attr(attr)?.attr_type;
106 #[allow(clippy::expect_used)]
111 let core_schema_type: entities::SchemaType = attr_type
112 .clone()
113 .try_into()
114 .expect("failed to convert validator type into Core SchemaType");
115 debug_assert!(attr_type.is_consistent_with(&core_schema_type));
116 Some(core_schema_type)
117 }
118
119 fn required_attrs<'s>(&'s self) -> Box<dyn Iterator<Item = SmolStr> + 's> {
120 Box::new(
121 self.validator_type
122 .attributes
123 .iter()
124 .filter(|(_, ty)| ty.is_required)
125 .map(|(attr, _)| attr.clone()),
126 )
127 }
128
129 fn allowed_parent_types(&self) -> Arc<HashSet<ast::EntityType>> {
130 Arc::clone(&self.allowed_parent_types)
131 }
132
133 fn open_attributes(&self) -> bool {
134 self.validator_type.open_attributes.is_open()
135 }
136}
137
138impl ast::RequestSchema for ValidatorSchema {
139 type Error = RequestValidationError;
140 fn validate_request(
141 &self,
142 request: &ast::Request,
143 extensions: Extensions<'_>,
144 ) -> std::result::Result<(), Self::Error> {
145 use ast::EntityUIDEntry;
146 if let EntityUIDEntry::Known {
150 euid: principal, ..
151 } = request.principal()
152 {
153 match principal.entity_type() {
154 ast::EntityType::Specified(name) => {
155 if self.get_entity_type(name).is_none() {
156 return Err(RequestValidationError::UndeclaredPrincipalType {
157 principal_ty: principal.entity_type().clone(),
158 });
159 }
160 }
161 ast::EntityType::Unspecified => {} }
163 }
164 if let EntityUIDEntry::Known { euid: resource, .. } = request.resource() {
165 match resource.entity_type() {
166 ast::EntityType::Specified(name) => {
167 if self.get_entity_type(name).is_none() {
168 return Err(RequestValidationError::UndeclaredResourceType {
169 resource_ty: resource.entity_type().clone(),
170 });
171 }
172 }
173 ast::EntityType::Unspecified => {} }
175 }
176
177 match request.action() {
179 EntityUIDEntry::Known { euid: action, .. } => {
180 let validator_action_id = self.get_action_id(action).ok_or_else(|| {
181 RequestValidationError::UndeclaredAction {
182 action: Arc::clone(action),
183 }
184 })?;
185 if let EntityUIDEntry::Known {
186 euid: principal, ..
187 } = request.principal()
188 {
189 if !validator_action_id
190 .applies_to
191 .is_applicable_principal_type(principal.entity_type())
192 {
193 return Err(RequestValidationError::InvalidPrincipalType {
194 principal_ty: principal.entity_type().clone(),
195 action: Arc::clone(action),
196 });
197 }
198 }
199 if let EntityUIDEntry::Known { euid: resource, .. } = request.resource() {
200 if !validator_action_id
201 .applies_to
202 .is_applicable_resource_type(resource.entity_type())
203 {
204 return Err(RequestValidationError::InvalidResourceType {
205 resource_ty: resource.entity_type().clone(),
206 action: Arc::clone(action),
207 });
208 }
209 }
210 if let Some(context) = request.context() {
211 let expected_context_ty = validator_action_id.context_type();
212 if !expected_context_ty
213 .typecheck_partial_value(context.as_ref(), extensions)
214 .map_err(RequestValidationError::TypeOfContext)?
215 {
216 return Err(RequestValidationError::InvalidContext {
217 context: context.clone(),
218 action: Arc::clone(action),
219 });
220 }
221 }
222 }
223 EntityUIDEntry::Unknown { .. } => {
224 }
231 }
232 Ok(())
233 }
234}
235
236impl<'a> ast::RequestSchema for CoreSchema<'a> {
237 type Error = RequestValidationError;
238 fn validate_request(
239 &self,
240 request: &ast::Request,
241 extensions: Extensions<'_>,
242 ) -> Result<(), Self::Error> {
243 self.schema.validate_request(request, extensions)
244 }
245}
246
247#[derive(Debug, Diagnostic, Error)]
248pub enum RequestValidationError {
249 #[error("request's action `{action}` is not declared in the schema")]
251 UndeclaredAction {
252 action: Arc<ast::EntityUID>,
254 },
255 #[error("principal type `{principal_ty}` is not declared in the schema")]
257 UndeclaredPrincipalType {
258 principal_ty: ast::EntityType,
260 },
261 #[error("resource type `{resource_ty}` is not declared in the schema")]
263 UndeclaredResourceType {
264 resource_ty: ast::EntityType,
266 },
267 #[error("principal type `{principal_ty}` is not valid for `{action}`")]
270 InvalidPrincipalType {
271 principal_ty: ast::EntityType,
273 action: Arc<ast::EntityUID>,
275 },
276 #[error("resource type `{resource_ty}` is not valid for `{action}`")]
279 InvalidResourceType {
280 resource_ty: ast::EntityType,
282 action: Arc<ast::EntityUID>,
284 },
285 #[error("context `{context}` is not valid for `{action}`")]
287 InvalidContext {
288 context: ast::Context,
290 action: Arc<ast::EntityUID>,
292 },
293 #[error("context is not valid: {0}")]
296 #[diagnostic(transparent)]
297 TypeOfContext(GetSchemaTypeError),
298}
299
300pub struct ContextSchema(
303 crate::types::Type,
306);
307
308impl entities::ContextSchema for ContextSchema {
310 fn context_type(&self) -> entities::SchemaType {
311 #[allow(clippy::expect_used)]
313 self.0
314 .clone()
315 .try_into()
316 .expect("failed to convert validator type into Core SchemaType")
317 }
318}
319
320pub fn context_schema_for_action(
325 schema: &ValidatorSchema,
326 action: &ast::EntityUID,
327) -> Option<ContextSchema> {
328 schema.context_type(action).map(ContextSchema)
335}
336
337#[cfg(test)]
338mod test {
339 use super::*;
340 use cool_asserts::assert_matches;
341 use serde_json::json;
342
343 fn schema() -> ValidatorSchema {
344 let src = json!(
345 { "": {
346 "entityTypes": {
347 "User": {
348 "memberOfTypes": [ "Group" ]
349 },
350 "Group": {
351 "memberOfTypes": []
352 },
353 "Photo": {
354 "memberOfTypes": [ "Album" ]
355 },
356 "Album": {
357 "memberOfTypes": []
358 }
359 },
360 "actions": {
361 "view_photo": {
362 "appliesTo": {
363 "principalTypes": ["User", "Group"],
364 "resourceTypes": ["Photo"]
365 }
366 },
367 "edit_photo": {
368 "appliesTo": {
369 "principalTypes": ["User", "Group"],
370 "resourceTypes": ["Photo"],
371 "context": {
372 "type": "Record",
373 "attributes": {
374 "admin_approval": {
375 "type": "Boolean",
376 "required": true,
377 }
378 }
379 }
380 }
381 }
382 }
383 }});
384 ValidatorSchema::from_json_value(src, Extensions::all_available())
385 .expect("failed to create ValidatorSchema")
386 }
387
388 #[test]
390 fn success_concrete_request_no_context() {
391 assert_matches!(
392 ast::Request::new(
393 (
394 ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(),
395 None
396 ),
397 (
398 ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(),
399 None
400 ),
401 (
402 ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(),
403 None
404 ),
405 ast::Context::empty(),
406 Some(&schema()),
407 Extensions::all_available(),
408 ),
409 Ok(_)
410 );
411 }
412
413 #[test]
415 fn success_concrete_request_with_context() {
416 assert_matches!(
417 ast::Request::new(
418 (
419 ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(),
420 None
421 ),
422 (
423 ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap(),
424 None
425 ),
426 (
427 ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(),
428 None
429 ),
430 ast::Context::from_pairs(
431 [("admin_approval".into(), ast::RestrictedExpr::val(true))],
432 Extensions::all_available()
433 )
434 .unwrap(),
435 Some(&schema()),
436 Extensions::all_available(),
437 ),
438 Ok(_)
439 );
440 }
441
442 #[test]
444 fn success_principal_unknown() {
445 assert_matches!(
446 ast::Request::new_with_unknowns(
447 ast::EntityUIDEntry::Unknown { loc: None },
448 ast::EntityUIDEntry::concrete(
449 ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(),
450 None,
451 ),
452 ast::EntityUIDEntry::concrete(
453 ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(),
454 None,
455 ),
456 Some(ast::Context::empty()),
457 Some(&schema()),
458 Extensions::all_available(),
459 ),
460 Ok(_)
461 );
462 }
463
464 #[test]
466 fn success_action_unknown() {
467 assert_matches!(
468 ast::Request::new_with_unknowns(
469 ast::EntityUIDEntry::concrete(
470 ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(),
471 None,
472 ),
473 ast::EntityUIDEntry::Unknown { loc: None },
474 ast::EntityUIDEntry::concrete(
475 ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(),
476 None,
477 ),
478 Some(ast::Context::empty()),
479 Some(&schema()),
480 Extensions::all_available(),
481 ),
482 Ok(_)
483 );
484 }
485
486 #[test]
488 fn success_resource_unknown() {
489 assert_matches!(
490 ast::Request::new_with_unknowns(
491 ast::EntityUIDEntry::concrete(
492 ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(),
493 None,
494 ),
495 ast::EntityUIDEntry::concrete(
496 ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(),
497 None,
498 ),
499 ast::EntityUIDEntry::Unknown { loc: None },
500 Some(ast::Context::empty()),
501 Some(&schema()),
502 Extensions::all_available(),
503 ),
504 Ok(_)
505 );
506 }
507
508 #[test]
510 fn success_context_unknown() {
511 assert_matches!(
512 ast::Request::new_with_unknowns(
513 ast::EntityUIDEntry::concrete(
514 ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(),
515 None,
516 ),
517 ast::EntityUIDEntry::concrete(
518 ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(),
519 None,
520 ),
521 ast::EntityUIDEntry::concrete(
522 ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(),
523 None,
524 ),
525 None,
526 Some(&schema()),
527 Extensions::all_available(),
528 ),
529 Ok(_)
530 )
531 }
532
533 #[test]
535 fn success_everything_unspecified() {
536 assert_matches!(
537 ast::Request::new_with_unknowns(
538 ast::EntityUIDEntry::Unknown { loc: None },
539 ast::EntityUIDEntry::Unknown { loc: None },
540 ast::EntityUIDEntry::Unknown { loc: None },
541 None,
542 Some(&schema()),
543 Extensions::all_available(),
544 ),
545 Ok(_)
546 );
547 }
548
549 #[test]
553 fn success_unknown_action_but_invalid_types() {
554 assert_matches!(
555 ast::Request::new_with_unknowns(
556 ast::EntityUIDEntry::concrete(
557 ast::EntityUID::with_eid_and_type("Album", "abc123").unwrap(),
558 None,
559 ),
560 ast::EntityUIDEntry::Unknown { loc: None },
561 ast::EntityUIDEntry::concrete(
562 ast::EntityUID::with_eid_and_type("User", "alice").unwrap(),
563 None,
564 ),
565 None,
566 Some(&schema()),
567 Extensions::all_available(),
568 ),
569 Ok(_)
570 );
571 }
572
573 #[test]
575 fn action_not_declared() {
576 assert_matches!(
577 ast::Request::new(
578 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
579 (ast::EntityUID::with_eid_and_type("Action", "destroy").unwrap(), None),
580 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
581 ast::Context::empty(),
582 Some(&schema()),
583 Extensions::all_available(),
584 ),
585 Err(RequestValidationError::UndeclaredAction { action }) => {
586 assert_eq!(&*action, &ast::EntityUID::with_eid_and_type("Action", "destroy").unwrap());
587 }
588 );
589 }
590
591 #[test]
593 fn action_unspecified() {
594 assert_matches!(
595 ast::Request::new(
596 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
597 (ast::EntityUID::unspecified_from_eid(ast::Eid::new("blahblah")), None),
598 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
599 ast::Context::empty(),
600 Some(&schema()),
601 Extensions::all_available(),
602 ),
603 Err(RequestValidationError::UndeclaredAction { action }) => {
604 assert_eq!(&*action, &ast::EntityUID::unspecified_from_eid(ast::Eid::new("blahblah")));
605 }
606 );
607 }
608
609 #[test]
611 fn principal_type_not_declared() {
612 assert_matches!(
613 ast::Request::new(
614 (ast::EntityUID::with_eid_and_type("Foo", "abc123").unwrap(), None),
615 (ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(), None),
616 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
617 ast::Context::empty(),
618 Some(&schema()),
619 Extensions::all_available(),
620 ),
621 Err(RequestValidationError::UndeclaredPrincipalType { principal_ty }) => {
622 assert_eq!(principal_ty, ast::EntityType::Specified(ast::Name::parse_unqualified_name("Foo").unwrap()));
623 }
624 );
625 }
626
627 #[test]
629 fn principal_type_not_declared_action_unspecified() {
630 assert_matches!(
631 ast::Request::new(
632 (ast::EntityUID::with_eid_and_type("Foo", "abc123").unwrap(), None),
633 (ast::EntityUID::unspecified_from_eid(ast::Eid::new("blahblah")), None),
634 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
635 ast::Context::empty(),
636 Some(&schema()),
637 Extensions::all_available(),
638 ),
639 Err(RequestValidationError::UndeclaredPrincipalType { principal_ty }) => {
640 assert_eq!(principal_ty, ast::EntityType::Specified(ast::Name::parse_unqualified_name("Foo").unwrap()));
641 }
642 );
643 }
644
645 #[test]
647 fn principal_unspecified() {
648 assert_matches!(
649 ast::Request::new(
650 (ast::EntityUID::unspecified_from_eid(ast::Eid::new("principal")), None),
651 (ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(), None),
652 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
653 ast::Context::empty(),
654 Some(&schema()),
655 Extensions::all_available(),
656 ),
657 Err(RequestValidationError::InvalidPrincipalType { principal_ty, .. }) => {
658 assert_eq!(principal_ty, ast::EntityType::Unspecified);
659 }
660 );
661 }
662
663 #[test]
665 fn resource_type_not_declared() {
666 assert_matches!(
667 ast::Request::new(
668 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
669 (ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(), None),
670 (ast::EntityUID::with_eid_and_type("Foo", "vacationphoto94.jpg").unwrap(), None),
671 ast::Context::empty(),
672 Some(&schema()),
673 Extensions::all_available(),
674 ),
675 Err(RequestValidationError::UndeclaredResourceType { resource_ty }) => {
676 assert_eq!(resource_ty, ast::EntityType::Specified(ast::Name::parse_unqualified_name("Foo").unwrap()));
677 }
678 );
679 }
680
681 #[test]
683 fn resource_type_not_declared_action_unspecified() {
684 assert_matches!(
685 ast::Request::new(
686 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
687 (ast::EntityUID::unspecified_from_eid(ast::Eid::new("blahblah")), None),
688 (ast::EntityUID::with_eid_and_type("Foo", "vacationphoto94.jpg").unwrap(), None),
689 ast::Context::empty(),
690 Some(&schema()),
691 Extensions::all_available(),
692 ),
693 Err(RequestValidationError::UndeclaredResourceType { resource_ty }) => {
694 assert_eq!(resource_ty, ast::EntityType::Specified(ast::Name::parse_unqualified_name("Foo").unwrap()));
695 }
696 );
697 }
698
699 #[test]
701 fn resource_unspecified() {
702 assert_matches!(
703 ast::Request::new(
704 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
705 (ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(), None),
706 (ast::EntityUID::unspecified_from_eid(ast::Eid::new("resource")), None),
707 ast::Context::empty(),
708 Some(&schema()),
709 Extensions::all_available(),
710 ),
711 Err(RequestValidationError::InvalidResourceType { resource_ty, .. }) => {
712 assert_eq!(resource_ty, ast::EntityType::Unspecified);
713 }
714 );
715 }
716
717 #[test]
719 fn principal_type_invalid() {
720 assert_matches!(
721 ast::Request::new(
722 (ast::EntityUID::with_eid_and_type("Album", "abc123").unwrap(), None),
723 (ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(), None),
724 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
725 ast::Context::empty(),
726 Some(&schema()),
727 Extensions::all_available(),
728 ),
729 Err(RequestValidationError::InvalidPrincipalType { principal_ty, action }) => {
730 assert_eq!(principal_ty, ast::EntityType::Specified(ast::Name::parse_unqualified_name("Album").unwrap()));
731 assert_eq!(&*action, &ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap());
732 }
733 );
734 }
735
736 #[test]
738 fn resource_type_invalid() {
739 assert_matches!(
740 ast::Request::new(
741 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
742 (ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap(), None),
743 (ast::EntityUID::with_eid_and_type("Group", "coders").unwrap(), None),
744 ast::Context::empty(),
745 Some(&schema()),
746 Extensions::all_available(),
747 ),
748 Err(RequestValidationError::InvalidResourceType { resource_ty, action }) => {
749 assert_eq!(resource_ty, ast::EntityType::Specified(ast::Name::parse_unqualified_name("Group").unwrap()));
750 assert_eq!(&*action, &ast::EntityUID::with_eid_and_type("Action", "view_photo").unwrap());
751 }
752 );
753 }
754
755 #[test]
757 fn context_missing_attribute() {
758 assert_matches!(
759 ast::Request::new(
760 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
761 (ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap(), None),
762 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
763 ast::Context::empty(),
764 Some(&schema()),
765 Extensions::all_available(),
766 ),
767 Err(RequestValidationError::InvalidContext { context, action }) => {
768 assert_eq!(context, ast::Context::empty());
769 assert_eq!(&*action, &ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap());
770 }
771 );
772 }
773
774 #[test]
776 fn context_extra_attribute() {
777 let context_with_extra_attr = ast::Context::from_pairs(
778 [
779 ("admin_approval".into(), ast::RestrictedExpr::val(true)),
780 ("extra".into(), ast::RestrictedExpr::val(42)),
781 ],
782 Extensions::all_available(),
783 )
784 .unwrap();
785 assert_matches!(
786 ast::Request::new(
787 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
788 (ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap(), None),
789 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
790 context_with_extra_attr.clone(),
791 Some(&schema()),
792 Extensions::all_available(),
793 ),
794 Err(RequestValidationError::InvalidContext { context, action }) => {
795 assert_eq!(context, context_with_extra_attr);
796 assert_eq!(&*action, &ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap());
797 }
798 );
799 }
800
801 #[test]
803 fn context_attribute_wrong_type() {
804 let context_with_wrong_type_attr = ast::Context::from_pairs(
805 [(
806 "admin_approval".into(),
807 ast::RestrictedExpr::set([ast::RestrictedExpr::val(true)]),
808 )],
809 Extensions::all_available(),
810 )
811 .unwrap();
812 assert_matches!(
813 ast::Request::new(
814 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
815 (ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap(), None),
816 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
817 context_with_wrong_type_attr.clone(),
818 Some(&schema()),
819 Extensions::all_available(),
820 ),
821 Err(RequestValidationError::InvalidContext { context, action }) => {
822 assert_eq!(context, context_with_wrong_type_attr);
823 assert_eq!(&*action, &ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap());
824 }
825 );
826 }
827
828 #[test]
830 fn context_attribute_heterogeneous_set() {
831 let context_with_heterogeneous_set = ast::Context::from_pairs(
832 [(
833 "admin_approval".into(),
834 ast::RestrictedExpr::set([
835 ast::RestrictedExpr::val(true),
836 ast::RestrictedExpr::val(-1001),
837 ]),
838 )],
839 Extensions::all_available(),
840 )
841 .unwrap();
842 assert_matches!(
843 ast::Request::new(
844 (ast::EntityUID::with_eid_and_type("User", "abc123").unwrap(), None),
845 (ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap(), None),
846 (ast::EntityUID::with_eid_and_type("Photo", "vacationphoto94.jpg").unwrap(), None),
847 context_with_heterogeneous_set.clone(),
848 Some(&schema()),
849 Extensions::all_available(),
850 ),
851 Err(RequestValidationError::InvalidContext { context, action }) => {
852 assert_eq!(context, context_with_heterogeneous_set);
853 assert_eq!(&*action, &ast::EntityUID::with_eid_and_type("Action", "edit_photo").unwrap());
854 }
855 );
856 }
857}