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