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