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