1use crate::ast::*;
18use itertools::Itertools;
19use serde::{Deserialize, Serialize};
20use smol_str::SmolStr;
21use std::collections::BTreeMap;
22use std::{collections::HashMap, sync::Arc};
23use thiserror::Error;
24
25#[derive(Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
28#[serde(from = "TemplateBody")]
29#[serde(into = "TemplateBody")]
30pub struct Template {
31 body: TemplateBody,
32 slots: Vec<SlotId>,
35}
36
37impl From<Template> for TemplateBody {
38 fn from(val: Template) -> Self {
39 val.body
40 }
41}
42
43impl Template {
44 #[cfg(test)]
46 pub fn check_invariant(&self) {
47 let cond = self.body.condition();
48 let slots = cond.slots().collect::<Vec<_>>();
49 for slot in slots.iter() {
50 assert!(self.slots.contains(slot));
51 }
52 for slot in self.slots() {
53 assert!(slots.contains(&slot));
54 }
55 }
56 pub fn new(
64 id: PolicyID,
65 annotations: BTreeMap<Id, SmolStr>,
66 effect: Effect,
67 principal_constraint: PrincipalConstraint,
68 action_constraint: ActionConstraint,
69 resource_constraint: ResourceConstraint,
70 non_head_constraint: Expr,
71 ) -> Self {
72 let body = TemplateBody::new(
73 id,
74 annotations,
75 effect,
76 principal_constraint,
77 action_constraint,
78 resource_constraint,
79 non_head_constraint,
80 );
81 Template::from(body)
84 }
85
86 pub fn principal_constraint(&self) -> &PrincipalConstraint {
88 self.body.principal_constraint()
89 }
90
91 pub fn action_constraint(&self) -> &ActionConstraint {
93 self.body.action_constraint()
94 }
95
96 pub fn resource_constraint(&self) -> &ResourceConstraint {
98 self.body.resource_constraint()
99 }
100
101 pub fn non_head_constraints(&self) -> &Expr {
103 self.body.non_head_constraints()
104 }
105
106 pub fn id(&self) -> &PolicyID {
108 self.body.id()
109 }
110
111 pub fn new_id(&self, id: PolicyID) -> Self {
113 Template {
114 body: self.body.new_id(id),
115 slots: self.slots.clone(),
116 }
117 }
118
119 pub fn effect(&self) -> Effect {
121 self.body.effect()
122 }
123
124 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
126 self.body.annotation(key)
127 }
128
129 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
131 self.body.annotations()
132 }
133
134 pub fn condition(&self) -> Expr {
140 self.body.condition()
141 }
142
143 pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
145 self.slots.iter()
146 }
147
148 pub fn is_static(&self) -> bool {
153 self.slots.is_empty()
154 }
155
156 pub fn check_binding(
160 template: &Template,
161 values: &HashMap<SlotId, EntityUID>,
162 ) -> Result<(), LinkingError> {
163 let unbound = template
165 .slots
166 .iter()
167 .filter(|slot| !values.contains_key(slot))
168 .collect::<Vec<_>>();
169
170 let extra = values
171 .iter()
172 .filter_map(|(slot, _)| {
173 if !template.slots.contains(slot) {
174 Some(slot)
175 } else {
176 None
177 }
178 })
179 .collect::<Vec<_>>();
180
181 if unbound.is_empty() && extra.is_empty() {
182 Ok(())
183 } else {
184 Err(LinkingError::from_unbound_and_extras(
185 unbound.into_iter().map(SlotId::clone),
186 extra.into_iter().map(SlotId::clone),
187 ))
188 }
189 }
190
191 pub fn link(
195 template: Arc<Template>,
196 new_id: PolicyID,
197 values: HashMap<SlotId, EntityUID>,
198 ) -> Result<Policy, LinkingError> {
199 Template::check_binding(&template, &values)
201 .map(|_| Policy::new(template, Some(new_id), values))
202 }
203
204 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
207 let body: TemplateBody = p.into();
208 let t = Arc::new(Self {
212 body,
213 slots: vec![],
214 });
215 #[cfg(test)]
216 {
217 t.check_invariant();
218 }
219 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
225 (t, p)
226 }
227}
228
229impl From<TemplateBody> for Template {
230 fn from(body: TemplateBody) -> Self {
231 let slots = body.condition().slots().copied().collect::<Vec<_>>();
234 Self { body, slots }
235 }
236}
237
238impl std::fmt::Display for Template {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(f, "{}", self.body)
241 }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Error)]
246pub enum LinkingError {
247 #[error("{}", describe_arity_error(.unbound_values, .extra_values))]
250 ArityError {
251 unbound_values: Vec<SlotId>,
253 extra_values: Vec<SlotId>,
255 },
256
257 #[error("failed to find a template with id: {0}")]
259 NoSuchTemplate(PolicyID),
260
261 #[error("template-linked policy id conflicts with an existing policy id")]
263 PolicyIdConflict,
264}
265
266impl LinkingError {
267 fn from_unbound_and_extras<T>(unbound: T, extra: T) -> Self
268 where
269 T: Iterator<Item = SlotId>,
270 {
271 Self::ArityError {
272 unbound_values: unbound.collect(),
273 extra_values: extra.collect(),
274 }
275 }
276}
277
278fn describe_arity_error(unbound_values: &[SlotId], extra_values: &[SlotId]) -> String {
279 match (unbound_values.len(), extra_values.len()) {
280 #[allow(clippy::unreachable)]
282 (0,0) => unreachable!(),
283 (_unbound, 0) => format!("the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
284 (0, _extra) => format!("the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
285 (_unbound, _extra) => format!("the following slots were not provided as arguments: {}\nthe following slots were provided as arguments, but did not exist in the template: {}", unbound_values.iter().join(","), extra_values.iter().join(","))
286 }
287}
288
289#[derive(Debug, Clone, Eq, PartialEq)]
297pub struct Policy {
298 template: Arc<Template>,
300 link: Option<PolicyID>,
303 values: HashMap<SlotId, EntityUID>,
309}
310
311impl Policy {
312 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
316 #[cfg(test)]
317 {
318 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
319 }
320 Self {
326 template,
327 link: link_id,
328 values,
329 }
330 }
331
332 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID) -> Self {
334 let t = Template::new(
335 id,
336 BTreeMap::new(),
337 effect,
338 PrincipalConstraint::any(),
339 ActionConstraint::any(),
340 ResourceConstraint::any(),
341 when,
342 );
343 Self::new(Arc::new(t), None, SlotEnv::new())
344 }
345
346 pub fn template(&self) -> &Template {
348 &self.template
349 }
350
351 pub(crate) fn template_arc(&self) -> Arc<Template> {
353 Arc::clone(&self.template)
354 }
355
356 pub fn effect(&self) -> Effect {
358 self.template.effect()
359 }
360
361 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
363 self.template.annotation(key)
364 }
365
366 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
368 self.template.annotations()
369 }
370
371 pub fn principal_constraint(&self) -> PrincipalConstraint {
377 let constraint = self.template.principal_constraint().clone();
378 match self.values.get(&SlotId::principal()) {
379 None => constraint,
380 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
381 }
382 }
383
384 pub fn action_constraint(&self) -> &ActionConstraint {
386 self.template.action_constraint()
387 }
388
389 pub fn resource_constraint(&self) -> ResourceConstraint {
395 let constraint = self.template.resource_constraint().clone();
396 match self.values.get(&SlotId::resource()) {
397 None => constraint,
398 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
399 }
400 }
401
402 pub fn non_head_constraints(&self) -> &Expr {
404 self.template.non_head_constraints()
405 }
406
407 pub fn condition(&self) -> Expr {
409 self.template.condition()
410 }
411
412 pub fn env(&self) -> &SlotEnv {
415 &self.values
416 }
417
418 pub fn id(&self) -> &PolicyID {
420 self.link.as_ref().unwrap_or_else(|| self.template.id())
421 }
422
423 pub fn new_id(&self, id: PolicyID) -> Self {
425 match self.link {
426 None => Policy {
427 template: Arc::new(self.template.new_id(id)),
428 link: None,
429 values: self.values.clone(),
430 },
431 Some(_) => Policy {
432 template: self.template.clone(),
433 link: Some(id),
434 values: self.values.clone(),
435 },
436 }
437 }
438
439 pub fn is_static(&self) -> bool {
441 self.link.is_none()
442 }
443}
444
445impl std::fmt::Display for Policy {
446 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
447 if self.is_static() {
448 write!(f, "{}", self.template())
449 } else {
450 write!(
451 f,
452 "Template Instance of {}, slots: [{}]",
453 self.template().id(),
454 display_slot_env(self.env())
455 )
456 }
457 }
458}
459
460pub type SlotEnv = HashMap<SlotId, EntityUID>;
462
463#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
466pub struct LiteralPolicy {
467 template_id: PolicyID,
469 link_id: Option<PolicyID>,
473 values: SlotEnv,
475}
476
477#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
479pub struct BorrowedLiteralPolicy<'a> {
480 template_id: &'a PolicyID,
482 link_id: Option<&'a PolicyID>,
486 values: &'a SlotEnv,
488}
489
490impl<'a> From<&'a Policy> for BorrowedLiteralPolicy<'a> {
491 fn from(p: &'a Policy) -> Self {
492 Self {
493 template_id: p.template.id(),
494 link_id: p.link.as_ref(),
495 values: &p.values,
496 }
497 }
498}
499
500impl std::hash::Hash for LiteralPolicy {
503 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
504 self.template_id.hash(state);
505 let mut buf = self.values.iter().collect::<Vec<_>>();
507 buf.sort();
508 for (id, euid) in buf {
509 id.hash(state);
510 euid.hash(state);
511 }
512 }
513}
514
515impl std::cmp::PartialEq for LiteralPolicy {
516 fn eq(&self, other: &Self) -> bool {
517 self.template_id() == other.template_id()
518 && self.link_id == other.link_id
519 && self.values == other.values
520 }
521}
522
523#[cfg(test)]
525mod hashing_tests {
526 use std::{
527 collections::hash_map::DefaultHasher,
528 hash::{Hash, Hasher},
529 };
530
531 use super::*;
532
533 fn compute_hash(ir: LiteralPolicy) -> u64 {
534 let mut s = DefaultHasher::new();
535 ir.hash(&mut s);
536 s.finish()
537 }
538
539 fn build_template_linked_policy() -> LiteralPolicy {
540 let mut map = HashMap::new();
541 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
542 LiteralPolicy {
543 template_id: PolicyID::from_string("template"),
544 link_id: Some(PolicyID::from_string("id")),
545 values: map,
546 }
547 }
548
549 #[test]
550 fn hash_property_instances() {
551 let a = build_template_linked_policy();
552 let b = build_template_linked_policy();
553 assert_eq!(a, b);
554 assert_eq!(compute_hash(a), compute_hash(b));
555 }
556}
557#[derive(Debug, Error)]
565pub enum ReificationError {
566 #[error("The PolicyID linked to does not exist")]
568 NoSuchTemplate(PolicyID),
569 #[error("{0}")]
571 Instantiation(#[from] LinkingError),
572}
573
574impl LiteralPolicy {
575 pub fn reify(
580 self,
581 templates: &HashMap<PolicyID, Arc<Template>>,
582 ) -> Result<Policy, ReificationError> {
583 let template = templates
584 .get(&self.template_id)
585 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
586 Template::check_binding(template, &self.values).map_err(ReificationError::Instantiation)?;
588 Ok(Policy::new(template.clone(), self.link_id, self.values))
589 }
590
591 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
593 self.values.get(id)
594 }
595
596 pub fn id(&self) -> &PolicyID {
599 self.link_id.as_ref().unwrap_or(&self.template_id)
600 }
601
602 pub fn template_id(&self) -> &PolicyID {
604 &self.template_id
605 }
606
607 pub fn is_static(&self) -> bool {
609 self.link_id.is_none()
610 }
611}
612
613fn display_slot_env(env: &SlotEnv) -> String {
614 env.iter()
615 .map(|(slot, value)| format!("{slot} -> {value}"))
616 .join(",")
617}
618
619impl std::fmt::Display for LiteralPolicy {
620 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
621 if self.is_static() {
622 write!(f, "Static policy w/ ID {}", self.template_id())
623 } else {
624 write!(
625 f,
626 "Template linked policy of {}, slots: [{}]",
627 self.template_id(),
628 display_slot_env(&self.values),
629 )
630 }
631 }
632}
633
634impl From<Policy> for LiteralPolicy {
635 fn from(p: Policy) -> Self {
636 Self {
637 template_id: p.template.id().clone(),
638 link_id: p.link,
639 values: p.values,
640 }
641 }
642}
643
644#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
648pub struct StaticPolicy(TemplateBody);
649
650impl StaticPolicy {
651 pub fn id(&self) -> &PolicyID {
653 self.0.id()
654 }
655
656 pub fn new_id(&mut self, id: PolicyID) -> Self {
658 StaticPolicy(self.0.new_id(id))
659 }
660
661 pub fn effect(&self) -> Effect {
663 self.0.effect()
664 }
665
666 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
668 self.0.annotation(key)
669 }
670
671 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
673 self.0.annotations()
674 }
675
676 pub fn principal_constraint(&self) -> &PrincipalConstraint {
678 self.0.principal_constraint()
679 }
680
681 pub fn principal_constraint_expr(&self) -> Expr {
685 self.0.principal_constraint_expr()
686 }
687
688 pub fn action_constraint(&self) -> &ActionConstraint {
690 self.0.action_constraint()
691 }
692
693 pub fn action_constraint_expr(&self) -> Expr {
697 self.0.action_constraint_expr()
698 }
699
700 pub fn resource_constraint(&self) -> &ResourceConstraint {
702 self.0.resource_constraint()
703 }
704
705 pub fn resource_constraint_expr(&self) -> Expr {
709 self.0.resource_constraint_expr()
710 }
711
712 pub fn non_head_constraints(&self) -> &Expr {
717 self.0.non_head_constraints()
718 }
719
720 pub fn condition(&self) -> Expr {
726 self.0.condition()
727 }
728
729 pub fn new(
731 id: PolicyID,
732 annotations: BTreeMap<Id, SmolStr>,
733 effect: Effect,
734 principal_constraint: PrincipalConstraint,
735 action_constraint: ActionConstraint,
736 resource_constraint: ResourceConstraint,
737 non_head_constraints: Expr,
738 ) -> Result<Self, UnexpectedSlotError> {
739 let body = TemplateBody::new(
740 id,
741 annotations,
742 effect,
743 principal_constraint,
744 action_constraint,
745 resource_constraint,
746 non_head_constraints,
747 );
748 let num_slots = body.condition().slots().next().map(SlotId::clone);
749 match num_slots {
751 Some(slot_id) => Err(UnexpectedSlotError::Named(slot_id))?,
752 None => Ok(Self(body)),
753 }
754 }
755}
756
757impl TryFrom<Template> for StaticPolicy {
758 type Error = UnexpectedSlotError;
759
760 fn try_from(value: Template) -> Result<Self, Self::Error> {
761 let o = value.slots().next().map(SlotId::clone);
763 match o {
764 Some(slot_id) => Err(Self::Error::Named(slot_id)),
765 None => Ok(Self(value.body)),
766 }
767 }
768}
769
770impl From<StaticPolicy> for Policy {
771 fn from(inline: StaticPolicy) -> Policy {
772 let (_, policy) = Template::link_static_policy(inline);
773 policy
774 }
775}
776
777impl From<StaticPolicy> for Arc<Template> {
778 fn from(p: StaticPolicy) -> Self {
779 let (t, _) = Template::link_static_policy(p);
780 t
781 }
782}
783
784#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
789pub struct TemplateBody {
790 id: PolicyID,
792 annotations: BTreeMap<Id, SmolStr>,
794 effect: Effect,
796 principal_constraint: PrincipalConstraint,
800 action_constraint: ActionConstraint,
804 resource_constraint: ResourceConstraint,
808 non_head_constraints: Expr,
813}
814
815impl TemplateBody {
816 pub fn id(&self) -> &PolicyID {
818 &self.id
819 }
820
821 pub fn new_id(&self, id: PolicyID) -> Self {
823 let mut new = self.clone();
824 new.id = id;
825 new
826 }
827
828 pub fn effect(&self) -> Effect {
830 self.effect
831 }
832
833 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
835 self.annotations.get(key)
836 }
837
838 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
840 self.annotations.iter()
841 }
842
843 pub fn principal_constraint(&self) -> &PrincipalConstraint {
845 &self.principal_constraint
846 }
847
848 pub fn principal_constraint_expr(&self) -> Expr {
852 self.principal_constraint.as_expr()
853 }
854
855 pub fn action_constraint(&self) -> &ActionConstraint {
857 &self.action_constraint
858 }
859
860 pub fn action_constraint_expr(&self) -> Expr {
864 self.action_constraint.as_expr()
865 }
866
867 pub fn resource_constraint(&self) -> &ResourceConstraint {
869 &self.resource_constraint
870 }
871
872 pub fn resource_constraint_expr(&self) -> Expr {
876 self.resource_constraint.as_expr()
877 }
878
879 pub fn non_head_constraints(&self) -> &Expr {
884 &self.non_head_constraints
885 }
886
887 pub fn condition(&self) -> Expr {
893 Expr::and(
894 Expr::and(
895 Expr::and(
896 self.principal_constraint_expr(),
897 self.action_constraint_expr(),
898 ),
899 self.resource_constraint_expr(),
900 ),
901 self.non_head_constraints.clone(),
902 )
903 }
904
905 pub fn new(
907 id: PolicyID,
908 annotations: BTreeMap<Id, SmolStr>,
909 effect: Effect,
910 principal_constraint: PrincipalConstraint,
911 action_constraint: ActionConstraint,
912 resource_constraint: ResourceConstraint,
913 non_head_constraints: Expr,
914 ) -> Self {
915 Self {
916 id,
917 annotations,
918 effect,
919 principal_constraint,
920 action_constraint,
921 resource_constraint,
922 non_head_constraints,
923 }
924 }
925}
926
927impl From<StaticPolicy> for TemplateBody {
928 fn from(p: StaticPolicy) -> Self {
929 p.0
930 }
931}
932
933impl std::fmt::Display for TemplateBody {
934 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
935 for (k, v) in &self.annotations {
936 writeln!(f, "@{}(\"{}\")", k, v.escape_debug())?
937 }
938 write!(
939 f,
940 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
941 self.effect(),
942 self.principal_constraint(),
943 self.action_constraint(),
944 self.resource_constraint(),
945 self.non_head_constraints()
946 )
947 }
948}
949
950#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
952pub struct PrincipalConstraint {
953 pub(crate) constraint: PrincipalOrResourceConstraint,
954}
955
956impl PrincipalConstraint {
957 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
959 PrincipalConstraint { constraint }
960 }
961
962 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
964 &self.constraint
965 }
966
967 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
969 self.constraint
970 }
971
972 pub fn as_expr(&self) -> Expr {
974 self.constraint.as_expr(PrincipalOrResource::Principal)
975 }
976
977 pub fn any() -> Self {
979 PrincipalConstraint {
980 constraint: PrincipalOrResourceConstraint::any(),
981 }
982 }
983
984 pub fn is_eq(euid: EntityUID) -> Self {
986 PrincipalConstraint {
987 constraint: PrincipalOrResourceConstraint::is_eq(euid),
988 }
989 }
990
991 pub fn is_eq_slot() -> Self {
993 Self {
994 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
995 }
996 }
997
998 pub fn is_in(euid: EntityUID) -> Self {
1000 PrincipalConstraint {
1001 constraint: PrincipalOrResourceConstraint::is_in(euid),
1002 }
1003 }
1004
1005 pub fn is_in_slot() -> Self {
1007 Self {
1008 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1009 }
1010 }
1011
1012 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1014 match self.constraint {
1015 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1016 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1017 },
1018 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1019 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1020 },
1021 _ => self,
1022 }
1023 }
1024}
1025
1026impl std::fmt::Display for PrincipalConstraint {
1027 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1028 write!(
1029 f,
1030 "{}",
1031 self.constraint.display(PrincipalOrResource::Principal)
1032 )
1033 }
1034}
1035
1036#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1038pub struct ResourceConstraint {
1039 pub(crate) constraint: PrincipalOrResourceConstraint,
1040}
1041
1042impl ResourceConstraint {
1043 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1045 ResourceConstraint { constraint }
1046 }
1047
1048 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1050 &self.constraint
1051 }
1052
1053 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1055 self.constraint
1056 }
1057
1058 pub fn as_expr(&self) -> Expr {
1060 self.constraint.as_expr(PrincipalOrResource::Resource)
1061 }
1062
1063 pub fn any() -> Self {
1065 ResourceConstraint {
1066 constraint: PrincipalOrResourceConstraint::any(),
1067 }
1068 }
1069
1070 pub fn is_eq(euid: EntityUID) -> Self {
1072 ResourceConstraint {
1073 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1074 }
1075 }
1076
1077 pub fn is_eq_slot() -> Self {
1079 Self {
1080 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1081 }
1082 }
1083
1084 pub fn is_in_slot() -> Self {
1086 Self {
1087 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1088 }
1089 }
1090
1091 pub fn is_in(euid: EntityUID) -> Self {
1093 ResourceConstraint {
1094 constraint: PrincipalOrResourceConstraint::is_in(euid),
1095 }
1096 }
1097
1098 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1100 match self.constraint {
1101 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1102 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1103 },
1104 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1105 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1106 },
1107 _ => self,
1108 }
1109 }
1110}
1111
1112impl std::fmt::Display for ResourceConstraint {
1113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1114 write!(
1115 f,
1116 "{}",
1117 self.as_inner().display(PrincipalOrResource::Resource)
1118 )
1119 }
1120}
1121
1122#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1124pub enum EntityReference {
1125 EUID(Arc<EntityUID>),
1127 Slot,
1129}
1130
1131impl EntityReference {
1132 pub fn euid(euid: EntityUID) -> Self {
1134 Self::EUID(Arc::new(euid))
1135 }
1136}
1137
1138#[derive(Debug, Clone, PartialEq, Error)]
1140pub enum UnexpectedSlotError {
1141 #[error("found a slot where none was expected")]
1143 Unnamed,
1144 #[error("found slot {0} where none was expected")]
1146 Named(SlotId),
1147}
1148
1149impl TryInto<Arc<EntityUID>> for EntityReference {
1150 type Error = UnexpectedSlotError;
1151
1152 fn try_into(self) -> Result<Arc<EntityUID>, Self::Error> {
1153 match self {
1154 EntityReference::EUID(euid) => Ok(euid),
1155 EntityReference::Slot => Err(Self::Error::Unnamed),
1156 }
1157 }
1158}
1159
1160impl From<EntityUID> for EntityReference {
1161 fn from(euid: EntityUID) -> Self {
1162 Self::EUID(Arc::new(euid))
1163 }
1164}
1165
1166impl EntityReference {
1167 pub fn into_expr(&self, name: SlotId) -> Expr {
1169 match self {
1170 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1171 EntityReference::Slot => Expr::slot(name),
1172 }
1173 }
1174}
1175
1176#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1178#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1179pub enum PrincipalOrResource {
1180 Principal,
1182 Resource,
1184}
1185
1186impl std::fmt::Display for PrincipalOrResource {
1187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1188 let v = Var::from(*self);
1189 write!(f, "{v}")
1190 }
1191}
1192
1193impl TryFrom<Var> for PrincipalOrResource {
1194 type Error = Var;
1195
1196 fn try_from(value: Var) -> Result<Self, Self::Error> {
1197 match value {
1198 Var::Principal => Ok(Self::Principal),
1199 Var::Action => Err(Var::Action),
1200 Var::Resource => Ok(Self::Resource),
1201 Var::Context => Err(Var::Context),
1202 }
1203 }
1204}
1205
1206#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1209pub enum PrincipalOrResourceConstraint {
1210 Any,
1212 In(EntityReference),
1214 Eq(EntityReference),
1216}
1217
1218impl PrincipalOrResourceConstraint {
1219 pub fn any() -> Self {
1221 PrincipalOrResourceConstraint::Any
1222 }
1223
1224 pub fn is_eq(euid: EntityUID) -> Self {
1226 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1227 }
1228
1229 pub fn is_eq_slot() -> Self {
1231 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1232 }
1233
1234 pub fn is_in_slot() -> Self {
1236 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1237 }
1238
1239 pub fn is_in(euid: EntityUID) -> Self {
1241 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1242 }
1243
1244 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1248 match self {
1249 PrincipalOrResourceConstraint::Any => Expr::val(true),
1250 PrincipalOrResourceConstraint::Eq(euid) => {
1251 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1252 }
1253 PrincipalOrResourceConstraint::In(euid) => {
1254 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1255 }
1256 }
1257 }
1258
1259 pub fn display(&self, v: PrincipalOrResource) -> String {
1263 match self {
1264 PrincipalOrResourceConstraint::In(euid) => {
1265 format!("{} in {}", v, euid.into_expr(v.into()))
1266 }
1267 PrincipalOrResourceConstraint::Eq(euid) => {
1268 format!("{} == {}", v, euid.into_expr(v.into()))
1269 }
1270 PrincipalOrResourceConstraint::Any => format!("{}", v),
1271 }
1272 }
1273
1274 pub fn iter_euids(&'_ self) -> impl Iterator<Item = &'_ EntityUID> {
1276 match self {
1277 PrincipalOrResourceConstraint::Any => EntityIterator::None,
1278 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => {
1279 EntityIterator::One(euid)
1280 }
1281 PrincipalOrResourceConstraint::In(EntityReference::Slot) => EntityIterator::None,
1282 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => {
1283 EntityIterator::One(euid)
1284 }
1285 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => EntityIterator::None,
1286 }
1287 }
1288}
1289
1290#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1293pub enum ActionConstraint {
1294 Any,
1296 In(Vec<Arc<EntityUID>>),
1298 Eq(Arc<EntityUID>),
1300}
1301
1302impl std::fmt::Display for ActionConstraint {
1303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1304 let render_euids =
1305 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1306 match self {
1307 ActionConstraint::Any => write!(f, "action"),
1308 ActionConstraint::In(euids) => {
1309 write!(f, "action in [{}]", render_euids(euids))
1310 }
1311 ActionConstraint::Eq(euid) => write!(f, "action == {}", euid),
1312 }
1313 }
1314}
1315
1316impl ActionConstraint {
1317 pub fn any() -> Self {
1319 ActionConstraint::Any
1320 }
1321
1322 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1324 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1325 }
1326
1327 pub fn is_eq(euid: EntityUID) -> Self {
1329 ActionConstraint::Eq(Arc::new(euid))
1330 }
1331
1332 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1333 Expr::set(euids.into_iter().map(Expr::val))
1334 }
1335
1336 pub fn as_expr(&self) -> Expr {
1338 match self {
1339 ActionConstraint::Any => Expr::val(true),
1340 ActionConstraint::In(euids) => Expr::is_in(
1341 Expr::var(Var::Action),
1342 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1343 ),
1344 ActionConstraint::Eq(euid) => {
1345 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1346 }
1347 }
1348 }
1349
1350 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1352 match self {
1353 ActionConstraint::Any => EntityIterator::None,
1354 ActionConstraint::In(euids) => {
1355 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1356 }
1357 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1358 }
1359 }
1360}
1361
1362impl std::fmt::Display for StaticPolicy {
1363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1364 for (k, v) in &self.0.annotations {
1365 writeln!(f, "@{}(\"{}\")", k, v.escape_debug())?
1366 }
1367 write!(
1368 f,
1369 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1370 self.effect(),
1371 self.principal_constraint(),
1372 self.action_constraint(),
1373 self.resource_constraint(),
1374 self.non_head_constraints()
1375 )
1376 }
1377}
1378
1379#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash)]
1381pub struct PolicyID(SmolStr);
1382
1383impl PolicyID {
1384 pub fn from_string(id: impl AsRef<str>) -> Self {
1386 Self(SmolStr::from(id.as_ref()))
1387 }
1388
1389 pub fn from_smolstr(id: SmolStr) -> Self {
1391 Self(id)
1392 }
1393}
1394
1395impl std::fmt::Display for PolicyID {
1396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1397 write!(f, "{}", self.0.escape_debug())
1398 }
1399}
1400
1401#[cfg(feature = "arbitrary")]
1402impl<'u> arbitrary::Arbitrary<'u> for PolicyID {
1403 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1404 let s: String = u.arbitrary()?;
1405 Ok(PolicyID::from_string(s))
1406 }
1407 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1408 <String as arbitrary::Arbitrary>::size_hint(depth)
1409 }
1410}
1411
1412#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, Clone, Copy)]
1414#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1415pub enum Effect {
1416 #[serde(rename = "permit")]
1418 Permit,
1419 #[serde(rename = "forbid")]
1421 Forbid,
1422}
1423
1424impl std::fmt::Display for Effect {
1425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1426 match self {
1427 Self::Permit => write!(f, "permit"),
1428 Self::Forbid => write!(f, "forbid"),
1429 }
1430 }
1431}
1432
1433enum EntityIterator<'a> {
1434 None,
1435 One(&'a EntityUID),
1436 Bunch(Vec<&'a EntityUID>),
1437}
1438
1439impl<'a> Iterator for EntityIterator<'a> {
1440 type Item = &'a EntityUID;
1441
1442 fn next(&mut self) -> Option<Self::Item> {
1443 match self {
1444 EntityIterator::None => None,
1445 EntityIterator::One(euid) => {
1446 let eptr = *euid;
1447 let mut ptr = EntityIterator::None;
1448 std::mem::swap(self, &mut ptr);
1449 Some(eptr)
1450 }
1451 EntityIterator::Bunch(v) => v.pop(),
1452 }
1453 }
1454}
1455
1456#[cfg(test)]
1457pub mod test_generators {
1458 use super::*;
1459
1460 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1461 let euid = EntityUID::with_eid("test");
1462 let v = vec![
1463 PrincipalOrResourceConstraint::any(),
1464 PrincipalOrResourceConstraint::is_eq(euid.clone()),
1465 PrincipalOrResourceConstraint::Eq(EntityReference::Slot),
1466 PrincipalOrResourceConstraint::is_in(euid),
1467 PrincipalOrResourceConstraint::In(EntityReference::Slot),
1468 ];
1469
1470 v.into_iter()
1471 }
1472
1473 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
1474 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
1475 }
1476
1477 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
1478 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
1479 }
1480
1481 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
1482 let euid: EntityUID = "Action::\"test\""
1483 .parse()
1484 .expect("Invalid action constraint euid");
1485 let v = vec![
1486 ActionConstraint::any(),
1487 ActionConstraint::is_eq(euid.clone()),
1488 ActionConstraint::is_in([euid.clone()]),
1489 ActionConstraint::is_in([euid.clone(), euid]),
1490 ];
1491
1492 v.into_iter()
1493 }
1494
1495 pub fn all_templates() -> impl Iterator<Item = Template> {
1496 let mut buf = vec![];
1497 let permit = PolicyID::from_string("permit");
1498 let forbid = PolicyID::from_string("forbid");
1499 for principal in all_principal_constraints() {
1500 for action in all_actions_constraints() {
1501 for resource in all_resource_constraints() {
1502 let permit = Template::new(
1503 permit.clone(),
1504 BTreeMap::new(),
1505 Effect::Permit,
1506 principal.clone(),
1507 action.clone(),
1508 resource.clone(),
1509 Expr::val(true),
1510 );
1511 let forbid = Template::new(
1512 forbid.clone(),
1513 BTreeMap::new(),
1514 Effect::Forbid,
1515 principal.clone(),
1516 action.clone(),
1517 resource.clone(),
1518 Expr::val(true),
1519 );
1520 buf.push(permit);
1521 buf.push(forbid);
1522 }
1523 }
1524 }
1525 buf.into_iter()
1526 }
1527}
1528
1529#[cfg(test)]
1530mod test {
1531 use std::collections::HashSet;
1532
1533 use super::{test_generators::*, *};
1534 use crate::ast::{entity, name, EntityUID};
1535
1536 #[test]
1537 fn literal_and_borrowed() {
1538 for template in all_templates() {
1539 let t = Arc::new(template);
1540 let env = t
1541 .slots()
1542 .map(|slotid| (*slotid, EntityUID::with_eid("eid")))
1543 .collect();
1544 let p =
1545 Template::link(t, PolicyID::from_string("id"), env).expect("Instantiation Failed");
1546
1547 let b_literal = BorrowedLiteralPolicy::from(&p);
1548 let src = serde_json::to_string(&b_literal).expect("ser error");
1549 let literal: LiteralPolicy = serde_json::from_str(&src).expect("de error");
1550
1551 assert_eq!(b_literal.template_id, &literal.template_id);
1552 assert_eq!(b_literal.link_id, literal.link_id.as_ref());
1553 assert_eq!(b_literal.values, &literal.values);
1554 }
1555 }
1556
1557 #[test]
1558 fn template_roundtrip() {
1559 for template in all_templates() {
1560 template.check_invariant();
1561 let json = serde_json::to_string(&template).expect("Serialization Failed");
1562 let t2 = serde_json::from_str::<Template>(&json).expect("Deserialization failed");
1563 t2.check_invariant();
1564 assert_eq!(template, t2);
1565 }
1566 }
1567
1568 #[test]
1569 fn test_template_rebuild() {
1570 for template in all_templates() {
1571 let id = template.id().clone();
1572 let effect = template.effect();
1573 let p = template.principal_constraint().clone();
1574 let a = template.action_constraint().clone();
1575 let r = template.resource_constraint().clone();
1576 let nhc = template.non_head_constraints().clone();
1577 let t2 = Template::new(id, BTreeMap::new(), effect, p, a, r, nhc);
1578 assert_eq!(template, t2);
1579 }
1580 }
1581
1582 #[test]
1583 fn test_inline_policy_rebuild() {
1584 for template in all_templates() {
1585 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
1586 let id = ip.id().clone();
1587 let e = ip.effect();
1588 let anno = ip
1589 .annotations()
1590 .map(|(k, v)| (k.clone(), v.clone()))
1591 .collect();
1592 let p = ip.principal_constraint().clone();
1593 let a = ip.action_constraint().clone();
1594 let r = ip.resource_constraint().clone();
1595 let nhc = ip.non_head_constraints().clone();
1596 let ip2 =
1597 StaticPolicy::new(id, anno, e, p, a, r, nhc).expect("Policy Creation Failed");
1598 assert_eq!(ip, ip2);
1599 let (t2, inst) = Template::link_static_policy(ip2);
1600 assert!(inst.is_static());
1601 assert_eq!(&template, t2.as_ref());
1602 }
1603 }
1604 }
1605
1606 #[test]
1607 fn ir_binding_too_many() {
1608 let tid = PolicyID::from_string("tid");
1609 let iid = PolicyID::from_string("iid");
1610 let t = Arc::new(Template::new(
1611 tid,
1612 BTreeMap::new(),
1613 Effect::Forbid,
1614 PrincipalConstraint::is_eq_slot(),
1615 ActionConstraint::Any,
1616 ResourceConstraint::any(),
1617 Expr::val(true),
1618 ));
1619 let mut m = HashMap::new();
1620 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
1621 match Template::link(t, iid, m) {
1622 Ok(_) => panic!("Should fail!"),
1623 Err(LinkingError::ArityError {
1624 unbound_values,
1625 extra_values,
1626 }) => {
1627 assert_eq!(unbound_values.len(), 1);
1628 assert!(unbound_values.contains(&SlotId::principal()));
1629 assert_eq!(extra_values.len(), 1);
1630 assert!(extra_values.contains(&SlotId::resource()));
1631 }
1632 Err(e) => panic!("Wrong error: {e}"),
1633 };
1634 }
1635
1636 #[test]
1637 fn ir_binding_too_few() {
1638 let tid = PolicyID::from_string("tid");
1639 let iid = PolicyID::from_string("iid");
1640 let t = Arc::new(Template::new(
1641 tid,
1642 BTreeMap::new(),
1643 Effect::Forbid,
1644 PrincipalConstraint::is_eq_slot(),
1645 ActionConstraint::Any,
1646 ResourceConstraint::is_in_slot(),
1647 Expr::val(true),
1648 ));
1649 match Template::link(t.clone(), iid.clone(), HashMap::new()) {
1650 Ok(_) => panic!("should have failed!"),
1651 Err(LinkingError::ArityError {
1652 unbound_values,
1653 extra_values,
1654 }) => {
1655 assert_eq!(unbound_values.len(), 2);
1656 assert_eq!(extra_values.len(), 0);
1657 }
1658 Err(e) => panic!("Wrong error: {e}"),
1659 };
1660 let mut m = HashMap::new();
1661 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
1662 match Template::link(t, iid, m) {
1663 Ok(_) => panic!("should have failed!"),
1664 Err(LinkingError::ArityError {
1665 unbound_values,
1666 extra_values,
1667 }) => {
1668 assert_eq!(unbound_values.len(), 1);
1669 assert!(unbound_values.contains(&SlotId::resource()));
1670 assert_eq!(extra_values.len(), 0);
1671 }
1672 Err(e) => panic!("Wrong error: {e}"),
1673 };
1674 }
1675
1676 #[test]
1677 fn ir_binding() {
1678 let tid = PolicyID::from_string("template");
1679 let iid = PolicyID::from_string("linked");
1680 let t = Arc::new(Template::new(
1681 tid,
1682 BTreeMap::new(),
1683 Effect::Permit,
1684 PrincipalConstraint::is_in_slot(),
1685 ActionConstraint::any(),
1686 ResourceConstraint::is_eq_slot(),
1687 Expr::val(true),
1688 ));
1689
1690 let mut m = HashMap::new();
1691 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
1692 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
1693
1694 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
1695 assert_eq!(r.id(), &iid);
1696 assert_eq!(
1697 r.env().get(&SlotId::principal()),
1698 Some(&EntityUID::with_eid("theprincipal"))
1699 );
1700 assert_eq!(
1701 r.env().get(&SlotId::resource()),
1702 Some(&EntityUID::with_eid("theresource"))
1703 );
1704 }
1705
1706 #[test]
1707 fn isnt_template_implies_from_succeeds() {
1708 for template in all_templates() {
1709 if template.slots().count() == 0 {
1710 StaticPolicy::try_from(template).expect("Should succeed");
1711 }
1712 }
1713 }
1714
1715 #[test]
1716 fn is_template_implies_from_fails() {
1717 for template in all_templates() {
1718 if template.slots().count() != 0 {
1719 assert!(
1720 StaticPolicy::try_from(template.clone()).is_err(),
1721 "Following template did convert {template}"
1722 );
1723 }
1724 }
1725 }
1726
1727 #[test]
1728 fn non_template_iso() {
1729 for template in all_templates() {
1730 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
1731 let (t2, _) = Template::link_static_policy(p);
1732 assert_eq!(&template, t2.as_ref());
1733 }
1734 }
1735 }
1736
1737 #[test]
1738 fn template_into_expr() {
1739 for template in all_templates() {
1740 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
1741 let t: Template = template;
1742 assert_eq!(p.condition(), t.condition());
1743 assert_eq!(p.effect(), t.effect());
1744 }
1745 }
1746 }
1747
1748 #[test]
1749 fn template_error_msgs_have_names() {
1750 for template in all_templates() {
1751 if let Err(e) = StaticPolicy::try_from(template) {
1752 match e {
1753 super::UnexpectedSlotError::Unnamed => panic!("Didn't get a name!"),
1754 super::UnexpectedSlotError::Named(_) => (),
1755 }
1756 }
1757 }
1758 }
1759
1760 #[test]
1761 fn template_por_iter() {
1762 let e = Arc::new(EntityUID::with_eid("eid"));
1763 assert_eq!(PrincipalOrResourceConstraint::Any.iter_euids().count(), 0);
1764 assert_eq!(
1765 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone()))
1766 .iter_euids()
1767 .count(),
1768 1
1769 );
1770 assert_eq!(
1771 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1772 .iter_euids()
1773 .count(),
1774 0
1775 );
1776 assert_eq!(
1777 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e))
1778 .iter_euids()
1779 .count(),
1780 1
1781 );
1782 assert_eq!(
1783 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1784 .iter_euids()
1785 .count(),
1786 0
1787 );
1788 }
1789
1790 #[test]
1791 fn action_iter() {
1792 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
1793 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
1794 let v = a.iter_euids().collect::<Vec<_>>();
1795 assert_eq!(vec![&EntityUID::with_eid("test")], v);
1796 let a =
1797 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
1798 let set = a.iter_euids().collect::<HashSet<_>>();
1799 let e1 = EntityUID::with_eid("test1");
1800 let e2 = EntityUID::with_eid("test2");
1801 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
1802 assert_eq!(set, correct);
1803 }
1804
1805 #[test]
1806 fn test_iter_none() {
1807 let mut i = EntityIterator::None;
1808 assert_eq!(i.next(), None);
1809 }
1810
1811 #[test]
1812 fn test_iter_once() {
1813 let id = EntityUID::from_components(
1814 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1815 entity::Eid::new("eid"),
1816 );
1817 let mut i = EntityIterator::One(&id);
1818 assert_eq!(i.next(), Some(&id));
1819 assert_eq!(i.next(), None);
1820 }
1821
1822 #[test]
1823 fn test_iter_mult() {
1824 let id1 = EntityUID::from_components(
1825 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1826 entity::Eid::new("eid1"),
1827 );
1828 let id2 = EntityUID::from_components(
1829 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1830 entity::Eid::new("eid2"),
1831 );
1832 let v = vec![&id1, &id2];
1833 let mut i = EntityIterator::Bunch(v);
1834 assert_eq!(i.next(), Some(&id2));
1835 assert_eq!(i.next(), Some(&id1));
1836 assert_eq!(i.next(), None)
1837 }
1838
1839 #[test]
1840 fn euid_into_expr() {
1841 let e = EntityReference::Slot;
1842 assert_eq!(
1843 e.into_expr(SlotId::principal()),
1844 Expr::slot(SlotId::principal())
1845 );
1846 let e = EntityReference::euid(EntityUID::with_eid("eid"));
1847 assert_eq!(
1848 e.into_expr(SlotId::principal()),
1849 Expr::val(EntityUID::with_eid("eid"))
1850 );
1851 }
1852
1853 #[test]
1854 fn por_constraint_display() {
1855 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot);
1856 let s = t.display(PrincipalOrResource::Principal);
1857 assert_eq!(s, "principal == ?principal");
1858 let t =
1859 PrincipalOrResourceConstraint::Eq(EntityReference::euid(EntityUID::with_eid("test")));
1860 let s = t.display(PrincipalOrResource::Principal);
1861 assert_eq!(s, "principal == test_entity_type::\"test\"");
1862 }
1863}