1#![allow(clippy::use_self, reason = "readability")]
18
19use super::models;
20use cedar_policy_core::{ast, FromNormalizedStr};
21use std::collections::HashMap;
22
23impl From<&models::Policy> for ast::LiteralPolicy {
24 fn from(v: &models::Policy) -> Self {
25 let mut values: ast::SlotEnv = HashMap::new();
26 if let Some(principal_euid) = v.principal_euid.as_ref() {
27 values.insert(
28 ast::SlotId::principal(),
29 ast::EntityUID::from(principal_euid),
30 );
31 }
32 if let Some(resource_euid) = v.resource_euid.as_ref() {
33 values.insert(ast::SlotId::resource(), ast::EntityUID::from(resource_euid));
34 }
35
36 let template_id = ast::PolicyID::from_string(v.template_id.clone());
37
38 if v.is_template_link {
39 #[expect(clippy::expect_used, reason = "experimental feature")]
40 Self::template_linked_policy(
41 template_id,
42 ast::PolicyID::from_string(v.link_id.as_ref().expect("link_id field should exist")),
43 values,
44 )
45 } else {
46 Self::static_policy(template_id)
47 }
48 }
49}
50
51impl From<&ast::LiteralPolicy> for models::Policy {
52 fn from(v: &ast::LiteralPolicy) -> Self {
53 Self {
54 template_id: v.template_id().as_ref().to_string(),
55 link_id: if v.is_static() {
56 None
57 } else {
58 Some(v.id().as_ref().to_string())
59 },
60 is_template_link: !v.is_static(),
61 principal_euid: v
62 .value(&ast::SlotId::principal())
63 .map(models::EntityUid::from),
64 resource_euid: v
65 .value(&ast::SlotId::resource())
66 .map(models::EntityUid::from),
67 }
68 }
69}
70
71impl From<&ast::Policy> for models::Policy {
72 fn from(v: &ast::Policy) -> Self {
73 Self {
74 template_id: v.template().id().as_ref().to_string(),
75 link_id: if v.is_static() {
76 None
77 } else {
78 Some(v.id().as_ref().to_string())
79 },
80 is_template_link: !v.is_static(),
81 principal_euid: v
82 .env()
83 .get(&ast::SlotId::principal())
84 .map(models::EntityUid::from),
85 resource_euid: v
86 .env()
87 .get(&ast::SlotId::resource())
88 .map(models::EntityUid::from),
89 }
90 }
91}
92
93impl From<&models::TemplateBody> for ast::Template {
94 fn from(v: &models::TemplateBody) -> Self {
95 ast::Template::from(ast::TemplateBody::from(v))
96 }
97}
98
99impl From<&models::TemplateBody> for ast::TemplateBody {
100 #[expect(
101 clippy::expect_used,
102 clippy::unwrap_used,
103 reason = "experimental feature"
104 )]
105 fn from(v: &models::TemplateBody) -> Self {
106 ast::TemplateBody::new(
107 ast::PolicyID::from_string(v.id.clone()),
108 None,
109 v.annotations
110 .iter()
111 .map(|(key, value)| {
112 (
113 ast::AnyId::from_normalized_str(key).unwrap(),
114 ast::Annotation {
115 val: value.into(),
116 loc: None,
117 },
118 )
119 })
120 .collect(),
121 ast::Effect::from(&models::Effect::try_from(v.effect).expect("decode should succeed")),
122 ast::PrincipalConstraint::from(
123 v.principal_constraint
124 .as_ref()
125 .expect("principal_constraint field should exist"),
126 ),
127 ast::ActionConstraint::from(
128 v.action_constraint
129 .as_ref()
130 .expect("action_constraint field should exist"),
131 ),
132 ast::ResourceConstraint::from(
133 v.resource_constraint
134 .as_ref()
135 .expect("resource_constraint field should exist"),
136 ),
137 v.non_scope_constraints.as_ref().map(ast::Expr::from),
138 )
139 }
140}
141
142impl From<&ast::TemplateBody> for models::TemplateBody {
143 fn from(v: &ast::TemplateBody) -> Self {
144 let annotations: HashMap<String, String> = v
145 .annotations()
146 .map(|(key, value)| (key.as_ref().into(), value.as_ref().into()))
147 .collect();
148
149 Self {
150 id: v.id().as_ref().to_string(),
151 annotations,
152 effect: models::Effect::from(&v.effect()).into(),
153 principal_constraint: Some(models::PrincipalOrResourceConstraint::from(
154 v.principal_constraint(),
155 )),
156 action_constraint: Some(models::ActionConstraint::from(v.action_constraint())),
157 resource_constraint: Some(models::PrincipalOrResourceConstraint::from(
158 v.resource_constraint(),
159 )),
160 non_scope_constraints: v.non_scope_constraints().map(models::Expr::from),
161 }
162 }
163}
164
165impl From<&ast::Template> for models::TemplateBody {
166 fn from(v: &ast::Template) -> Self {
167 models::TemplateBody::from(&ast::TemplateBody::from(v.clone()))
168 }
169}
170
171impl From<&models::PrincipalOrResourceConstraint> for ast::PrincipalConstraint {
172 fn from(v: &models::PrincipalOrResourceConstraint) -> Self {
173 Self::new(ast::PrincipalOrResourceConstraint::from(v))
174 }
175}
176
177impl From<&ast::PrincipalConstraint> for models::PrincipalOrResourceConstraint {
178 fn from(v: &ast::PrincipalConstraint) -> Self {
179 models::PrincipalOrResourceConstraint::from(v.as_inner())
180 }
181}
182
183impl From<&models::PrincipalOrResourceConstraint> for ast::ResourceConstraint {
184 fn from(v: &models::PrincipalOrResourceConstraint) -> Self {
185 Self::new(ast::PrincipalOrResourceConstraint::from(v))
186 }
187}
188
189impl From<&ast::ResourceConstraint> for models::PrincipalOrResourceConstraint {
190 fn from(v: &ast::ResourceConstraint) -> Self {
191 models::PrincipalOrResourceConstraint::from(v.as_inner())
192 }
193}
194
195impl From<&models::EntityReference> for ast::EntityReference {
196 #[expect(clippy::expect_used, reason = "experimental feature")]
197 fn from(v: &models::EntityReference) -> Self {
198 match v.data.as_ref().expect("data field should exist") {
199 models::entity_reference::Data::Slot(slot) => {
200 match models::entity_reference::Slot::try_from(*slot)
201 .expect("decode should succeed")
202 {
203 models::entity_reference::Slot::Unit => ast::EntityReference::Slot(None),
204 }
205 }
206 models::entity_reference::Data::Euid(euid) => {
207 ast::EntityReference::euid(ast::EntityUID::from(euid).into())
208 }
209 }
210 }
211}
212
213impl From<&ast::EntityReference> for models::EntityReference {
214 fn from(v: &ast::EntityReference) -> Self {
215 match v {
216 ast::EntityReference::EUID(euid) => Self {
217 data: Some(models::entity_reference::Data::Euid(
218 models::EntityUid::from(euid.as_ref()),
219 )),
220 },
221 ast::EntityReference::Slot(_) => Self {
222 data: Some(models::entity_reference::Data::Slot(
223 models::entity_reference::Slot::Unit.into(),
224 )),
225 },
226 }
227 }
228}
229
230impl From<&models::PrincipalOrResourceConstraint> for ast::PrincipalOrResourceConstraint {
231 #[expect(clippy::expect_used, reason = "experimental feature")]
232 fn from(v: &models::PrincipalOrResourceConstraint) -> Self {
233 match v.data.as_ref().expect("data field should exist") {
234 models::principal_or_resource_constraint::Data::Any(unit) => {
235 match models::principal_or_resource_constraint::Any::try_from(*unit)
236 .expect("decode should succeed")
237 {
238 models::principal_or_resource_constraint::Any::Unit => {
239 ast::PrincipalOrResourceConstraint::Any
240 }
241 }
242 }
243 models::principal_or_resource_constraint::Data::In(msg) => {
244 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::from(
245 msg.er.as_ref().expect("er field should exist"),
246 ))
247 }
248 models::principal_or_resource_constraint::Data::Eq(msg) => {
249 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::from(
250 msg.er.as_ref().expect("er field should exist"),
251 ))
252 }
253 models::principal_or_resource_constraint::Data::Is(msg) => {
254 ast::PrincipalOrResourceConstraint::Is(
255 ast::EntityType::from(
256 msg.entity_type
257 .as_ref()
258 .expect("entity_type field should exist"),
259 )
260 .into(),
261 )
262 }
263 models::principal_or_resource_constraint::Data::IsIn(msg) => {
264 ast::PrincipalOrResourceConstraint::IsIn(
265 ast::EntityType::from(
266 msg.entity_type
267 .as_ref()
268 .expect("entity_type field should exist"),
269 )
270 .into(),
271 ast::EntityReference::from(msg.er.as_ref().expect("er field should exist")),
272 )
273 }
274 }
275 }
276}
277
278impl From<&ast::PrincipalOrResourceConstraint> for models::PrincipalOrResourceConstraint {
279 fn from(v: &ast::PrincipalOrResourceConstraint) -> Self {
280 match v {
281 ast::PrincipalOrResourceConstraint::Any => Self {
282 data: Some(models::principal_or_resource_constraint::Data::Any(
283 models::principal_or_resource_constraint::Any::Unit.into(),
284 )),
285 },
286 ast::PrincipalOrResourceConstraint::In(er) => Self {
287 data: Some(models::principal_or_resource_constraint::Data::In(
288 models::principal_or_resource_constraint::InMessage {
289 er: Some(models::EntityReference::from(er)),
290 },
291 )),
292 },
293 ast::PrincipalOrResourceConstraint::Eq(er) => Self {
294 data: Some(models::principal_or_resource_constraint::Data::Eq(
295 models::principal_or_resource_constraint::EqMessage {
296 er: Some(models::EntityReference::from(er)),
297 },
298 )),
299 },
300 ast::PrincipalOrResourceConstraint::Is(na) => Self {
301 data: Some(models::principal_or_resource_constraint::Data::Is(
302 models::principal_or_resource_constraint::IsMessage {
303 entity_type: Some(models::Name::from(na.as_ref())),
304 },
305 )),
306 },
307 ast::PrincipalOrResourceConstraint::IsIn(na, er) => Self {
308 data: Some(models::principal_or_resource_constraint::Data::IsIn(
309 models::principal_or_resource_constraint::IsInMessage {
310 er: Some(models::EntityReference::from(er)),
311 entity_type: Some(models::Name::from(na.as_ref())),
312 },
313 )),
314 },
315 }
316 }
317}
318
319impl From<&models::ActionConstraint> for ast::ActionConstraint {
320 #[expect(clippy::expect_used, reason = "experimental feature")]
321 fn from(v: &models::ActionConstraint) -> Self {
322 match v.data.as_ref().expect("data.as_ref()") {
323 models::action_constraint::Data::Any(unit) => {
324 match models::action_constraint::Any::try_from(*unit)
325 .expect("decode should succeed")
326 {
327 models::action_constraint::Any::Unit => ast::ActionConstraint::Any,
328 }
329 }
330 models::action_constraint::Data::In(msg) => ast::ActionConstraint::In(
331 msg.euids
332 .iter()
333 .map(|value| ast::EntityUID::from(value).into())
334 .collect(),
335 ),
336 models::action_constraint::Data::Eq(msg) => ast::ActionConstraint::Eq(
337 ast::EntityUID::from(msg.euid.as_ref().expect("euid field should exist")).into(),
338 ),
339 }
340 }
341}
342
343impl From<&ast::ActionConstraint> for models::ActionConstraint {
344 fn from(v: &ast::ActionConstraint) -> Self {
345 match v {
346 ast::ActionConstraint::Any => Self {
347 data: Some(models::action_constraint::Data::Any(
348 models::action_constraint::Any::Unit.into(),
349 )),
350 },
351 ast::ActionConstraint::In(euids) => {
352 let mut peuids: Vec<models::EntityUid> = Vec::with_capacity(euids.len());
353 for value in euids {
354 peuids.push(models::EntityUid::from(value.as_ref()));
355 }
356 Self {
357 data: Some(models::action_constraint::Data::In(
358 models::action_constraint::InMessage { euids: peuids },
359 )),
360 }
361 }
362 ast::ActionConstraint::Eq(euid) => Self {
363 data: Some(models::action_constraint::Data::Eq(
364 models::action_constraint::EqMessage {
365 euid: Some(models::EntityUid::from(euid.as_ref())),
366 },
367 )),
368 },
369 #[cfg(feature = "tolerant-ast")]
370 ast::ActionConstraint::ErrorConstraint =>
371 {
373 Self {
374 data: Some(models::action_constraint::Data::Any(
375 models::action_constraint::Any::Unit.into(),
376 )),
377 }
378 }
379 }
380 }
381}
382
383impl From<&models::Effect> for ast::Effect {
384 fn from(v: &models::Effect) -> Self {
385 match v {
386 models::Effect::Forbid => ast::Effect::Forbid,
387 models::Effect::Permit => ast::Effect::Permit,
388 }
389 }
390}
391
392impl From<&ast::Effect> for models::Effect {
393 fn from(v: &ast::Effect) -> Self {
394 match v {
395 ast::Effect::Permit => models::Effect::Permit,
396 ast::Effect::Forbid => models::Effect::Forbid,
397 }
398 }
399}
400
401impl From<&models::PolicySet> for ast::LiteralPolicySet {
402 #[expect(clippy::expect_used, reason = "experimental feature")]
403 fn from(v: &models::PolicySet) -> Self {
404 let templates = v.templates.iter().map(|tb| {
405 (
406 ast::PolicyID::from_string(&tb.id),
407 ast::Template::from(ast::TemplateBody::from(tb)),
408 )
409 });
410
411 let links = v.links.iter().map(|p| {
412 let id = if p.is_template_link {
415 p.link_id
416 .as_ref()
417 .expect("template link should have a link_id")
418 } else {
419 &p.template_id
420 };
421 (ast::PolicyID::from_string(id), ast::LiteralPolicy::from(p))
422 });
423
424 Self::new(templates, links)
425 }
426}
427
428impl From<&ast::LiteralPolicySet> for models::PolicySet {
429 fn from(v: &ast::LiteralPolicySet) -> Self {
430 let templates = v.templates().map(models::TemplateBody::from).collect();
431 let links = v.policies().map(models::Policy::from).collect();
432 Self { templates, links }
433 }
434}
435
436impl From<&ast::PolicySet> for models::PolicySet {
437 fn from(v: &ast::PolicySet) -> Self {
438 let templates = v.all_templates().map(models::TemplateBody::from).collect();
439 let links = v.policies().map(models::Policy::from).collect();
440 Self { templates, links }
441 }
442}
443
444impl TryFrom<&models::PolicySet> for ast::PolicySet {
445 type Error = ast::ReificationError;
446 fn try_from(pset: &models::PolicySet) -> Result<Self, Self::Error> {
447 ast::PolicySet::try_from(ast::LiteralPolicySet::from(pset))
448 }
449}
450
451#[cfg(test)]
452mod test {
453 use std::sync::Arc;
454
455 use super::*;
456
457 impl PartialOrd for models::Policy {
460 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
461 Some(self.cmp(other))
462 }
463 }
464 impl Ord for models::Policy {
465 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
466 self.link_id()
470 .cmp(other.link_id())
471 .then_with(|| self.template_id.cmp(&other.template_id))
472 }
473 }
474 impl Eq for models::TemplateBody {}
475 impl PartialOrd for models::TemplateBody {
476 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
477 Some(self.cmp(other))
478 }
479 }
480 impl Ord for models::TemplateBody {
481 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
482 self.id.cmp(&other.id)
486 }
487 }
488
489 #[test]
490 #[expect(clippy::too_many_lines, reason = "unit test code")]
491 fn policy_roundtrip() {
492 let annotation1 = ast::Annotation {
493 val: "".into(),
494 loc: None,
495 };
496
497 let annotation2 = ast::Annotation {
498 val: "Hello World".into(),
499 loc: None,
500 };
501
502 assert_eq!(
503 ast::Effect::Permit,
504 ast::Effect::from(&models::Effect::from(&ast::Effect::Permit))
505 );
506 assert_eq!(
507 ast::Effect::Forbid,
508 ast::Effect::from(&models::Effect::from(&ast::Effect::Forbid))
509 );
510
511 let er1 = ast::EntityReference::euid(Arc::new(
512 ast::EntityUID::with_eid_and_type("A", "foo").unwrap(),
513 ));
514 assert_eq!(
515 er1,
516 ast::EntityReference::from(&models::EntityReference::from(&er1))
517 );
518 assert_eq!(
519 ast::EntityReference::Slot(None),
520 ast::EntityReference::from(&models::EntityReference::from(
521 &ast::EntityReference::Slot(None)
522 ))
523 );
524
525 let read_euid = Arc::new(ast::EntityUID::with_eid_and_type("Action", "read").unwrap());
526 let write_euid = Arc::new(ast::EntityUID::with_eid_and_type("Action", "write").unwrap());
527 let ac1 = ast::ActionConstraint::Eq(read_euid.clone());
528 let ac2 = ast::ActionConstraint::In(vec![read_euid, write_euid]);
529 assert_eq!(
530 ast::ActionConstraint::Any,
531 ast::ActionConstraint::from(&models::ActionConstraint::from(
532 &ast::ActionConstraint::Any
533 ))
534 );
535 assert_eq!(
536 ac1,
537 ast::ActionConstraint::from(&models::ActionConstraint::from(&ac1))
538 );
539 assert_eq!(
540 ac2,
541 ast::ActionConstraint::from(&models::ActionConstraint::from(&ac2))
542 );
543
544 let euid1 = Arc::new(ast::EntityUID::with_eid_and_type("A", "friend").unwrap());
545 let name1 = Arc::new(ast::EntityType::from(
546 ast::Name::from_normalized_str("B::C::D").unwrap(),
547 ));
548 let prc1 = ast::PrincipalOrResourceConstraint::is_eq(euid1.clone());
549 let prc2 = ast::PrincipalOrResourceConstraint::is_in(euid1.clone());
550 let prc3 = ast::PrincipalOrResourceConstraint::is_entity_type(name1.clone());
551 let prc4 = ast::PrincipalOrResourceConstraint::is_entity_type_in(name1, euid1);
552 assert_eq!(
553 ast::PrincipalOrResourceConstraint::any(),
554 ast::PrincipalOrResourceConstraint::from(&models::PrincipalOrResourceConstraint::from(
555 &ast::PrincipalOrResourceConstraint::any()
556 ))
557 );
558 assert_eq!(
559 prc1,
560 ast::PrincipalOrResourceConstraint::from(&models::PrincipalOrResourceConstraint::from(
561 &prc1
562 ))
563 );
564 assert_eq!(
565 prc2,
566 ast::PrincipalOrResourceConstraint::from(&models::PrincipalOrResourceConstraint::from(
567 &prc2
568 ))
569 );
570 assert_eq!(
571 prc3,
572 ast::PrincipalOrResourceConstraint::from(&models::PrincipalOrResourceConstraint::from(
573 &prc3
574 ))
575 );
576 assert_eq!(
577 prc4,
578 ast::PrincipalOrResourceConstraint::from(&models::PrincipalOrResourceConstraint::from(
579 &prc4
580 ))
581 );
582
583 let pc = ast::PrincipalConstraint::new(prc1);
584 let rc = ast::ResourceConstraint::new(prc3);
585 assert_eq!(
586 pc,
587 ast::PrincipalConstraint::from(&models::PrincipalOrResourceConstraint::from(&pc))
588 );
589 assert_eq!(
590 rc,
591 ast::ResourceConstraint::from(&models::PrincipalOrResourceConstraint::from(&rc))
592 );
593
594 assert_eq!(
595 ast::Effect::Permit,
596 ast::Effect::from(&models::Effect::from(&ast::Effect::Permit))
597 );
598 assert_eq!(
599 ast::Effect::Forbid,
600 ast::Effect::from(&models::Effect::from(&ast::Effect::Forbid))
601 );
602
603 let tb = ast::TemplateBody::new(
604 ast::PolicyID::from_string("template"),
605 None,
606 ast::Annotations::from_iter([
607 (
608 ast::AnyId::from_normalized_str("read").unwrap(),
609 annotation1,
610 ),
611 (
612 ast::AnyId::from_normalized_str("write").unwrap(),
613 annotation2,
614 ),
615 ]),
616 ast::Effect::Permit,
617 pc.clone(),
618 ac1.clone(),
619 rc.clone(),
620 None,
621 );
622 assert_eq!(
623 tb,
624 ast::TemplateBody::from(&models::TemplateBody::from(&tb))
625 );
626
627 let policy = ast::LiteralPolicy::template_linked_policy(
628 ast::PolicyID::from_string("template"),
629 ast::PolicyID::from_string("id"),
630 HashMap::from_iter([(
631 ast::SlotId::principal(),
632 ast::EntityUID::with_eid_and_type("A", "eid").unwrap(),
633 )]),
634 );
635 assert_eq!(
636 policy,
637 ast::LiteralPolicy::from(&models::Policy::from(&policy))
638 );
639
640 let tb = ast::TemplateBody::new(
641 ast::PolicyID::from_string("\0\n \' \"+-$^!"),
642 None,
643 ast::Annotations::from_iter([]),
644 ast::Effect::Permit,
645 pc,
646 ac1,
647 rc,
648 None,
649 );
650 assert_eq!(
651 tb,
652 ast::TemplateBody::from(&models::TemplateBody::from(&tb))
653 );
654
655 let policy = ast::LiteralPolicy::template_linked_policy(
656 ast::PolicyID::from_string("template\0\n \' \"+-$^!"),
657 ast::PolicyID::from_string("link\0\n \' \"+-$^!"),
658 HashMap::from_iter([(
659 ast::SlotId::principal(),
660 ast::EntityUID::with_eid_and_type("A", "eid").unwrap(),
661 )]),
662 );
663 assert_eq!(
664 policy,
665 ast::LiteralPolicy::from(&models::Policy::from(&policy))
666 );
667 }
668
669 #[test]
670 fn policyset_roundtrip() {
671 let tb = ast::TemplateBody::new(
672 ast::PolicyID::from_string("template"),
673 None,
674 ast::Annotations::from_iter(vec![(
675 ast::AnyId::from_normalized_str("read").unwrap(),
676 ast::Annotation {
677 val: "".into(),
678 loc: None,
679 },
680 )]),
681 ast::Effect::Permit,
682 ast::PrincipalConstraint::is_eq_slot(),
683 ast::ActionConstraint::Eq(
684 ast::EntityUID::with_eid_and_type("Action", "read")
685 .unwrap()
686 .into(),
687 ),
688 ast::ResourceConstraint::is_entity_type(
689 ast::EntityType::from(ast::Name::from_normalized_str("photo").unwrap()).into(),
690 ),
691 None,
692 );
693
694 let policy1 = ast::Policy::from_when_clause(
695 ast::Effect::Permit,
696 ast::Expr::val(true),
697 ast::PolicyID::from_string("permit-true-trivial"),
698 None,
699 );
700 let policy2 = ast::Policy::from_when_clause(
701 ast::Effect::Forbid,
702 ast::Expr::is_eq(
703 ast::Expr::var(ast::Var::Principal),
704 ast::Expr::val(ast::EntityUID::with_eid_and_type("A", "dog").unwrap()),
705 ),
706 ast::PolicyID::from_string("forbid-dog"),
707 None,
708 );
709
710 let mut ps = ast::PolicySet::new();
711 ps.add_template(ast::Template::from(tb))
712 .expect("Failed to add template to policy set.");
713 ps.add(policy1).expect("Failed to add policy to policy set");
714 ps.add(policy2).expect("Failed to add policy to policy set");
715 ps.link(
716 ast::PolicyID::from_string("template"),
717 ast::PolicyID::from_string("link"),
718 HashMap::from_iter([(
719 ast::SlotId::principal(),
720 ast::EntityUID::with_eid_and_type("A", "friend").unwrap(),
721 )]),
722 )
723 .unwrap();
724 let mut mps = models::PolicySet::from(&ps);
725 let mut mps_roundtrip = models::PolicySet::from(&ast::LiteralPolicySet::from(&mps));
726
727 mps.templates.sort();
730 mps_roundtrip.templates.sort();
731 mps.links.sort();
732 mps_roundtrip.links.sort();
733
734 assert_eq!(mps.templates, mps_roundtrip.templates);
736 assert_eq!(mps.links, mps_roundtrip.links);
737 }
738
739 #[test]
740 fn policyset_roundtrip_escapes() {
741 let tb = ast::TemplateBody::new(
742 ast::PolicyID::from_string("template\0\n \' \"+-$^!"),
743 None,
744 ast::Annotations::from_iter(vec![(
745 ast::AnyId::from_normalized_str("read").unwrap(),
746 ast::Annotation {
747 val: "".into(),
748 loc: None,
749 },
750 )]),
751 ast::Effect::Permit,
752 ast::PrincipalConstraint::is_eq_slot(),
753 ast::ActionConstraint::Eq(
754 ast::EntityUID::with_eid_and_type("Action", "read")
755 .unwrap()
756 .into(),
757 ),
758 ast::ResourceConstraint::is_entity_type(
759 ast::EntityType::from(ast::Name::from_normalized_str("photo").unwrap()).into(),
760 ),
761 None,
762 );
763
764 let policy1 = ast::Policy::from_when_clause(
765 ast::Effect::Permit,
766 ast::Expr::val(true),
767 ast::PolicyID::from_string("permit-true-trivial\0\n \' \"+-$^!"),
768 None,
769 );
770 let policy2 = ast::Policy::from_when_clause(
771 ast::Effect::Forbid,
772 ast::Expr::is_eq(
773 ast::Expr::var(ast::Var::Principal),
774 ast::Expr::val(ast::EntityUID::with_eid_and_type("A", "dog").unwrap()),
775 ),
776 ast::PolicyID::from_string("forbid-dog\0\n \' \"+-$^!"),
777 None,
778 );
779
780 let mut ps = ast::PolicySet::new();
781 ps.add_template(ast::Template::from(tb))
782 .expect("Failed to add template to policy set.");
783 ps.add(policy1).expect("Failed to add policy to policy set");
784 ps.add(policy2).expect("Failed to add policy to policy set");
785 ps.link(
786 ast::PolicyID::from_string("template\0\n \' \"+-$^!"),
787 ast::PolicyID::from_string("link\0\n \' \"+-$^!"),
788 HashMap::from_iter([(
789 ast::SlotId::principal(),
790 ast::EntityUID::with_eid_and_type("A", "friend").unwrap(),
791 )]),
792 )
793 .unwrap();
794 let mut mps = models::PolicySet::from(&ps);
795 let mut mps_roundtrip = models::PolicySet::from(&ast::LiteralPolicySet::from(&mps));
796
797 mps.templates.sort();
800 mps_roundtrip.templates.sort();
801 mps.links.sort();
802 mps_roundtrip.links.sort();
803
804 assert_eq!(mps.templates, mps_roundtrip.templates);
806 assert_eq!(mps.links, mps_roundtrip.links);
807 }
808}