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