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)]
30#[serde(from = "TemplateBody")]
31#[serde(into = "TemplateBody")]
32pub struct Template {
33 body: TemplateBody,
34 slots: Vec<SlotId>,
39}
40
41impl From<Template> for TemplateBody {
42 fn from(val: Template) -> Self {
43 val.body
44 }
45}
46
47impl Template {
48 #[cfg(test)]
50 pub fn check_invariant(&self) {
51 let cond = self.body.condition();
52 let slots = cond.slots().collect::<Vec<_>>();
53 for slot in slots.iter() {
54 assert!(self.slots.contains(slot));
55 }
56 for slot in self.slots() {
57 assert!(slots.contains(&slot));
58 }
59 }
60 pub fn new(
68 id: PolicyID,
69 annotations: BTreeMap<Id, SmolStr>,
70 effect: Effect,
71 principal_constraint: PrincipalConstraint,
72 action_constraint: ActionConstraint,
73 resource_constraint: ResourceConstraint,
74 non_head_constraint: Expr,
75 ) -> Self {
76 let body = TemplateBody::new(
77 id,
78 annotations,
79 effect,
80 principal_constraint,
81 action_constraint,
82 resource_constraint,
83 non_head_constraint,
84 );
85 Template::from(body)
88 }
89
90 pub fn principal_constraint(&self) -> &PrincipalConstraint {
92 self.body.principal_constraint()
93 }
94
95 pub fn action_constraint(&self) -> &ActionConstraint {
97 self.body.action_constraint()
98 }
99
100 pub fn resource_constraint(&self) -> &ResourceConstraint {
102 self.body.resource_constraint()
103 }
104
105 pub fn non_head_constraints(&self) -> &Expr {
107 self.body.non_head_constraints()
108 }
109
110 pub fn id(&self) -> &PolicyID {
112 self.body.id()
113 }
114
115 pub fn new_id(&self, id: PolicyID) -> Self {
117 Template {
118 body: self.body.new_id(id),
119 slots: self.slots.clone(),
120 }
121 }
122
123 pub fn effect(&self) -> Effect {
125 self.body.effect()
126 }
127
128 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
130 self.body.annotation(key)
131 }
132
133 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
135 self.body.annotations()
136 }
137
138 pub fn condition(&self) -> Expr {
144 self.body.condition()
145 }
146
147 pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
149 self.slots.iter()
150 }
151
152 pub fn is_static(&self) -> bool {
157 self.slots.is_empty()
158 }
159
160 pub fn check_binding(
164 template: &Template,
165 values: &HashMap<SlotId, EntityUID>,
166 ) -> Result<(), LinkingError> {
167 let unbound = template
169 .slots
170 .iter()
171 .filter(|slot| !values.contains_key(slot))
172 .collect::<Vec<_>>();
173
174 let extra = values
175 .iter()
176 .filter_map(|(slot, _)| {
177 if !template.slots.contains(slot) {
178 Some(slot)
179 } else {
180 None
181 }
182 })
183 .collect::<Vec<_>>();
184
185 if unbound.is_empty() && extra.is_empty() {
186 Ok(())
187 } else {
188 Err(LinkingError::from_unbound_and_extras(
189 unbound.into_iter().copied(),
190 extra.into_iter().copied(),
191 ))
192 }
193 }
194
195 pub fn link(
199 template: Arc<Template>,
200 new_id: PolicyID,
201 values: HashMap<SlotId, EntityUID>,
202 ) -> Result<Policy, LinkingError> {
203 Template::check_binding(&template, &values)
205 .map(|_| Policy::new(template, Some(new_id), values))
206 }
207
208 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
211 let body: TemplateBody = p.into();
212 let t = Arc::new(Self {
216 body,
217 slots: vec![],
218 });
219 #[cfg(test)]
220 {
221 t.check_invariant();
222 }
223 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
229 (t, p)
230 }
231}
232
233impl From<TemplateBody> for Template {
234 fn from(body: TemplateBody) -> Self {
235 let slots = body.condition().slots().copied().collect::<Vec<_>>();
238 Self { body, slots }
239 }
240}
241
242impl std::fmt::Display for Template {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 write!(f, "{}", self.body)
245 }
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Error)]
250pub enum LinkingError {
251 #[error("{}", describe_arity_error(.unbound_values, .extra_values))]
254 ArityError {
255 unbound_values: Vec<SlotId>,
257 extra_values: Vec<SlotId>,
259 },
260
261 #[error("failed to find a template with id: {0}")]
263 NoSuchTemplate(PolicyID),
264
265 #[error("template-linked policy id conflicts with an existing policy id")]
267 PolicyIdConflict,
268}
269
270impl LinkingError {
271 fn from_unbound_and_extras<T>(unbound: T, extra: T) -> Self
272 where
273 T: Iterator<Item = SlotId>,
274 {
275 Self::ArityError {
276 unbound_values: unbound.collect(),
277 extra_values: extra.collect(),
278 }
279 }
280}
281
282fn describe_arity_error(unbound_values: &[SlotId], extra_values: &[SlotId]) -> String {
283 match (unbound_values.len(), extra_values.len()) {
284 #[allow(clippy::unreachable)]
286 (0,0) => unreachable!(),
287 (_unbound, 0) => format!("the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
288 (0, _extra) => format!("the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
289 (_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(","))
290 }
291}
292
293#[derive(Debug, Clone, Eq, PartialEq)]
301pub struct Policy {
302 template: Arc<Template>,
304 link: Option<PolicyID>,
307 values: HashMap<SlotId, EntityUID>,
313}
314
315impl Policy {
316 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
320 #[cfg(test)]
321 {
322 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
323 }
324 Self {
330 template,
331 link: link_id,
332 values,
333 }
334 }
335
336 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID) -> Self {
338 let t = Template::new(
339 id,
340 BTreeMap::new(),
341 effect,
342 PrincipalConstraint::any(),
343 ActionConstraint::any(),
344 ResourceConstraint::any(),
345 when,
346 );
347 Self::new(Arc::new(t), None, SlotEnv::new())
348 }
349
350 pub fn template(&self) -> &Template {
352 &self.template
353 }
354
355 pub(crate) fn template_arc(&self) -> Arc<Template> {
357 Arc::clone(&self.template)
358 }
359
360 pub fn effect(&self) -> Effect {
362 self.template.effect()
363 }
364
365 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
367 self.template.annotation(key)
368 }
369
370 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
372 self.template.annotations()
373 }
374
375 pub fn principal_constraint(&self) -> PrincipalConstraint {
381 let constraint = self.template.principal_constraint().clone();
382 match self.values.get(&SlotId::principal()) {
383 None => constraint,
384 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
385 }
386 }
387
388 pub fn action_constraint(&self) -> &ActionConstraint {
390 self.template.action_constraint()
391 }
392
393 pub fn resource_constraint(&self) -> ResourceConstraint {
399 let constraint = self.template.resource_constraint().clone();
400 match self.values.get(&SlotId::resource()) {
401 None => constraint,
402 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
403 }
404 }
405
406 pub fn non_head_constraints(&self) -> &Expr {
408 self.template.non_head_constraints()
409 }
410
411 pub fn condition(&self) -> Expr {
413 self.template.condition()
414 }
415
416 pub fn env(&self) -> &SlotEnv {
419 &self.values
420 }
421
422 pub fn id(&self) -> &PolicyID {
424 self.link.as_ref().unwrap_or_else(|| self.template.id())
425 }
426
427 pub fn new_id(&self, id: PolicyID) -> Self {
429 match self.link {
430 None => Policy {
431 template: Arc::new(self.template.new_id(id)),
432 link: None,
433 values: self.values.clone(),
434 },
435 Some(_) => Policy {
436 template: self.template.clone(),
437 link: Some(id),
438 values: self.values.clone(),
439 },
440 }
441 }
442
443 pub fn is_static(&self) -> bool {
445 self.link.is_none()
446 }
447}
448
449impl std::fmt::Display for Policy {
450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451 if self.is_static() {
452 write!(f, "{}", self.template())
453 } else {
454 write!(
455 f,
456 "Template Instance of {}, slots: [{}]",
457 self.template().id(),
458 display_slot_env(self.env())
459 )
460 }
461 }
462}
463
464pub type SlotEnv = HashMap<SlotId, EntityUID>;
466
467#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
470pub struct LiteralPolicy {
471 template_id: PolicyID,
473 link_id: Option<PolicyID>,
477 values: SlotEnv,
479}
480
481#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
483pub struct BorrowedLiteralPolicy<'a> {
484 template_id: &'a PolicyID,
486 link_id: Option<&'a PolicyID>,
490 values: &'a SlotEnv,
492}
493
494impl<'a> From<&'a Policy> for BorrowedLiteralPolicy<'a> {
495 fn from(p: &'a Policy) -> Self {
496 Self {
497 template_id: p.template.id(),
498 link_id: p.link.as_ref(),
499 values: &p.values,
500 }
501 }
502}
503
504impl std::hash::Hash for LiteralPolicy {
507 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
508 self.template_id.hash(state);
509 let mut buf = self.values.iter().collect::<Vec<_>>();
511 buf.sort();
512 for (id, euid) in buf {
513 id.hash(state);
514 euid.hash(state);
515 }
516 }
517}
518
519impl std::cmp::PartialEq for LiteralPolicy {
520 fn eq(&self, other: &Self) -> bool {
521 self.template_id() == other.template_id()
522 && self.link_id == other.link_id
523 && self.values == other.values
524 }
525}
526
527#[cfg(test)]
529mod hashing_tests {
530 use std::{
531 collections::hash_map::DefaultHasher,
532 hash::{Hash, Hasher},
533 };
534
535 use super::*;
536
537 fn compute_hash(ir: LiteralPolicy) -> u64 {
538 let mut s = DefaultHasher::new();
539 ir.hash(&mut s);
540 s.finish()
541 }
542
543 fn build_template_linked_policy() -> LiteralPolicy {
544 let mut map = HashMap::new();
545 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
546 LiteralPolicy {
547 template_id: PolicyID::from_string("template"),
548 link_id: Some(PolicyID::from_string("id")),
549 values: map,
550 }
551 }
552
553 #[test]
554 fn hash_property_instances() {
555 let a = build_template_linked_policy();
556 let b = build_template_linked_policy();
557 assert_eq!(a, b);
558 assert_eq!(compute_hash(a), compute_hash(b));
559 }
560}
561#[derive(Debug, Error)]
569pub enum ReificationError {
570 #[error("The PolicyID linked to does not exist")]
572 NoSuchTemplate(PolicyID),
573 #[error("{0}")]
575 Instantiation(#[from] LinkingError),
576}
577
578impl LiteralPolicy {
579 pub fn reify(
584 self,
585 templates: &HashMap<PolicyID, Arc<Template>>,
586 ) -> Result<Policy, ReificationError> {
587 let template = templates
588 .get(&self.template_id)
589 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
590 Template::check_binding(template, &self.values).map_err(ReificationError::Instantiation)?;
592 Ok(Policy::new(template.clone(), self.link_id, self.values))
593 }
594
595 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
597 self.values.get(id)
598 }
599
600 pub fn id(&self) -> &PolicyID {
603 self.link_id.as_ref().unwrap_or(&self.template_id)
604 }
605
606 pub fn template_id(&self) -> &PolicyID {
608 &self.template_id
609 }
610
611 pub fn is_static(&self) -> bool {
613 self.link_id.is_none()
614 }
615}
616
617fn display_slot_env(env: &SlotEnv) -> String {
618 env.iter()
619 .map(|(slot, value)| format!("{slot} -> {value}"))
620 .join(",")
621}
622
623impl std::fmt::Display for LiteralPolicy {
624 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625 if self.is_static() {
626 write!(f, "Static policy w/ ID {}", self.template_id())
627 } else {
628 write!(
629 f,
630 "Template linked policy of {}, slots: [{}]",
631 self.template_id(),
632 display_slot_env(&self.values),
633 )
634 }
635 }
636}
637
638impl From<Policy> for LiteralPolicy {
639 fn from(p: Policy) -> Self {
640 Self {
641 template_id: p.template.id().clone(),
642 link_id: p.link,
643 values: p.values,
644 }
645 }
646}
647
648#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
652pub struct StaticPolicy(TemplateBody);
653
654impl StaticPolicy {
655 pub fn id(&self) -> &PolicyID {
657 self.0.id()
658 }
659
660 pub fn new_id(&mut self, id: PolicyID) -> Self {
662 StaticPolicy(self.0.new_id(id))
663 }
664
665 pub fn effect(&self) -> Effect {
667 self.0.effect()
668 }
669
670 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
672 self.0.annotation(key)
673 }
674
675 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
677 self.0.annotations()
678 }
679
680 pub fn principal_constraint(&self) -> &PrincipalConstraint {
682 self.0.principal_constraint()
683 }
684
685 pub fn principal_constraint_expr(&self) -> Expr {
689 self.0.principal_constraint_expr()
690 }
691
692 pub fn action_constraint(&self) -> &ActionConstraint {
694 self.0.action_constraint()
695 }
696
697 pub fn action_constraint_expr(&self) -> Expr {
701 self.0.action_constraint_expr()
702 }
703
704 pub fn resource_constraint(&self) -> &ResourceConstraint {
706 self.0.resource_constraint()
707 }
708
709 pub fn resource_constraint_expr(&self) -> Expr {
713 self.0.resource_constraint_expr()
714 }
715
716 pub fn non_head_constraints(&self) -> &Expr {
721 self.0.non_head_constraints()
722 }
723
724 pub fn condition(&self) -> Expr {
730 self.0.condition()
731 }
732
733 pub fn new(
735 id: PolicyID,
736 annotations: BTreeMap<Id, SmolStr>,
737 effect: Effect,
738 principal_constraint: PrincipalConstraint,
739 action_constraint: ActionConstraint,
740 resource_constraint: ResourceConstraint,
741 non_head_constraints: Expr,
742 ) -> Result<Self, UnexpectedSlotError> {
743 let body = TemplateBody::new(
744 id,
745 annotations,
746 effect,
747 principal_constraint,
748 action_constraint,
749 resource_constraint,
750 non_head_constraints,
751 );
752 let num_slots = body.condition().slots().next().copied();
753 match num_slots {
755 Some(slot_id) => Err(UnexpectedSlotError::Named(slot_id))?,
756 None => Ok(Self(body)),
757 }
758 }
759}
760
761impl TryFrom<Template> for StaticPolicy {
762 type Error = UnexpectedSlotError;
763
764 fn try_from(value: Template) -> Result<Self, Self::Error> {
765 let o = value.slots().next().copied();
767 match o {
768 Some(slot_id) => Err(Self::Error::Named(slot_id)),
769 None => Ok(Self(value.body)),
770 }
771 }
772}
773
774impl From<StaticPolicy> for Policy {
775 fn from(inline: StaticPolicy) -> Policy {
776 let (_, policy) = Template::link_static_policy(inline);
777 policy
778 }
779}
780
781impl From<StaticPolicy> for Arc<Template> {
782 fn from(p: StaticPolicy) -> Self {
783 let (t, _) = Template::link_static_policy(p);
784 t
785 }
786}
787
788#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
793pub struct TemplateBody {
794 id: PolicyID,
796 annotations: BTreeMap<Id, SmolStr>,
798 effect: Effect,
800 principal_constraint: PrincipalConstraint,
804 action_constraint: ActionConstraint,
808 resource_constraint: ResourceConstraint,
812 non_head_constraints: Expr,
817}
818
819impl TemplateBody {
820 pub fn id(&self) -> &PolicyID {
822 &self.id
823 }
824
825 pub fn new_id(&self, id: PolicyID) -> Self {
827 let mut new = self.clone();
828 new.id = id;
829 new
830 }
831
832 pub fn effect(&self) -> Effect {
834 self.effect
835 }
836
837 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
839 self.annotations.get(key)
840 }
841
842 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
844 self.annotations.iter()
845 }
846
847 pub fn principal_constraint(&self) -> &PrincipalConstraint {
849 &self.principal_constraint
850 }
851
852 pub fn principal_constraint_expr(&self) -> Expr {
856 self.principal_constraint.as_expr()
857 }
858
859 pub fn action_constraint(&self) -> &ActionConstraint {
861 &self.action_constraint
862 }
863
864 pub fn action_constraint_expr(&self) -> Expr {
868 self.action_constraint.as_expr()
869 }
870
871 pub fn resource_constraint(&self) -> &ResourceConstraint {
873 &self.resource_constraint
874 }
875
876 pub fn resource_constraint_expr(&self) -> Expr {
880 self.resource_constraint.as_expr()
881 }
882
883 pub fn non_head_constraints(&self) -> &Expr {
888 &self.non_head_constraints
889 }
890
891 pub fn condition(&self) -> Expr {
897 Expr::and(
898 Expr::and(
899 Expr::and(
900 self.principal_constraint_expr(),
901 self.action_constraint_expr(),
902 ),
903 self.resource_constraint_expr(),
904 ),
905 self.non_head_constraints.clone(),
906 )
907 }
908
909 pub fn new(
911 id: PolicyID,
912 annotations: BTreeMap<Id, SmolStr>,
913 effect: Effect,
914 principal_constraint: PrincipalConstraint,
915 action_constraint: ActionConstraint,
916 resource_constraint: ResourceConstraint,
917 non_head_constraints: Expr,
918 ) -> Self {
919 Self {
920 id,
921 annotations,
922 effect,
923 principal_constraint,
924 action_constraint,
925 resource_constraint,
926 non_head_constraints,
927 }
928 }
929}
930
931impl From<StaticPolicy> for TemplateBody {
932 fn from(p: StaticPolicy) -> Self {
933 p.0
934 }
935}
936
937impl std::fmt::Display for TemplateBody {
938 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
939 for (k, v) in &self.annotations {
940 writeln!(f, "@{}(\"{}\")", k, v.escape_debug())?
941 }
942 write!(
943 f,
944 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
945 self.effect(),
946 self.principal_constraint(),
947 self.action_constraint(),
948 self.resource_constraint(),
949 self.non_head_constraints()
950 )
951 }
952}
953
954#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
956pub struct PrincipalConstraint {
957 pub(crate) constraint: PrincipalOrResourceConstraint,
958}
959
960impl PrincipalConstraint {
961 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
963 PrincipalConstraint { constraint }
964 }
965
966 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
968 &self.constraint
969 }
970
971 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
973 self.constraint
974 }
975
976 pub fn as_expr(&self) -> Expr {
978 self.constraint.as_expr(PrincipalOrResource::Principal)
979 }
980
981 pub fn any() -> Self {
983 PrincipalConstraint {
984 constraint: PrincipalOrResourceConstraint::any(),
985 }
986 }
987
988 pub fn is_eq(euid: EntityUID) -> Self {
990 PrincipalConstraint {
991 constraint: PrincipalOrResourceConstraint::is_eq(euid),
992 }
993 }
994
995 pub fn is_eq_slot() -> Self {
997 Self {
998 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
999 }
1000 }
1001
1002 pub fn is_in(euid: EntityUID) -> Self {
1004 PrincipalConstraint {
1005 constraint: PrincipalOrResourceConstraint::is_in(euid),
1006 }
1007 }
1008
1009 pub fn is_in_slot() -> Self {
1011 Self {
1012 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1013 }
1014 }
1015
1016 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1018 match self.constraint {
1019 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1020 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1021 },
1022 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1023 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1024 },
1025 _ => self,
1026 }
1027 }
1028}
1029
1030impl std::fmt::Display for PrincipalConstraint {
1031 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1032 write!(
1033 f,
1034 "{}",
1035 self.constraint.display(PrincipalOrResource::Principal)
1036 )
1037 }
1038}
1039
1040#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1042pub struct ResourceConstraint {
1043 pub(crate) constraint: PrincipalOrResourceConstraint,
1044}
1045
1046impl ResourceConstraint {
1047 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1049 ResourceConstraint { constraint }
1050 }
1051
1052 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1054 &self.constraint
1055 }
1056
1057 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1059 self.constraint
1060 }
1061
1062 pub fn as_expr(&self) -> Expr {
1064 self.constraint.as_expr(PrincipalOrResource::Resource)
1065 }
1066
1067 pub fn any() -> Self {
1069 ResourceConstraint {
1070 constraint: PrincipalOrResourceConstraint::any(),
1071 }
1072 }
1073
1074 pub fn is_eq(euid: EntityUID) -> Self {
1076 ResourceConstraint {
1077 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1078 }
1079 }
1080
1081 pub fn is_eq_slot() -> Self {
1083 Self {
1084 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1085 }
1086 }
1087
1088 pub fn is_in_slot() -> Self {
1090 Self {
1091 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1092 }
1093 }
1094
1095 pub fn is_in(euid: EntityUID) -> Self {
1097 ResourceConstraint {
1098 constraint: PrincipalOrResourceConstraint::is_in(euid),
1099 }
1100 }
1101
1102 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1104 match self.constraint {
1105 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1106 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1107 },
1108 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1109 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1110 },
1111 _ => self,
1112 }
1113 }
1114}
1115
1116impl std::fmt::Display for ResourceConstraint {
1117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1118 write!(
1119 f,
1120 "{}",
1121 self.as_inner().display(PrincipalOrResource::Resource)
1122 )
1123 }
1124}
1125
1126#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1128pub enum EntityReference {
1129 EUID(Arc<EntityUID>),
1131 Slot,
1133}
1134
1135impl EntityReference {
1136 pub fn euid(euid: EntityUID) -> Self {
1138 Self::EUID(Arc::new(euid))
1139 }
1140}
1141
1142#[derive(Debug, Clone, PartialEq, Error)]
1144pub enum UnexpectedSlotError {
1145 #[error("found a slot where none was expected")]
1147 Unnamed,
1148 #[error("found slot {0} where none was expected")]
1150 Named(SlotId),
1151}
1152
1153impl TryInto<Arc<EntityUID>> for EntityReference {
1154 type Error = UnexpectedSlotError;
1155
1156 fn try_into(self) -> Result<Arc<EntityUID>, Self::Error> {
1157 match self {
1158 EntityReference::EUID(euid) => Ok(euid),
1159 EntityReference::Slot => Err(Self::Error::Unnamed),
1160 }
1161 }
1162}
1163
1164impl From<EntityUID> for EntityReference {
1165 fn from(euid: EntityUID) -> Self {
1166 Self::EUID(Arc::new(euid))
1167 }
1168}
1169
1170impl EntityReference {
1171 pub fn into_expr(&self, name: SlotId) -> Expr {
1173 match self {
1174 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1175 EntityReference::Slot => Expr::slot(name),
1176 }
1177 }
1178}
1179
1180#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1182#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1183pub enum PrincipalOrResource {
1184 Principal,
1186 Resource,
1188}
1189
1190impl std::fmt::Display for PrincipalOrResource {
1191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1192 let v = Var::from(*self);
1193 write!(f, "{v}")
1194 }
1195}
1196
1197impl TryFrom<Var> for PrincipalOrResource {
1198 type Error = Var;
1199
1200 fn try_from(value: Var) -> Result<Self, Self::Error> {
1201 match value {
1202 Var::Principal => Ok(Self::Principal),
1203 Var::Action => Err(Var::Action),
1204 Var::Resource => Ok(Self::Resource),
1205 Var::Context => Err(Var::Context),
1206 }
1207 }
1208}
1209
1210#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1213pub enum PrincipalOrResourceConstraint {
1214 Any,
1216 In(EntityReference),
1218 Eq(EntityReference),
1220}
1221
1222impl PrincipalOrResourceConstraint {
1223 pub fn any() -> Self {
1225 PrincipalOrResourceConstraint::Any
1226 }
1227
1228 pub fn is_eq(euid: EntityUID) -> Self {
1230 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1231 }
1232
1233 pub fn is_eq_slot() -> Self {
1235 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1236 }
1237
1238 pub fn is_in_slot() -> Self {
1240 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1241 }
1242
1243 pub fn is_in(euid: EntityUID) -> Self {
1245 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1246 }
1247
1248 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1252 match self {
1253 PrincipalOrResourceConstraint::Any => Expr::val(true),
1254 PrincipalOrResourceConstraint::Eq(euid) => {
1255 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1256 }
1257 PrincipalOrResourceConstraint::In(euid) => {
1258 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1259 }
1260 }
1261 }
1262
1263 pub fn display(&self, v: PrincipalOrResource) -> String {
1267 match self {
1268 PrincipalOrResourceConstraint::In(euid) => {
1269 format!("{} in {}", v, euid.into_expr(v.into()))
1270 }
1271 PrincipalOrResourceConstraint::Eq(euid) => {
1272 format!("{} == {}", v, euid.into_expr(v.into()))
1273 }
1274 PrincipalOrResourceConstraint::Any => format!("{}", v),
1275 }
1276 }
1277
1278 pub fn iter_euids(&'_ self) -> impl Iterator<Item = &'_ EntityUID> {
1280 match self {
1281 PrincipalOrResourceConstraint::Any => EntityIterator::None,
1282 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => {
1283 EntityIterator::One(euid)
1284 }
1285 PrincipalOrResourceConstraint::In(EntityReference::Slot) => EntityIterator::None,
1286 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => {
1287 EntityIterator::One(euid)
1288 }
1289 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => EntityIterator::None,
1290 }
1291 }
1292}
1293
1294#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1297pub enum ActionConstraint {
1298 Any,
1300 In(Vec<Arc<EntityUID>>),
1302 Eq(Arc<EntityUID>),
1304}
1305
1306impl std::fmt::Display for ActionConstraint {
1307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1308 let render_euids =
1309 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1310 match self {
1311 ActionConstraint::Any => write!(f, "action"),
1312 ActionConstraint::In(euids) => {
1313 write!(f, "action in [{}]", render_euids(euids))
1314 }
1315 ActionConstraint::Eq(euid) => write!(f, "action == {}", euid),
1316 }
1317 }
1318}
1319
1320impl ActionConstraint {
1321 pub fn any() -> Self {
1323 ActionConstraint::Any
1324 }
1325
1326 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1328 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1329 }
1330
1331 pub fn is_eq(euid: EntityUID) -> Self {
1333 ActionConstraint::Eq(Arc::new(euid))
1334 }
1335
1336 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1337 Expr::set(euids.into_iter().map(Expr::val))
1338 }
1339
1340 pub fn as_expr(&self) -> Expr {
1342 match self {
1343 ActionConstraint::Any => Expr::val(true),
1344 ActionConstraint::In(euids) => Expr::is_in(
1345 Expr::var(Var::Action),
1346 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1347 ),
1348 ActionConstraint::Eq(euid) => {
1349 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1350 }
1351 }
1352 }
1353
1354 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1356 match self {
1357 ActionConstraint::Any => EntityIterator::None,
1358 ActionConstraint::In(euids) => {
1359 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1360 }
1361 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1362 }
1363 }
1364}
1365
1366impl std::fmt::Display for StaticPolicy {
1367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1368 for (k, v) in &self.0.annotations {
1369 writeln!(f, "@{}(\"{}\")", k, v.escape_debug())?
1370 }
1371 write!(
1372 f,
1373 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1374 self.effect(),
1375 self.principal_constraint(),
1376 self.action_constraint(),
1377 self.resource_constraint(),
1378 self.non_head_constraints()
1379 )
1380 }
1381}
1382
1383#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash)]
1385pub struct PolicyID(SmolStr);
1386
1387impl PolicyID {
1388 pub fn from_string(id: impl AsRef<str>) -> Self {
1390 Self(SmolStr::from(id.as_ref()))
1391 }
1392
1393 pub fn from_smolstr(id: SmolStr) -> Self {
1395 Self(id)
1396 }
1397}
1398
1399impl std::fmt::Display for PolicyID {
1400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1401 write!(f, "{}", self.0.escape_debug())
1402 }
1403}
1404
1405#[cfg(feature = "arbitrary")]
1406impl<'u> arbitrary::Arbitrary<'u> for PolicyID {
1407 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1408 let s: String = u.arbitrary()?;
1409 Ok(PolicyID::from_string(s))
1410 }
1411 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1412 <String as arbitrary::Arbitrary>::size_hint(depth)
1413 }
1414}
1415
1416#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, Clone, Copy)]
1418#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1419pub enum Effect {
1420 #[serde(rename = "permit")]
1422 Permit,
1423 #[serde(rename = "forbid")]
1425 Forbid,
1426}
1427
1428impl std::fmt::Display for Effect {
1429 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1430 match self {
1431 Self::Permit => write!(f, "permit"),
1432 Self::Forbid => write!(f, "forbid"),
1433 }
1434 }
1435}
1436
1437enum EntityIterator<'a> {
1438 None,
1439 One(&'a EntityUID),
1440 Bunch(Vec<&'a EntityUID>),
1441}
1442
1443impl<'a> Iterator for EntityIterator<'a> {
1444 type Item = &'a EntityUID;
1445
1446 fn next(&mut self) -> Option<Self::Item> {
1447 match self {
1448 EntityIterator::None => None,
1449 EntityIterator::One(euid) => {
1450 let eptr = *euid;
1451 let mut ptr = EntityIterator::None;
1452 std::mem::swap(self, &mut ptr);
1453 Some(eptr)
1454 }
1455 EntityIterator::Bunch(v) => v.pop(),
1456 }
1457 }
1458}
1459
1460#[cfg(test)]
1461pub mod test_generators {
1462 use super::*;
1463
1464 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1465 let euid = EntityUID::with_eid("test");
1466 let v = vec![
1467 PrincipalOrResourceConstraint::any(),
1468 PrincipalOrResourceConstraint::is_eq(euid.clone()),
1469 PrincipalOrResourceConstraint::Eq(EntityReference::Slot),
1470 PrincipalOrResourceConstraint::is_in(euid),
1471 PrincipalOrResourceConstraint::In(EntityReference::Slot),
1472 ];
1473
1474 v.into_iter()
1475 }
1476
1477 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
1478 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
1479 }
1480
1481 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
1482 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
1483 }
1484
1485 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
1486 let euid: EntityUID = "Action::\"test\""
1487 .parse()
1488 .expect("Invalid action constraint euid");
1489 let v = vec![
1490 ActionConstraint::any(),
1491 ActionConstraint::is_eq(euid.clone()),
1492 ActionConstraint::is_in([euid.clone()]),
1493 ActionConstraint::is_in([euid.clone(), euid]),
1494 ];
1495
1496 v.into_iter()
1497 }
1498
1499 pub fn all_templates() -> impl Iterator<Item = Template> {
1500 let mut buf = vec![];
1501 let permit = PolicyID::from_string("permit");
1502 let forbid = PolicyID::from_string("forbid");
1503 for principal in all_principal_constraints() {
1504 for action in all_actions_constraints() {
1505 for resource in all_resource_constraints() {
1506 let permit = Template::new(
1507 permit.clone(),
1508 BTreeMap::new(),
1509 Effect::Permit,
1510 principal.clone(),
1511 action.clone(),
1512 resource.clone(),
1513 Expr::val(true),
1514 );
1515 let forbid = Template::new(
1516 forbid.clone(),
1517 BTreeMap::new(),
1518 Effect::Forbid,
1519 principal.clone(),
1520 action.clone(),
1521 resource.clone(),
1522 Expr::val(true),
1523 );
1524 buf.push(permit);
1525 buf.push(forbid);
1526 }
1527 }
1528 }
1529 buf.into_iter()
1530 }
1531}
1532
1533#[cfg(test)]
1534#[allow(clippy::indexing_slicing)]
1536#[allow(clippy::panic)]
1538mod test {
1539 use std::collections::HashSet;
1540
1541 use super::{test_generators::*, *};
1542 use crate::ast::{entity, name, EntityUID};
1543
1544 #[test]
1545 fn literal_and_borrowed() {
1546 for template in all_templates() {
1547 let t = Arc::new(template);
1548 let env = t
1549 .slots()
1550 .map(|slotid| (*slotid, EntityUID::with_eid("eid")))
1551 .collect();
1552 let p =
1553 Template::link(t, PolicyID::from_string("id"), env).expect("Instantiation Failed");
1554
1555 let b_literal = BorrowedLiteralPolicy::from(&p);
1556 let src = serde_json::to_string(&b_literal).expect("ser error");
1557 let literal: LiteralPolicy = serde_json::from_str(&src).expect("de error");
1558
1559 assert_eq!(b_literal.template_id, &literal.template_id);
1560 assert_eq!(b_literal.link_id, literal.link_id.as_ref());
1561 assert_eq!(b_literal.values, &literal.values);
1562 }
1563 }
1564
1565 #[test]
1566 fn template_roundtrip() {
1567 for template in all_templates() {
1568 template.check_invariant();
1569 let json = serde_json::to_string(&template).expect("Serialization Failed");
1570 let t2 = serde_json::from_str::<Template>(&json).expect("Deserialization failed");
1571 t2.check_invariant();
1572 assert_eq!(template, t2);
1573 }
1574 }
1575
1576 #[test]
1577 fn test_template_rebuild() {
1578 for template in all_templates() {
1579 let id = template.id().clone();
1580 let effect = template.effect();
1581 let p = template.principal_constraint().clone();
1582 let a = template.action_constraint().clone();
1583 let r = template.resource_constraint().clone();
1584 let nhc = template.non_head_constraints().clone();
1585 let t2 = Template::new(id, BTreeMap::new(), effect, p, a, r, nhc);
1586 assert_eq!(template, t2);
1587 }
1588 }
1589
1590 #[test]
1591 fn test_inline_policy_rebuild() {
1592 for template in all_templates() {
1593 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
1594 let id = ip.id().clone();
1595 let e = ip.effect();
1596 let anno = ip
1597 .annotations()
1598 .map(|(k, v)| (k.clone(), v.clone()))
1599 .collect();
1600 let p = ip.principal_constraint().clone();
1601 let a = ip.action_constraint().clone();
1602 let r = ip.resource_constraint().clone();
1603 let nhc = ip.non_head_constraints().clone();
1604 let ip2 =
1605 StaticPolicy::new(id, anno, e, p, a, r, nhc).expect("Policy Creation Failed");
1606 assert_eq!(ip, ip2);
1607 let (t2, inst) = Template::link_static_policy(ip2);
1608 assert!(inst.is_static());
1609 assert_eq!(&template, t2.as_ref());
1610 }
1611 }
1612 }
1613
1614 #[test]
1615 fn ir_binding_too_many() {
1616 let tid = PolicyID::from_string("tid");
1617 let iid = PolicyID::from_string("iid");
1618 let t = Arc::new(Template::new(
1619 tid,
1620 BTreeMap::new(),
1621 Effect::Forbid,
1622 PrincipalConstraint::is_eq_slot(),
1623 ActionConstraint::Any,
1624 ResourceConstraint::any(),
1625 Expr::val(true),
1626 ));
1627 let mut m = HashMap::new();
1628 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
1629 match Template::link(t, iid, m) {
1630 Ok(_) => panic!("Should fail!"),
1631 Err(LinkingError::ArityError {
1632 unbound_values,
1633 extra_values,
1634 }) => {
1635 assert_eq!(unbound_values.len(), 1);
1636 assert!(unbound_values.contains(&SlotId::principal()));
1637 assert_eq!(extra_values.len(), 1);
1638 assert!(extra_values.contains(&SlotId::resource()));
1639 }
1640 Err(e) => panic!("Wrong error: {e}"),
1641 };
1642 }
1643
1644 #[test]
1645 fn ir_binding_too_few() {
1646 let tid = PolicyID::from_string("tid");
1647 let iid = PolicyID::from_string("iid");
1648 let t = Arc::new(Template::new(
1649 tid,
1650 BTreeMap::new(),
1651 Effect::Forbid,
1652 PrincipalConstraint::is_eq_slot(),
1653 ActionConstraint::Any,
1654 ResourceConstraint::is_in_slot(),
1655 Expr::val(true),
1656 ));
1657 match Template::link(t.clone(), iid.clone(), HashMap::new()) {
1658 Ok(_) => panic!("should have failed!"),
1659 Err(LinkingError::ArityError {
1660 unbound_values,
1661 extra_values,
1662 }) => {
1663 assert_eq!(unbound_values.len(), 2);
1664 assert_eq!(extra_values.len(), 0);
1665 }
1666 Err(e) => panic!("Wrong error: {e}"),
1667 };
1668 let mut m = HashMap::new();
1669 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
1670 match Template::link(t, iid, m) {
1671 Ok(_) => panic!("should have failed!"),
1672 Err(LinkingError::ArityError {
1673 unbound_values,
1674 extra_values,
1675 }) => {
1676 assert_eq!(unbound_values.len(), 1);
1677 assert!(unbound_values.contains(&SlotId::resource()));
1678 assert_eq!(extra_values.len(), 0);
1679 }
1680 Err(e) => panic!("Wrong error: {e}"),
1681 };
1682 }
1683
1684 #[test]
1685 fn ir_binding() {
1686 let tid = PolicyID::from_string("template");
1687 let iid = PolicyID::from_string("linked");
1688 let t = Arc::new(Template::new(
1689 tid,
1690 BTreeMap::new(),
1691 Effect::Permit,
1692 PrincipalConstraint::is_in_slot(),
1693 ActionConstraint::any(),
1694 ResourceConstraint::is_eq_slot(),
1695 Expr::val(true),
1696 ));
1697
1698 let mut m = HashMap::new();
1699 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
1700 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
1701
1702 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
1703 assert_eq!(r.id(), &iid);
1704 assert_eq!(
1705 r.env().get(&SlotId::principal()),
1706 Some(&EntityUID::with_eid("theprincipal"))
1707 );
1708 assert_eq!(
1709 r.env().get(&SlotId::resource()),
1710 Some(&EntityUID::with_eid("theresource"))
1711 );
1712 }
1713
1714 #[test]
1715 fn isnt_template_implies_from_succeeds() {
1716 for template in all_templates() {
1717 if template.slots().count() == 0 {
1718 StaticPolicy::try_from(template).expect("Should succeed");
1719 }
1720 }
1721 }
1722
1723 #[test]
1724 fn is_template_implies_from_fails() {
1725 for template in all_templates() {
1726 if template.slots().count() != 0 {
1727 assert!(
1728 StaticPolicy::try_from(template.clone()).is_err(),
1729 "Following template did convert {template}"
1730 );
1731 }
1732 }
1733 }
1734
1735 #[test]
1736 fn non_template_iso() {
1737 for template in all_templates() {
1738 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
1739 let (t2, _) = Template::link_static_policy(p);
1740 assert_eq!(&template, t2.as_ref());
1741 }
1742 }
1743 }
1744
1745 #[test]
1746 fn template_into_expr() {
1747 for template in all_templates() {
1748 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
1749 let t: Template = template;
1750 assert_eq!(p.condition(), t.condition());
1751 assert_eq!(p.effect(), t.effect());
1752 }
1753 }
1754 }
1755
1756 #[test]
1757 fn template_error_msgs_have_names() {
1758 for template in all_templates() {
1759 if let Err(e) = StaticPolicy::try_from(template) {
1760 match e {
1761 super::UnexpectedSlotError::Unnamed => panic!("Didn't get a name!"),
1762 super::UnexpectedSlotError::Named(_) => (),
1763 }
1764 }
1765 }
1766 }
1767
1768 #[test]
1769 fn template_por_iter() {
1770 let e = Arc::new(EntityUID::with_eid("eid"));
1771 assert_eq!(PrincipalOrResourceConstraint::Any.iter_euids().count(), 0);
1772 assert_eq!(
1773 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone()))
1774 .iter_euids()
1775 .count(),
1776 1
1777 );
1778 assert_eq!(
1779 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1780 .iter_euids()
1781 .count(),
1782 0
1783 );
1784 assert_eq!(
1785 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e))
1786 .iter_euids()
1787 .count(),
1788 1
1789 );
1790 assert_eq!(
1791 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1792 .iter_euids()
1793 .count(),
1794 0
1795 );
1796 }
1797
1798 #[test]
1799 fn action_iter() {
1800 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
1801 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
1802 let v = a.iter_euids().collect::<Vec<_>>();
1803 assert_eq!(vec![&EntityUID::with_eid("test")], v);
1804 let a =
1805 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
1806 let set = a.iter_euids().collect::<HashSet<_>>();
1807 let e1 = EntityUID::with_eid("test1");
1808 let e2 = EntityUID::with_eid("test2");
1809 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
1810 assert_eq!(set, correct);
1811 }
1812
1813 #[test]
1814 fn test_iter_none() {
1815 let mut i = EntityIterator::None;
1816 assert_eq!(i.next(), None);
1817 }
1818
1819 #[test]
1820 fn test_iter_once() {
1821 let id = EntityUID::from_components(
1822 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1823 entity::Eid::new("eid"),
1824 );
1825 let mut i = EntityIterator::One(&id);
1826 assert_eq!(i.next(), Some(&id));
1827 assert_eq!(i.next(), None);
1828 }
1829
1830 #[test]
1831 fn test_iter_mult() {
1832 let id1 = EntityUID::from_components(
1833 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1834 entity::Eid::new("eid1"),
1835 );
1836 let id2 = EntityUID::from_components(
1837 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1838 entity::Eid::new("eid2"),
1839 );
1840 let v = vec![&id1, &id2];
1841 let mut i = EntityIterator::Bunch(v);
1842 assert_eq!(i.next(), Some(&id2));
1843 assert_eq!(i.next(), Some(&id1));
1844 assert_eq!(i.next(), None)
1845 }
1846
1847 #[test]
1848 fn euid_into_expr() {
1849 let e = EntityReference::Slot;
1850 assert_eq!(
1851 e.into_expr(SlotId::principal()),
1852 Expr::slot(SlotId::principal())
1853 );
1854 let e = EntityReference::euid(EntityUID::with_eid("eid"));
1855 assert_eq!(
1856 e.into_expr(SlotId::principal()),
1857 Expr::val(EntityUID::with_eid("eid"))
1858 );
1859 }
1860
1861 #[test]
1862 fn por_constraint_display() {
1863 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot);
1864 let s = t.display(PrincipalOrResource::Principal);
1865 assert_eq!(s, "principal == ?principal");
1866 let t =
1867 PrincipalOrResourceConstraint::Eq(EntityReference::euid(EntityUID::with_eid("test")));
1868 let s = t.display(PrincipalOrResource::Principal);
1869 assert_eq!(s, "principal == test_entity_type::\"test\"");
1870 }
1871}