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 #[error("No such template with id {0}")]
258 NoSuchTemplate(PolicyID),
259
260 #[error("The new id conflicted with an existing Policy Id")]
262 PolicyIdConflict,
263}
264
265impl LinkingError {
266 fn from_unbound_and_extras<T>(unbound: T, extra: T) -> Self
267 where
268 T: Iterator<Item = SlotId>,
269 {
270 Self::ArityError {
271 unbound_values: unbound.collect(),
272 extra_values: extra.collect(),
273 }
274 }
275}
276
277fn describe_arity_error(unbound_values: &[SlotId], extra_values: &[SlotId]) -> String {
278 match (unbound_values.len(), extra_values.len()) {
279 (0,0) => panic!("Unreachable"),
280 (_unbound, 0) => format!("The following slots were unbound: {}", unbound_values.iter().join(",")),
281 (0, _extra) => format!("The following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
282 (_unbound, _extra) => format!("The following slots were unbound: {}\nThe following slots were provided as arguments, but did not exist in the template: {}", unbound_values.iter().join(","), extra_values.iter().join(","))
283 }
284}
285
286#[derive(Debug, Clone, Eq, PartialEq)]
294pub struct Policy {
295 template: Arc<Template>,
297 link: Option<PolicyID>,
300 values: HashMap<SlotId, EntityUID>,
306}
307
308impl Policy {
309 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
313 #[cfg(test)]
314 {
315 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
316 }
317 Self {
323 template,
324 link: link_id,
325 values,
326 }
327 }
328
329 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID) -> Self {
331 let t = Template::new(
332 id,
333 BTreeMap::new(),
334 effect,
335 PrincipalConstraint::any(),
336 ActionConstraint::any(),
337 ResourceConstraint::any(),
338 when,
339 );
340 Self::new(Arc::new(t), None, SlotEnv::new())
341 }
342
343 pub fn template(&self) -> &Template {
345 &self.template
346 }
347
348 pub(crate) fn template_arc(&self) -> Arc<Template> {
350 Arc::clone(&self.template)
351 }
352
353 pub fn effect(&self) -> Effect {
355 self.template.effect()
356 }
357
358 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
360 self.template.annotation(key)
361 }
362
363 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
365 self.template.annotations()
366 }
367
368 pub fn principal_constraint(&self) -> PrincipalConstraint {
374 let constraint = self.template.principal_constraint().clone();
375 match self.values.get(&SlotId::principal()) {
376 None => constraint,
377 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
378 }
379 }
380
381 pub fn action_constraint(&self) -> &ActionConstraint {
383 self.template.action_constraint()
384 }
385
386 pub fn resource_constraint(&self) -> ResourceConstraint {
392 let constraint = self.template.resource_constraint().clone();
393 match self.values.get(&SlotId::resource()) {
394 None => constraint,
395 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
396 }
397 }
398
399 pub fn non_head_constraints(&self) -> &Expr {
401 self.template.non_head_constraints()
402 }
403
404 pub fn condition(&self) -> Expr {
406 self.template.condition()
407 }
408
409 pub fn env(&self) -> &SlotEnv {
412 &self.values
413 }
414
415 pub fn id(&self) -> &PolicyID {
417 self.link.as_ref().unwrap_or_else(|| self.template.id())
418 }
419
420 pub fn new_id(&self, id: PolicyID) -> Self {
422 match self.link {
423 None => Policy {
424 template: Arc::new(self.template.new_id(id)),
425 link: None,
426 values: self.values.clone(),
427 },
428 Some(_) => Policy {
429 template: self.template.clone(),
430 link: Some(id),
431 values: self.values.clone(),
432 },
433 }
434 }
435
436 pub fn is_static(&self) -> bool {
438 self.link.is_none()
439 }
440}
441
442impl std::fmt::Display for Policy {
443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444 if self.is_static() {
445 write!(f, "{}", self.template())
446 } else {
447 write!(
448 f,
449 "Template Instance of {}, slots: [{}]",
450 self.template().id(),
451 display_slot_env(self.env())
452 )
453 }
454 }
455}
456
457pub type SlotEnv = HashMap<SlotId, EntityUID>;
459
460#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
463pub struct LiteralPolicy {
464 template_id: PolicyID,
466 link_id: Option<PolicyID>,
470 values: SlotEnv,
472}
473
474#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
476pub struct BorrowedLiteralPolicy<'a> {
477 template_id: &'a PolicyID,
479 link_id: Option<&'a PolicyID>,
483 values: &'a SlotEnv,
485}
486
487impl<'a> From<&'a Policy> for BorrowedLiteralPolicy<'a> {
488 fn from(p: &'a Policy) -> Self {
489 Self {
490 template_id: p.template.id(),
491 link_id: p.link.as_ref(),
492 values: &p.values,
493 }
494 }
495}
496
497impl std::hash::Hash for LiteralPolicy {
500 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
501 self.template_id.hash(state);
502 let mut buf = self.values.iter().collect::<Vec<_>>();
504 buf.sort();
505 for (id, euid) in buf {
506 id.hash(state);
507 euid.hash(state);
508 }
509 }
510}
511
512impl std::cmp::PartialEq for LiteralPolicy {
513 fn eq(&self, other: &Self) -> bool {
514 self.template_id() == other.template_id()
515 && self.link_id == other.link_id
516 && self.values == other.values
517 }
518}
519
520#[cfg(test)]
522mod hashing_tests {
523 use std::{
524 collections::hash_map::DefaultHasher,
525 hash::{Hash, Hasher},
526 };
527
528 use super::*;
529
530 fn compute_hash(ir: LiteralPolicy) -> u64 {
531 let mut s = DefaultHasher::new();
532 ir.hash(&mut s);
533 s.finish()
534 }
535
536 fn build_template_linked_policy() -> LiteralPolicy {
537 let mut map = HashMap::new();
538 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
539 LiteralPolicy {
540 template_id: PolicyID::from_string("template"),
541 link_id: Some(PolicyID::from_string("id")),
542 values: map,
543 }
544 }
545
546 #[test]
547 fn hash_property_instances() {
548 let a = build_template_linked_policy();
549 let b = build_template_linked_policy();
550 assert_eq!(a, b);
551 assert_eq!(compute_hash(a), compute_hash(b));
552 }
553}
554#[derive(Debug, Error)]
562pub enum ReificationError {
563 #[error("The PolicyID linked to does not exist")]
565 NoSuchTemplate(PolicyID),
566 #[error("{0}")]
568 Instantiation(#[from] LinkingError),
569}
570
571impl LiteralPolicy {
572 pub fn reify(
577 self,
578 templates: &HashMap<PolicyID, Arc<Template>>,
579 ) -> Result<Policy, ReificationError> {
580 let template = templates
581 .get(&self.template_id)
582 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
583 Template::check_binding(template, &self.values).map_err(ReificationError::Instantiation)?;
585 Ok(Policy::new(template.clone(), self.link_id, self.values))
586 }
587
588 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
590 self.values.get(id)
591 }
592
593 pub fn id(&self) -> &PolicyID {
596 self.link_id.as_ref().unwrap_or(&self.template_id)
597 }
598
599 pub fn template_id(&self) -> &PolicyID {
601 &self.template_id
602 }
603
604 pub fn is_static(&self) -> bool {
606 self.link_id.is_none()
607 }
608}
609
610fn display_slot_env(env: &SlotEnv) -> String {
611 env.iter()
612 .map(|(slot, value)| format!("{slot} -> {value}"))
613 .join(",")
614}
615
616impl std::fmt::Display for LiteralPolicy {
617 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618 if self.is_static() {
619 write!(f, "Static policy w/ ID {}", self.template_id())
620 } else {
621 write!(
622 f,
623 "Template linked policy of {}, slots: [{}]",
624 self.template_id(),
625 display_slot_env(&self.values),
626 )
627 }
628 }
629}
630
631impl From<Policy> for LiteralPolicy {
632 fn from(p: Policy) -> Self {
633 Self {
634 template_id: p.template.id().clone(),
635 link_id: p.link,
636 values: p.values,
637 }
638 }
639}
640
641#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
645pub struct StaticPolicy(TemplateBody);
646
647impl StaticPolicy {
648 pub fn id(&self) -> &PolicyID {
650 self.0.id()
651 }
652
653 pub fn new_id(&mut self, id: PolicyID) -> Self {
655 StaticPolicy(self.0.new_id(id))
656 }
657
658 pub fn effect(&self) -> Effect {
660 self.0.effect()
661 }
662
663 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
665 self.0.annotation(key)
666 }
667
668 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
670 self.0.annotations()
671 }
672
673 pub fn principal_constraint(&self) -> &PrincipalConstraint {
675 self.0.principal_constraint()
676 }
677
678 pub fn principal_constraint_expr(&self) -> Expr {
682 self.0.principal_constraint_expr()
683 }
684
685 pub fn action_constraint(&self) -> &ActionConstraint {
687 self.0.action_constraint()
688 }
689
690 pub fn action_constraint_expr(&self) -> Expr {
694 self.0.action_constraint_expr()
695 }
696
697 pub fn resource_constraint(&self) -> &ResourceConstraint {
699 self.0.resource_constraint()
700 }
701
702 pub fn resource_constraint_expr(&self) -> Expr {
706 self.0.resource_constraint_expr()
707 }
708
709 pub fn non_head_constraints(&self) -> &Expr {
714 self.0.non_head_constraints()
715 }
716
717 pub fn condition(&self) -> Expr {
723 self.0.condition()
724 }
725
726 pub fn new(
728 id: PolicyID,
729 annotations: BTreeMap<Id, SmolStr>,
730 effect: Effect,
731 principal_constraint: PrincipalConstraint,
732 action_constraint: ActionConstraint,
733 resource_constraint: ResourceConstraint,
734 non_head_constraints: Expr,
735 ) -> Result<Self, ContainsSlot> {
736 let body = TemplateBody::new(
737 id,
738 annotations,
739 effect,
740 principal_constraint,
741 action_constraint,
742 resource_constraint,
743 non_head_constraints,
744 );
745 let num_slots = body.condition().slots().next().map(SlotId::clone);
746 match num_slots {
748 Some(slot_id) => Err(ContainsSlot::Named(slot_id))?,
749 None => Ok(Self(body)),
750 }
751 }
752}
753
754impl TryFrom<Template> for StaticPolicy {
755 type Error = ContainsSlot;
756
757 fn try_from(value: Template) -> Result<Self, Self::Error> {
758 let o = value.slots().next().map(SlotId::clone);
760 match o {
761 Some(slot_id) => Err(ContainsSlot::Named(slot_id)),
762 None => Ok(Self(value.body)),
763 }
764 }
765}
766
767impl From<StaticPolicy> for Policy {
768 fn from(inline: StaticPolicy) -> Policy {
769 let (_, policy) = Template::link_static_policy(inline);
770 policy
771 }
772}
773
774impl From<StaticPolicy> for Arc<Template> {
775 fn from(p: StaticPolicy) -> Self {
776 let (t, _) = Template::link_static_policy(p);
777 t
778 }
779}
780
781#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
786pub struct TemplateBody {
787 id: PolicyID,
789 annotations: BTreeMap<Id, SmolStr>,
791 effect: Effect,
793 principal_constraint: PrincipalConstraint,
797 action_constraint: ActionConstraint,
801 resource_constraint: ResourceConstraint,
805 non_head_constraints: Expr,
810}
811
812impl TemplateBody {
813 pub fn id(&self) -> &PolicyID {
815 &self.id
816 }
817
818 pub fn new_id(&self, id: PolicyID) -> Self {
820 let mut new = self.clone();
821 new.id = id;
822 new
823 }
824
825 pub fn effect(&self) -> Effect {
827 self.effect
828 }
829
830 pub fn annotation(&self, key: &Id) -> Option<&SmolStr> {
832 self.annotations.get(key)
833 }
834
835 pub fn annotations(&self) -> impl Iterator<Item = (&Id, &SmolStr)> {
837 self.annotations.iter()
838 }
839
840 pub fn principal_constraint(&self) -> &PrincipalConstraint {
842 &self.principal_constraint
843 }
844
845 pub fn principal_constraint_expr(&self) -> Expr {
849 self.principal_constraint.as_expr()
850 }
851
852 pub fn action_constraint(&self) -> &ActionConstraint {
854 &self.action_constraint
855 }
856
857 pub fn action_constraint_expr(&self) -> Expr {
861 self.action_constraint.as_expr()
862 }
863
864 pub fn resource_constraint(&self) -> &ResourceConstraint {
866 &self.resource_constraint
867 }
868
869 pub fn resource_constraint_expr(&self) -> Expr {
873 self.resource_constraint.as_expr()
874 }
875
876 pub fn non_head_constraints(&self) -> &Expr {
881 &self.non_head_constraints
882 }
883
884 pub fn condition(&self) -> Expr {
890 Expr::and(
891 Expr::and(
892 Expr::and(
893 self.principal_constraint_expr(),
894 self.action_constraint_expr(),
895 ),
896 self.resource_constraint_expr(),
897 ),
898 self.non_head_constraints.clone(),
899 )
900 }
901
902 pub fn new(
904 id: PolicyID,
905 annotations: BTreeMap<Id, SmolStr>,
906 effect: Effect,
907 principal_constraint: PrincipalConstraint,
908 action_constraint: ActionConstraint,
909 resource_constraint: ResourceConstraint,
910 non_head_constraints: Expr,
911 ) -> Self {
912 Self {
913 id,
914 annotations,
915 effect,
916 principal_constraint,
917 action_constraint,
918 resource_constraint,
919 non_head_constraints,
920 }
921 }
922}
923
924impl From<StaticPolicy> for TemplateBody {
925 fn from(p: StaticPolicy) -> Self {
926 p.0
927 }
928}
929
930impl std::fmt::Display for TemplateBody {
931 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
932 for (k, v) in &self.annotations {
933 writeln!(f, "@{}(\"{}\")", k, v.escape_debug())?
934 }
935 write!(
936 f,
937 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
938 self.effect(),
939 self.principal_constraint(),
940 self.action_constraint(),
941 self.resource_constraint(),
942 self.non_head_constraints()
943 )
944 }
945}
946
947#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
949pub struct PrincipalConstraint {
950 pub(crate) constraint: PrincipalOrResourceConstraint,
951}
952
953impl PrincipalConstraint {
954 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
956 PrincipalConstraint { constraint }
957 }
958
959 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
961 &self.constraint
962 }
963
964 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
966 self.constraint
967 }
968
969 pub fn as_expr(&self) -> Expr {
971 self.constraint.as_expr(PrincipalOrResource::Principal)
972 }
973
974 pub fn any() -> Self {
976 PrincipalConstraint {
977 constraint: PrincipalOrResourceConstraint::any(),
978 }
979 }
980
981 pub fn is_eq(euid: EntityUID) -> Self {
983 PrincipalConstraint {
984 constraint: PrincipalOrResourceConstraint::is_eq(euid),
985 }
986 }
987
988 pub fn is_eq_slot() -> Self {
990 Self {
991 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
992 }
993 }
994
995 pub fn is_in(euid: EntityUID) -> Self {
997 PrincipalConstraint {
998 constraint: PrincipalOrResourceConstraint::is_in(euid),
999 }
1000 }
1001
1002 pub fn is_in_slot() -> Self {
1004 Self {
1005 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1006 }
1007 }
1008
1009 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1011 match self.constraint {
1012 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1013 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1014 },
1015 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1016 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1017 },
1018 _ => self,
1019 }
1020 }
1021}
1022
1023impl std::fmt::Display for PrincipalConstraint {
1024 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1025 write!(
1026 f,
1027 "{}",
1028 self.constraint.display(PrincipalOrResource::Principal)
1029 )
1030 }
1031}
1032
1033#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1035pub struct ResourceConstraint {
1036 pub(crate) constraint: PrincipalOrResourceConstraint,
1037}
1038
1039impl ResourceConstraint {
1040 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1042 ResourceConstraint { constraint }
1043 }
1044
1045 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1047 &self.constraint
1048 }
1049
1050 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1052 self.constraint
1053 }
1054
1055 pub fn as_expr(&self) -> Expr {
1057 self.constraint.as_expr(PrincipalOrResource::Resource)
1058 }
1059
1060 pub fn any() -> Self {
1062 ResourceConstraint {
1063 constraint: PrincipalOrResourceConstraint::any(),
1064 }
1065 }
1066
1067 pub fn is_eq(euid: EntityUID) -> Self {
1069 ResourceConstraint {
1070 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1071 }
1072 }
1073
1074 pub fn is_eq_slot() -> Self {
1076 Self {
1077 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1078 }
1079 }
1080
1081 pub fn is_in_slot() -> Self {
1083 Self {
1084 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1085 }
1086 }
1087
1088 pub fn is_in(euid: EntityUID) -> Self {
1090 ResourceConstraint {
1091 constraint: PrincipalOrResourceConstraint::is_in(euid),
1092 }
1093 }
1094
1095 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1097 match self.constraint {
1098 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1099 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1100 },
1101 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1102 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1103 },
1104 _ => self,
1105 }
1106 }
1107}
1108
1109impl std::fmt::Display for ResourceConstraint {
1110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1111 write!(
1112 f,
1113 "{}",
1114 self.as_inner().display(PrincipalOrResource::Resource)
1115 )
1116 }
1117}
1118
1119#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1121pub enum EntityReference {
1122 EUID(Arc<EntityUID>),
1124 Slot,
1126}
1127
1128impl EntityReference {
1129 pub fn euid(euid: EntityUID) -> Self {
1131 Self::EUID(Arc::new(euid))
1132 }
1133}
1134
1135#[derive(Debug, Clone, PartialEq, Error)]
1137pub enum ContainsSlot {
1138 #[error("Found a slot where none was expected")]
1140 Unnamed,
1141 #[error("Found slot {0} where none was expected")]
1143 Named(SlotId),
1144}
1145
1146impl TryInto<Arc<EntityUID>> for EntityReference {
1147 type Error = ContainsSlot;
1148
1149 fn try_into(self) -> Result<Arc<EntityUID>, Self::Error> {
1150 match self {
1151 EntityReference::EUID(euid) => Ok(euid),
1152 EntityReference::Slot => Err(ContainsSlot::Unnamed),
1153 }
1154 }
1155}
1156
1157impl From<EntityUID> for EntityReference {
1158 fn from(euid: EntityUID) -> Self {
1159 Self::EUID(Arc::new(euid))
1160 }
1161}
1162
1163impl EntityReference {
1164 pub fn into_expr(&self, name: SlotId) -> Expr {
1166 match self {
1167 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1168 EntityReference::Slot => Expr::slot(name),
1169 }
1170 }
1171}
1172
1173#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1175#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
1176pub enum PrincipalOrResource {
1177 Principal,
1179 Resource,
1181}
1182
1183impl std::fmt::Display for PrincipalOrResource {
1184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1185 let v = Var::from(*self);
1186 write!(f, "{v}")
1187 }
1188}
1189
1190impl TryFrom<Var> for PrincipalOrResource {
1191 type Error = Var;
1192
1193 fn try_from(value: Var) -> Result<Self, Self::Error> {
1194 match value {
1195 Var::Principal => Ok(Self::Principal),
1196 Var::Action => Err(Var::Action),
1197 Var::Resource => Ok(Self::Resource),
1198 Var::Context => Err(Var::Context),
1199 }
1200 }
1201}
1202
1203#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1206pub enum PrincipalOrResourceConstraint {
1207 Any,
1209 In(EntityReference),
1211 Eq(EntityReference),
1213}
1214
1215impl PrincipalOrResourceConstraint {
1216 pub fn any() -> Self {
1218 PrincipalOrResourceConstraint::Any
1219 }
1220
1221 pub fn is_eq(euid: EntityUID) -> Self {
1223 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1224 }
1225
1226 pub fn is_eq_slot() -> Self {
1228 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1229 }
1230
1231 pub fn is_in_slot() -> Self {
1233 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1234 }
1235
1236 pub fn is_in(euid: EntityUID) -> Self {
1238 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1239 }
1240
1241 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1245 match self {
1246 PrincipalOrResourceConstraint::Any => Expr::val(true),
1247 PrincipalOrResourceConstraint::Eq(euid) => {
1248 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1249 }
1250 PrincipalOrResourceConstraint::In(euid) => {
1251 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1252 }
1253 }
1254 }
1255
1256 pub fn display(&self, v: PrincipalOrResource) -> String {
1260 match self {
1261 PrincipalOrResourceConstraint::In(euid) => {
1262 format!("{} in {}", v, euid.into_expr(v.into()))
1263 }
1264 PrincipalOrResourceConstraint::Eq(euid) => {
1265 format!("{} == {}", v, euid.into_expr(v.into()))
1266 }
1267 PrincipalOrResourceConstraint::Any => format!("{}", v),
1268 }
1269 }
1270
1271 pub fn iter_euids(&'_ self) -> impl Iterator<Item = &'_ EntityUID> {
1273 match self {
1274 PrincipalOrResourceConstraint::Any => EntityIterator::None,
1275 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => {
1276 EntityIterator::One(euid)
1277 }
1278 PrincipalOrResourceConstraint::In(EntityReference::Slot) => EntityIterator::None,
1279 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => {
1280 EntityIterator::One(euid)
1281 }
1282 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => EntityIterator::None,
1283 }
1284 }
1285}
1286
1287#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
1290pub enum ActionConstraint {
1291 Any,
1293 In(Vec<Arc<EntityUID>>),
1295 Eq(Arc<EntityUID>),
1297}
1298
1299impl std::fmt::Display for ActionConstraint {
1300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1301 let render_euids =
1302 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1303 match self {
1304 ActionConstraint::Any => write!(f, "action"),
1305 ActionConstraint::In(euids) => {
1306 write!(f, "action in [{}]", render_euids(euids))
1307 }
1308 ActionConstraint::Eq(euid) => write!(f, "action == {}", euid),
1309 }
1310 }
1311}
1312
1313impl ActionConstraint {
1314 pub fn any() -> Self {
1316 ActionConstraint::Any
1317 }
1318
1319 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1321 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1322 }
1323
1324 pub fn is_eq(euid: EntityUID) -> Self {
1326 ActionConstraint::Eq(Arc::new(euid))
1327 }
1328
1329 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1330 Expr::set(euids.into_iter().map(Expr::val))
1331 }
1332
1333 pub fn as_expr(&self) -> Expr {
1335 match self {
1336 ActionConstraint::Any => Expr::val(true),
1337 ActionConstraint::In(euids) => Expr::is_in(
1338 Expr::var(Var::Action),
1339 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1340 ),
1341 ActionConstraint::Eq(euid) => {
1342 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1343 }
1344 }
1345 }
1346
1347 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1349 match self {
1350 ActionConstraint::Any => EntityIterator::None,
1351 ActionConstraint::In(euids) => {
1352 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1353 }
1354 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1355 }
1356 }
1357}
1358
1359impl std::fmt::Display for StaticPolicy {
1360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1361 for (k, v) in &self.0.annotations {
1362 writeln!(f, "@{}(\"{}\")", k, v.escape_debug())?
1363 }
1364 write!(
1365 f,
1366 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1367 self.effect(),
1368 self.principal_constraint(),
1369 self.action_constraint(),
1370 self.resource_constraint(),
1371 self.non_head_constraints()
1372 )
1373 }
1374}
1375
1376#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash)]
1378pub struct PolicyID(SmolStr);
1379
1380impl PolicyID {
1381 pub fn from_string(id: impl AsRef<str>) -> Self {
1383 Self(SmolStr::from(id.as_ref()))
1384 }
1385
1386 pub fn from_smolstr(id: SmolStr) -> Self {
1388 Self(id)
1389 }
1390}
1391
1392impl std::fmt::Display for PolicyID {
1393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1394 write!(f, "{}", self.0.escape_debug())
1395 }
1396}
1397
1398#[cfg(fuzzing)]
1399impl<'u> arbitrary::Arbitrary<'u> for PolicyID {
1400 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1401 let s: String = u.arbitrary()?;
1402 Ok(PolicyID::from_string(s))
1403 }
1404 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1405 <String as arbitrary::Arbitrary>::size_hint(depth)
1406 }
1407}
1408
1409#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, Clone, Copy)]
1411#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
1412pub enum Effect {
1413 #[serde(rename = "permit")]
1415 Permit,
1416 #[serde(rename = "forbid")]
1418 Forbid,
1419}
1420
1421impl std::fmt::Display for Effect {
1422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1423 match self {
1424 Self::Permit => write!(f, "permit"),
1425 Self::Forbid => write!(f, "forbid"),
1426 }
1427 }
1428}
1429
1430enum EntityIterator<'a> {
1431 None,
1432 One(&'a EntityUID),
1433 Bunch(Vec<&'a EntityUID>),
1434}
1435
1436impl<'a> Iterator for EntityIterator<'a> {
1437 type Item = &'a EntityUID;
1438
1439 fn next(&mut self) -> Option<Self::Item> {
1440 match self {
1441 EntityIterator::None => None,
1442 EntityIterator::One(euid) => {
1443 let eptr = *euid;
1444 let mut ptr = EntityIterator::None;
1445 std::mem::swap(self, &mut ptr);
1446 Some(eptr)
1447 }
1448 EntityIterator::Bunch(v) => v.pop(),
1449 }
1450 }
1451}
1452
1453#[cfg(test)]
1454pub mod test_generators {
1455 use super::*;
1456
1457 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1458 let euid = EntityUID::with_eid("test");
1459 let v = vec![
1460 PrincipalOrResourceConstraint::any(),
1461 PrincipalOrResourceConstraint::is_eq(euid.clone()),
1462 PrincipalOrResourceConstraint::Eq(EntityReference::Slot),
1463 PrincipalOrResourceConstraint::is_in(euid),
1464 PrincipalOrResourceConstraint::In(EntityReference::Slot),
1465 ];
1466
1467 v.into_iter()
1468 }
1469
1470 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
1471 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
1472 }
1473
1474 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
1475 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
1476 }
1477
1478 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
1479 let euid: EntityUID = "Action::\"test\""
1480 .parse()
1481 .expect("Invalid action constraint euid");
1482 let v = vec![
1483 ActionConstraint::any(),
1484 ActionConstraint::is_eq(euid.clone()),
1485 ActionConstraint::is_in([euid.clone()]),
1486 ActionConstraint::is_in([euid.clone(), euid]),
1487 ];
1488
1489 v.into_iter()
1490 }
1491
1492 pub fn all_templates() -> impl Iterator<Item = Template> {
1493 let mut buf = vec![];
1494 let permit = PolicyID::from_string("permit");
1495 let forbid = PolicyID::from_string("forbid");
1496 for principal in all_principal_constraints() {
1497 for action in all_actions_constraints() {
1498 for resource in all_resource_constraints() {
1499 let permit = Template::new(
1500 permit.clone(),
1501 BTreeMap::new(),
1502 Effect::Permit,
1503 principal.clone(),
1504 action.clone(),
1505 resource.clone(),
1506 Expr::val(true),
1507 );
1508 let forbid = Template::new(
1509 forbid.clone(),
1510 BTreeMap::new(),
1511 Effect::Forbid,
1512 principal.clone(),
1513 action.clone(),
1514 resource.clone(),
1515 Expr::val(true),
1516 );
1517 buf.push(permit);
1518 buf.push(forbid);
1519 }
1520 }
1521 }
1522 buf.into_iter()
1523 }
1524}
1525
1526#[cfg(test)]
1527mod test {
1528 use std::collections::HashSet;
1529
1530 use super::{test_generators::*, *};
1531 use crate::ast::{entity, name, EntityUID};
1532
1533 #[test]
1534 fn literal_and_borrowed() {
1535 for template in all_templates() {
1536 let t = Arc::new(template);
1537 let env = t
1538 .slots()
1539 .map(|slotid| (*slotid, EntityUID::with_eid("eid")))
1540 .collect();
1541 let p =
1542 Template::link(t, PolicyID::from_string("id"), env).expect("Instantiation Failed");
1543
1544 let b_literal = BorrowedLiteralPolicy::from(&p);
1545 let src = serde_json::to_string(&b_literal).expect("ser error");
1546 let literal: LiteralPolicy = serde_json::from_str(&src).expect("de error");
1547
1548 assert_eq!(b_literal.template_id, &literal.template_id);
1549 assert_eq!(b_literal.link_id, literal.link_id.as_ref());
1550 assert_eq!(b_literal.values, &literal.values);
1551 }
1552 }
1553
1554 #[test]
1555 fn template_roundtrip() {
1556 for template in all_templates() {
1557 template.check_invariant();
1558 let json = serde_json::to_string(&template).expect("Serialization Failed");
1559 let t2 = serde_json::from_str::<Template>(&json).expect("Deserialization failed");
1560 t2.check_invariant();
1561 assert_eq!(template, t2);
1562 }
1563 }
1564
1565 #[test]
1566 fn test_template_rebuild() {
1567 for template in all_templates() {
1568 let id = template.id().clone();
1569 let effect = template.effect();
1570 let p = template.principal_constraint().clone();
1571 let a = template.action_constraint().clone();
1572 let r = template.resource_constraint().clone();
1573 let nhc = template.non_head_constraints().clone();
1574 let t2 = Template::new(id, BTreeMap::new(), effect, p, a, r, nhc);
1575 assert_eq!(template, t2);
1576 }
1577 }
1578
1579 #[test]
1580 fn test_inline_policy_rebuild() {
1581 for template in all_templates() {
1582 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
1583 let id = ip.id().clone();
1584 let e = ip.effect();
1585 let anno = ip
1586 .annotations()
1587 .map(|(k, v)| (k.clone(), v.clone()))
1588 .collect();
1589 let p = ip.principal_constraint().clone();
1590 let a = ip.action_constraint().clone();
1591 let r = ip.resource_constraint().clone();
1592 let nhc = ip.non_head_constraints().clone();
1593 let ip2 =
1594 StaticPolicy::new(id, anno, e, p, a, r, nhc).expect("Policy Creation Failed");
1595 assert_eq!(ip, ip2);
1596 let (t2, inst) = Template::link_static_policy(ip2);
1597 assert!(inst.is_static());
1598 assert_eq!(&template, t2.as_ref());
1599 }
1600 }
1601 }
1602
1603 #[test]
1604 fn ir_binding_too_many() {
1605 let tid = PolicyID::from_string("tid");
1606 let iid = PolicyID::from_string("iid");
1607 let t = Arc::new(Template::new(
1608 tid,
1609 BTreeMap::new(),
1610 Effect::Forbid,
1611 PrincipalConstraint::is_eq_slot(),
1612 ActionConstraint::Any,
1613 ResourceConstraint::any(),
1614 Expr::val(true),
1615 ));
1616 let mut m = HashMap::new();
1617 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
1618 match Template::link(t, iid, m) {
1619 Ok(_) => panic!("Should fail!"),
1620 Err(LinkingError::ArityError {
1621 unbound_values,
1622 extra_values,
1623 }) => {
1624 assert_eq!(unbound_values.len(), 1);
1625 assert!(unbound_values.contains(&SlotId::principal()));
1626 assert_eq!(extra_values.len(), 1);
1627 assert!(extra_values.contains(&SlotId::resource()));
1628 }
1629 Err(e) => panic!("Wrong error: {e}"),
1630 };
1631 }
1632
1633 #[test]
1634 fn ir_binding_too_few() {
1635 let tid = PolicyID::from_string("tid");
1636 let iid = PolicyID::from_string("iid");
1637 let t = Arc::new(Template::new(
1638 tid,
1639 BTreeMap::new(),
1640 Effect::Forbid,
1641 PrincipalConstraint::is_eq_slot(),
1642 ActionConstraint::Any,
1643 ResourceConstraint::is_in_slot(),
1644 Expr::val(true),
1645 ));
1646 match Template::link(t.clone(), iid.clone(), HashMap::new()) {
1647 Ok(_) => panic!("should have failed!"),
1648 Err(LinkingError::ArityError {
1649 unbound_values,
1650 extra_values,
1651 }) => {
1652 assert_eq!(unbound_values.len(), 2);
1653 assert_eq!(extra_values.len(), 0);
1654 }
1655 Err(e) => panic!("Wrong error: {e}"),
1656 };
1657 let mut m = HashMap::new();
1658 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
1659 match Template::link(t, iid, m) {
1660 Ok(_) => panic!("should have failed!"),
1661 Err(LinkingError::ArityError {
1662 unbound_values,
1663 extra_values,
1664 }) => {
1665 assert_eq!(unbound_values.len(), 1);
1666 assert!(unbound_values.contains(&SlotId::resource()));
1667 assert_eq!(extra_values.len(), 0);
1668 }
1669 Err(e) => panic!("Wrong error: {e}"),
1670 };
1671 }
1672
1673 #[test]
1674 fn ir_binding() {
1675 let tid = PolicyID::from_string("template");
1676 let iid = PolicyID::from_string("linked");
1677 let t = Arc::new(Template::new(
1678 tid,
1679 BTreeMap::new(),
1680 Effect::Permit,
1681 PrincipalConstraint::is_in_slot(),
1682 ActionConstraint::any(),
1683 ResourceConstraint::is_eq_slot(),
1684 Expr::val(true),
1685 ));
1686
1687 let mut m = HashMap::new();
1688 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
1689 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
1690
1691 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
1692 assert_eq!(r.id(), &iid);
1693 assert_eq!(
1694 r.env().get(&SlotId::principal()),
1695 Some(&EntityUID::with_eid("theprincipal"))
1696 );
1697 assert_eq!(
1698 r.env().get(&SlotId::resource()),
1699 Some(&EntityUID::with_eid("theresource"))
1700 );
1701 }
1702
1703 #[test]
1704 fn isnt_template_implies_from_succeeds() {
1705 for template in all_templates() {
1706 if template.slots().count() == 0 {
1707 StaticPolicy::try_from(template).expect("Should succeed");
1708 }
1709 }
1710 }
1711
1712 #[test]
1713 fn is_template_implies_from_fails() {
1714 for template in all_templates() {
1715 if template.slots().count() != 0 {
1716 assert!(
1717 StaticPolicy::try_from(template.clone()).is_err(),
1718 "Following template did convert {template}"
1719 );
1720 }
1721 }
1722 }
1723
1724 #[test]
1725 fn non_template_iso() {
1726 for template in all_templates() {
1727 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
1728 let (t2, _) = Template::link_static_policy(p);
1729 assert_eq!(&template, t2.as_ref());
1730 }
1731 }
1732 }
1733
1734 #[test]
1735 fn template_into_expr() {
1736 for template in all_templates() {
1737 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
1738 let t: Template = template;
1739 assert_eq!(p.condition(), t.condition());
1740 assert_eq!(p.effect(), t.effect());
1741 }
1742 }
1743 }
1744
1745 #[test]
1746 fn template_error_msgs_have_names() {
1747 for template in all_templates() {
1748 if let Err(e) = StaticPolicy::try_from(template) {
1749 match e {
1750 super::ContainsSlot::Unnamed => panic!("Didn't get a name!"),
1751 super::ContainsSlot::Named(_) => (),
1752 }
1753 }
1754 }
1755 }
1756
1757 #[test]
1758 fn template_por_iter() {
1759 let e = Arc::new(EntityUID::with_eid("eid"));
1760 assert_eq!(PrincipalOrResourceConstraint::Any.iter_euids().count(), 0);
1761 assert_eq!(
1762 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone()))
1763 .iter_euids()
1764 .count(),
1765 1
1766 );
1767 assert_eq!(
1768 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1769 .iter_euids()
1770 .count(),
1771 0
1772 );
1773 assert_eq!(
1774 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e))
1775 .iter_euids()
1776 .count(),
1777 1
1778 );
1779 assert_eq!(
1780 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1781 .iter_euids()
1782 .count(),
1783 0
1784 );
1785 }
1786
1787 #[test]
1788 fn action_iter() {
1789 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
1790 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
1791 let v = a.iter_euids().collect::<Vec<_>>();
1792 assert_eq!(vec![&EntityUID::with_eid("test")], v);
1793 let a =
1794 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
1795 let set = a.iter_euids().collect::<HashSet<_>>();
1796 let e1 = EntityUID::with_eid("test1");
1797 let e2 = EntityUID::with_eid("test2");
1798 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
1799 assert_eq!(set, correct);
1800 }
1801
1802 #[test]
1803 fn test_iter_none() {
1804 let mut i = EntityIterator::None;
1805 assert_eq!(i.next(), None);
1806 }
1807
1808 #[test]
1809 fn test_iter_once() {
1810 let id = EntityUID::from_components(
1811 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1812 entity::Eid::new("eid"),
1813 );
1814 let mut i = EntityIterator::One(&id);
1815 assert_eq!(i.next(), Some(&id));
1816 assert_eq!(i.next(), None);
1817 }
1818
1819 #[test]
1820 fn test_iter_mult() {
1821 let id1 = EntityUID::from_components(
1822 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1823 entity::Eid::new("eid1"),
1824 );
1825 let id2 = EntityUID::from_components(
1826 name::Name::unqualified_name(name::Id::new_unchecked("s")),
1827 entity::Eid::new("eid2"),
1828 );
1829 let v = vec![&id1, &id2];
1830 let mut i = EntityIterator::Bunch(v);
1831 assert_eq!(i.next(), Some(&id2));
1832 assert_eq!(i.next(), Some(&id1));
1833 assert_eq!(i.next(), None)
1834 }
1835
1836 #[test]
1837 fn euid_into_expr() {
1838 let e = EntityReference::Slot;
1839 assert_eq!(
1840 e.into_expr(SlotId::principal()),
1841 Expr::slot(SlotId::principal())
1842 );
1843 let e = EntityReference::euid(EntityUID::with_eid("eid"));
1844 assert_eq!(
1845 e.into_expr(SlotId::principal()),
1846 Expr::val(EntityUID::with_eid("eid"))
1847 );
1848 }
1849
1850 #[test]
1851 fn por_constraint_display() {
1852 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot);
1853 let s = t.display(PrincipalOrResource::Principal);
1854 assert_eq!(s, "principal == ?principal");
1855 let t =
1856 PrincipalOrResourceConstraint::Eq(EntityReference::euid(EntityUID::with_eid("test")));
1857 let s = t.display(PrincipalOrResource::Principal);
1858 assert_eq!(s, "principal == test_entity_type::\"test\"");
1859 }
1860}