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