1#![allow(clippy::use_self)]
18
19use super::models;
20use cedar_policy_core::{
21 ast, evaluator::RestrictedEvaluator, extensions::Extensions, FromNormalizedStr,
22};
23use smol_str::SmolStr;
24use std::{
25 collections::{BTreeMap, HashSet},
26 sync::Arc,
27};
28
29impl From<&models::Annotation> for ast::Annotation {
30 fn from(v: &models::Annotation) -> Self {
31 Self {
32 val: v.val.clone().into(),
33 loc: None,
34 }
35 }
36}
37
38impl From<&ast::Annotation> for models::Annotation {
39 fn from(v: &ast::Annotation) -> Self {
40 Self {
41 val: v.val.to_string(),
42 }
43 }
44}
45
46#[allow(clippy::fallible_impl_from)]
48impl From<&models::Name> for ast::InternalName {
49 #[allow(clippy::unwrap_used)]
51 fn from(v: &models::Name) -> Self {
52 let basename = ast::Id::from_normalized_str(&v.id).unwrap();
53 let path = v
54 .path
55 .iter()
56 .map(|id| ast::Id::from_normalized_str(id).unwrap());
57 ast::InternalName::new(basename, path, None)
58 }
59}
60
61#[allow(clippy::fallible_impl_from)]
63impl From<&models::Name> for ast::Name {
64 #[allow(clippy::unwrap_used)]
66 fn from(v: &models::Name) -> Self {
67 ast::Name::try_from(ast::InternalName::from(v)).unwrap()
68 }
69}
70
71impl From<&models::Name> for ast::EntityType {
72 fn from(v: &models::Name) -> Self {
73 ast::EntityType::from(ast::Name::from(v))
74 }
75}
76
77impl From<&ast::InternalName> for models::Name {
78 fn from(v: &ast::InternalName) -> Self {
79 Self {
80 id: v.basename().to_string(),
81 path: v
82 .namespace_components()
83 .map(|id| String::from(id.as_ref()))
84 .collect(),
85 }
86 }
87}
88
89impl From<&ast::Name> for models::Name {
90 fn from(v: &ast::Name) -> Self {
91 Self::from(v.as_ref())
92 }
93}
94
95impl From<&ast::EntityType> for models::Name {
96 fn from(v: &ast::EntityType) -> Self {
97 Self::from(v.as_ref())
98 }
99}
100
101impl From<&models::EntityType> for ast::Name {
102 #[allow(clippy::expect_used, clippy::fallible_impl_from)]
104 fn from(v: &models::EntityType) -> Self {
105 Self::from(v.name.as_ref().expect("name field should exist"))
106 }
107}
108
109impl From<&models::EntityType> for ast::EntityType {
110 fn from(v: &models::EntityType) -> Self {
111 Self::from(ast::Name::from(v))
112 }
113}
114
115impl From<&ast::EntityType> for models::EntityType {
116 fn from(v: &ast::EntityType) -> Self {
117 Self {
118 name: Some(models::Name::from(v.name())),
119 }
120 }
121}
122
123impl From<&models::EntityUid> for ast::EntityUID {
124 #[allow(clippy::expect_used)]
126 fn from(v: &models::EntityUid) -> Self {
127 Self::from_components(
128 ast::EntityType::from(v.ty.as_ref().expect("ty field should exist")),
129 ast::Eid::new(v.eid.clone()),
130 None,
131 )
132 }
133}
134
135impl From<&ast::EntityUID> for models::EntityUid {
136 fn from(v: &ast::EntityUID) -> Self {
137 Self {
138 ty: Some(models::EntityType::from(v.entity_type())),
139 eid: <ast::Eid as AsRef<str>>::as_ref(v.eid()).into(),
140 }
141 }
142}
143
144impl From<&models::EntityUidEntry> for ast::EntityUIDEntry {
145 fn from(v: &models::EntityUidEntry) -> Self {
146 #[allow(clippy::expect_used)]
148 ast::EntityUIDEntry::known(
149 ast::EntityUID::from(v.euid.as_ref().expect("euid field should exist")),
150 None,
151 )
152 }
153}
154
155impl From<&ast::EntityUIDEntry> for models::EntityUidEntry {
156 #[allow(clippy::unimplemented)]
158 fn from(v: &ast::EntityUIDEntry) -> Self {
159 match v {
160 ast::EntityUIDEntry::Unknown { .. } => {
161 unimplemented!(
162 "Unknown EntityUID is not currently supported by the Protobuf interface"
163 );
164 }
165 ast::EntityUIDEntry::Known { euid, .. } => Self {
166 euid: Some(models::EntityUid::from(euid.as_ref())),
167 },
168 }
169 }
170}
171
172impl From<&models::Entity> for ast::Entity {
173 #[allow(clippy::expect_used, clippy::unwrap_used)]
175 fn from(v: &models::Entity) -> Self {
176 let eval = RestrictedEvaluator::new(Extensions::none());
177
178 let attrs: BTreeMap<SmolStr, ast::PartialValueSerializedAsExpr> = v
179 .attrs
180 .iter()
181 .map(|(key, value)| {
182 let pval = eval
183 .partial_interpret(
184 ast::BorrowedRestrictedExpr::new(&ast::Expr::from(value)).unwrap(),
185 )
186 .expect("interpret on RestrictedExpr");
187 (key.into(), pval.into())
188 })
189 .collect();
190
191 let ancestors: HashSet<ast::EntityUID> =
192 v.ancestors.iter().map(ast::EntityUID::from).collect();
193
194 let tags: BTreeMap<SmolStr, ast::PartialValueSerializedAsExpr> = v
195 .tags
196 .iter()
197 .map(|(key, value)| {
198 let pval = eval
199 .partial_interpret(
200 ast::BorrowedRestrictedExpr::new(&ast::Expr::from(value))
201 .expect("RestrictedExpr"),
202 )
203 .expect("interpret on RestrictedExpr");
204 (key.into(), pval.into())
205 })
206 .collect();
207
208 Self::new_with_attr_partial_value_serialized_as_expr(
209 ast::EntityUID::from(v.uid.as_ref().expect("uid field should exist")),
210 attrs,
211 ancestors,
212 tags,
213 )
214 }
215}
216
217impl From<&ast::Entity> for models::Entity {
218 fn from(v: &ast::Entity) -> Self {
219 Self {
220 uid: Some(models::EntityUid::from(v.uid())),
221 attrs: v
222 .attrs()
223 .map(|(key, value)| {
224 (
225 key.to_string(),
226 models::Expr::from(&ast::Expr::from(value.clone())),
227 )
228 })
229 .collect(),
230 ancestors: v.ancestors().map(models::EntityUid::from).collect(),
231 tags: v
232 .tags()
233 .map(|(key, value)| {
234 (
235 key.to_string(),
236 models::Expr::from(&ast::Expr::from(value.clone())),
237 )
238 })
239 .collect(),
240 }
241 }
242}
243
244impl From<&Arc<ast::Entity>> for models::Entity {
245 fn from(v: &Arc<ast::Entity>) -> Self {
246 Self::from(v.as_ref())
247 }
248}
249
250impl From<&models::Expr> for ast::Expr {
251 #[allow(clippy::expect_used, clippy::too_many_lines)]
253 fn from(v: &models::Expr) -> Self {
254 let pdata = v.expr_kind.as_ref().expect("expr_kind field should exist");
255 let ety = pdata.data.as_ref().expect("data field should exist");
256
257 match ety {
258 models::expr::expr_kind::Data::Lit(lit) => ast::Expr::val(ast::Literal::from(lit)),
259
260 models::expr::expr_kind::Data::Var(var) => {
261 let pvar =
262 models::expr::Var::try_from(var.to_owned()).expect("decode should succeed");
263 ast::Expr::var(ast::Var::from(&pvar))
264 }
265
266 models::expr::expr_kind::Data::Slot(slot) => {
267 let pslot =
268 models::SlotId::try_from(slot.to_owned()).expect("decode should succeed");
269 ast::Expr::slot(ast::SlotId::from(&pslot))
270 }
271
272 models::expr::expr_kind::Data::If(msg) => {
273 let test_expr = msg
274 .test_expr
275 .as_ref()
276 .expect("test_expr field should exist")
277 .as_ref();
278 let then_expr = msg
279 .then_expr
280 .as_ref()
281 .expect("then_expr field should exist")
282 .as_ref();
283 let else_expr = msg
284 .else_expr
285 .as_ref()
286 .expect("else_expr field should exist")
287 .as_ref();
288 ast::Expr::ite(
289 ast::Expr::from(test_expr),
290 ast::Expr::from(then_expr),
291 ast::Expr::from(else_expr),
292 )
293 }
294
295 models::expr::expr_kind::Data::And(msg) => {
296 let left = msg.left.as_ref().expect("left field should exist").as_ref();
297 let right = msg
298 .right
299 .as_ref()
300 .expect("right field should exist")
301 .as_ref();
302 ast::Expr::and(ast::Expr::from(left), ast::Expr::from(right))
303 }
304
305 models::expr::expr_kind::Data::Or(msg) => {
306 let left = msg.left.as_ref().expect("left field should exist").as_ref();
307 let right = msg
308 .right
309 .as_ref()
310 .expect("right field should exist")
311 .as_ref();
312 ast::Expr::or(ast::Expr::from(left), ast::Expr::from(right))
313 }
314
315 models::expr::expr_kind::Data::UApp(msg) => {
316 let arg = msg.expr.as_ref().expect("expr field should exist").as_ref();
317 let puop =
318 models::expr::unary_app::Op::try_from(msg.op).expect("decode should succeed");
319 ast::Expr::unary_app(ast::UnaryOp::from(&puop), ast::Expr::from(arg))
320 }
321
322 models::expr::expr_kind::Data::BApp(msg) => {
323 let pbop =
324 models::expr::binary_app::Op::try_from(msg.op).expect("decode should succeed");
325 let left = msg.left.as_ref().expect("left field should exist");
326 let right = msg.right.as_ref().expect("right field should exist");
327 ast::Expr::binary_app(
328 ast::BinaryOp::from(&pbop),
329 ast::Expr::from(left.as_ref()),
330 ast::Expr::from(right.as_ref()),
331 )
332 }
333
334 models::expr::expr_kind::Data::ExtApp(msg) => ast::Expr::call_extension_fn(
335 ast::Name::from(msg.fn_name.as_ref().expect("fn_name field should exist")),
336 msg.args.iter().map(ast::Expr::from).collect(),
337 ),
338
339 models::expr::expr_kind::Data::GetAttr(msg) => {
340 let arg = msg.expr.as_ref().expect("expr field should exist").as_ref();
341 ast::Expr::get_attr(ast::Expr::from(arg), msg.attr.clone().into())
342 }
343
344 models::expr::expr_kind::Data::HasAttr(msg) => {
345 let arg = msg.expr.as_ref().expect("expr field should exist").as_ref();
346 ast::Expr::has_attr(ast::Expr::from(arg), msg.attr.clone().into())
347 }
348
349 models::expr::expr_kind::Data::Like(msg) => {
350 let arg = msg.expr.as_ref().expect("expr field should exist").as_ref();
351 ast::Expr::like(
352 ast::Expr::from(arg),
353 msg.pattern.iter().map(ast::PatternElem::from).collect(),
354 )
355 }
356
357 models::expr::expr_kind::Data::Is(msg) => {
358 let arg = msg.expr.as_ref().expect("expr field should exist").as_ref();
359 ast::Expr::is_entity_type(
360 ast::Expr::from(arg),
361 ast::EntityType::from(
362 msg.entity_type
363 .as_ref()
364 .expect("entity_type field should exist"),
365 ),
366 )
367 }
368
369 models::expr::expr_kind::Data::Set(msg) => {
370 ast::Expr::set(msg.elements.iter().map(ast::Expr::from))
371 }
372
373 models::expr::expr_kind::Data::Record(msg) => ast::Expr::record(
374 msg.items
375 .iter()
376 .map(|(key, value)| (key.into(), ast::Expr::from(value))),
377 )
378 .expect("Expr should be valid"),
379 }
380 }
381}
382
383impl From<&ast::Expr> for models::Expr {
384 #[allow(clippy::unimplemented, clippy::too_many_lines)]
386 fn from(v: &ast::Expr) -> Self {
387 let expr_kind = match v.expr_kind() {
388 ast::ExprKind::Lit(l) => {
389 models::expr::expr_kind::Data::Lit(models::expr::Literal::from(l))
390 }
391 ast::ExprKind::Var(v) => {
392 models::expr::expr_kind::Data::Var(models::expr::Var::from(v).into())
393 }
394 ast::ExprKind::Slot(sid) => {
395 models::expr::expr_kind::Data::Slot(models::SlotId::from(sid).into())
396 }
397
398 ast::ExprKind::Unknown(_u) => {
399 unimplemented!("Protobuffer interface does not support Unknown expressions")
400 }
401 ast::ExprKind::If {
402 test_expr,
403 then_expr,
404 else_expr,
405 } => models::expr::expr_kind::Data::If(Box::new(models::expr::If {
406 test_expr: Some(Box::new(models::Expr::from(test_expr.as_ref()))),
407 then_expr: Some(Box::new(models::Expr::from(then_expr.as_ref()))),
408 else_expr: Some(Box::new(models::Expr::from(else_expr.as_ref()))),
409 })),
410 ast::ExprKind::And { left, right } => {
411 models::expr::expr_kind::Data::And(Box::new(models::expr::And {
412 left: Some(Box::new(models::Expr::from(left.as_ref()))),
413 right: Some(Box::new(models::Expr::from(right.as_ref()))),
414 }))
415 }
416 ast::ExprKind::Or { left, right } => {
417 models::expr::expr_kind::Data::Or(Box::new(models::expr::Or {
418 left: Some(Box::new(models::Expr::from(left.as_ref()))),
419 right: Some(Box::new(models::Expr::from(right.as_ref()))),
420 }))
421 }
422 ast::ExprKind::UnaryApp { op, arg } => {
423 models::expr::expr_kind::Data::UApp(Box::new(models::expr::UnaryApp {
424 op: models::expr::unary_app::Op::from(op).into(),
425 expr: Some(Box::new(models::Expr::from(arg.as_ref()))),
426 }))
427 }
428 ast::ExprKind::BinaryApp { op, arg1, arg2 } => {
429 models::expr::expr_kind::Data::BApp(Box::new(models::expr::BinaryApp {
430 op: models::expr::binary_app::Op::from(op).into(),
431 left: Some(Box::new(models::Expr::from(arg1.as_ref()))),
432 right: Some(Box::new(models::Expr::from(arg2.as_ref()))),
433 }))
434 }
435 ast::ExprKind::ExtensionFunctionApp { fn_name, args } => {
436 let pargs: Vec<models::Expr> = args.iter().map(models::Expr::from).collect();
437 models::expr::expr_kind::Data::ExtApp(models::expr::ExtensionFunctionApp {
438 fn_name: Some(models::Name::from(fn_name)),
439 args: pargs,
440 })
441 }
442 ast::ExprKind::GetAttr { expr, attr } => {
443 models::expr::expr_kind::Data::GetAttr(Box::new(models::expr::GetAttr {
444 attr: attr.to_string(),
445 expr: Some(Box::new(models::Expr::from(expr.as_ref()))),
446 }))
447 }
448 ast::ExprKind::HasAttr { expr, attr } => {
449 models::expr::expr_kind::Data::HasAttr(Box::new(models::expr::HasAttr {
450 attr: attr.to_string(),
451 expr: Some(Box::new(models::Expr::from(expr.as_ref()))),
452 }))
453 }
454 ast::ExprKind::Like { expr, pattern } => {
455 let mut ppattern: Vec<models::expr::like::PatternElem> =
456 Vec::with_capacity(pattern.len());
457 for value in pattern.iter() {
458 ppattern.push(models::expr::like::PatternElem::from(value));
459 }
460 models::expr::expr_kind::Data::Like(Box::new(models::expr::Like {
461 expr: Some(Box::new(models::Expr::from(expr.as_ref()))),
462 pattern: ppattern,
463 }))
464 }
465 ast::ExprKind::Is { expr, entity_type } => {
466 models::expr::expr_kind::Data::Is(Box::new(models::expr::Is {
467 expr: Some(Box::new(models::Expr::from(expr.as_ref()))),
468 entity_type: Some(models::EntityType::from(entity_type)),
469 }))
470 }
471 ast::ExprKind::Set(args) => {
472 let mut pargs: Vec<models::Expr> = Vec::with_capacity(args.as_ref().len());
473 for arg in args.as_ref() {
474 pargs.push(models::Expr::from(arg));
475 }
476 models::expr::expr_kind::Data::Set(models::expr::Set { elements: pargs })
477 }
478 ast::ExprKind::Record(record) => {
479 let precord = record
480 .as_ref()
481 .iter()
482 .map(|(key, value)| (key.to_string(), models::Expr::from(value)))
483 .collect();
484 models::expr::expr_kind::Data::Record(models::expr::Record { items: precord })
485 }
486 };
487 Self {
488 expr_kind: Some(Box::new(models::expr::ExprKind {
489 data: Some(expr_kind),
490 })),
491 }
492 }
493}
494
495impl From<&models::expr::Var> for ast::Var {
496 fn from(v: &models::expr::Var) -> Self {
497 match v {
498 models::expr::Var::Principal => ast::Var::Principal,
499 models::expr::Var::Action => ast::Var::Action,
500 models::expr::Var::Resource => ast::Var::Resource,
501 models::expr::Var::Context => ast::Var::Context,
502 }
503 }
504}
505
506impl From<&ast::Var> for models::expr::Var {
507 fn from(v: &ast::Var) -> Self {
508 match v {
509 ast::Var::Principal => models::expr::Var::Principal,
510 ast::Var::Action => models::expr::Var::Action,
511 ast::Var::Resource => models::expr::Var::Resource,
512 ast::Var::Context => models::expr::Var::Context,
513 }
514 }
515}
516
517impl From<&models::expr::Literal> for ast::Literal {
518 #[allow(clippy::expect_used)]
520 fn from(v: &models::expr::Literal) -> Self {
521 match v.lit.as_ref().expect("lit field should exist") {
522 models::expr::literal::Lit::B(b) => ast::Literal::Bool(*b),
523 models::expr::literal::Lit::I(l) => ast::Literal::Long(*l),
524 models::expr::literal::Lit::S(s) => ast::Literal::String(s.clone().into()),
525 models::expr::literal::Lit::Euid(e) => {
526 ast::Literal::EntityUID(ast::EntityUID::from(e).into())
527 }
528 }
529 }
530}
531
532impl From<&ast::Literal> for models::expr::Literal {
533 fn from(v: &ast::Literal) -> Self {
534 match v {
535 ast::Literal::Bool(b) => Self {
536 lit: Some(models::expr::literal::Lit::B(*b)),
537 },
538 ast::Literal::Long(l) => Self {
539 lit: Some(models::expr::literal::Lit::I(*l)),
540 },
541 ast::Literal::String(s) => Self {
542 lit: Some(models::expr::literal::Lit::S(s.to_string())),
543 },
544 ast::Literal::EntityUID(euid) => Self {
545 lit: Some(models::expr::literal::Lit::Euid(models::EntityUid::from(
546 euid.as_ref(),
547 ))),
548 },
549 }
550 }
551}
552
553impl From<&models::SlotId> for ast::SlotId {
554 fn from(v: &models::SlotId) -> Self {
555 match v {
556 models::SlotId::Principal => ast::SlotId::principal(),
557 models::SlotId::Resource => ast::SlotId::resource(),
558 }
559 }
560}
561
562#[allow(clippy::fallible_impl_from)]
564impl From<&ast::SlotId> for models::SlotId {
565 #[allow(clippy::panic)]
567 fn from(v: &ast::SlotId) -> Self {
568 if v.is_principal() {
569 models::SlotId::Principal
570 } else if v.is_resource() {
571 models::SlotId::Resource
572 } else {
573 panic!("Slot other than principal or resource")
574 }
575 }
576}
577
578impl From<&models::expr::unary_app::Op> for ast::UnaryOp {
579 fn from(v: &models::expr::unary_app::Op) -> Self {
580 match v {
581 models::expr::unary_app::Op::Not => ast::UnaryOp::Not,
582 models::expr::unary_app::Op::Neg => ast::UnaryOp::Neg,
583 models::expr::unary_app::Op::IsEmpty => ast::UnaryOp::IsEmpty,
584 }
585 }
586}
587
588impl From<&ast::UnaryOp> for models::expr::unary_app::Op {
589 fn from(v: &ast::UnaryOp) -> Self {
590 match v {
591 ast::UnaryOp::Not => models::expr::unary_app::Op::Not,
592 ast::UnaryOp::Neg => models::expr::unary_app::Op::Neg,
593 ast::UnaryOp::IsEmpty => models::expr::unary_app::Op::IsEmpty,
594 }
595 }
596}
597
598impl From<&models::expr::binary_app::Op> for ast::BinaryOp {
599 fn from(v: &models::expr::binary_app::Op) -> Self {
600 match v {
601 models::expr::binary_app::Op::Eq => ast::BinaryOp::Eq,
602 models::expr::binary_app::Op::Less => ast::BinaryOp::Less,
603 models::expr::binary_app::Op::LessEq => ast::BinaryOp::LessEq,
604 models::expr::binary_app::Op::Add => ast::BinaryOp::Add,
605 models::expr::binary_app::Op::Sub => ast::BinaryOp::Sub,
606 models::expr::binary_app::Op::Mul => ast::BinaryOp::Mul,
607 models::expr::binary_app::Op::In => ast::BinaryOp::In,
608 models::expr::binary_app::Op::Contains => ast::BinaryOp::Contains,
609 models::expr::binary_app::Op::ContainsAll => ast::BinaryOp::ContainsAll,
610 models::expr::binary_app::Op::ContainsAny => ast::BinaryOp::ContainsAny,
611 models::expr::binary_app::Op::GetTag => ast::BinaryOp::GetTag,
612 models::expr::binary_app::Op::HasTag => ast::BinaryOp::HasTag,
613 }
614 }
615}
616
617impl From<&ast::BinaryOp> for models::expr::binary_app::Op {
618 fn from(v: &ast::BinaryOp) -> Self {
619 match v {
620 ast::BinaryOp::Eq => models::expr::binary_app::Op::Eq,
621 ast::BinaryOp::Less => models::expr::binary_app::Op::Less,
622 ast::BinaryOp::LessEq => models::expr::binary_app::Op::LessEq,
623 ast::BinaryOp::Add => models::expr::binary_app::Op::Add,
624 ast::BinaryOp::Sub => models::expr::binary_app::Op::Sub,
625 ast::BinaryOp::Mul => models::expr::binary_app::Op::Mul,
626 ast::BinaryOp::In => models::expr::binary_app::Op::In,
627 ast::BinaryOp::Contains => models::expr::binary_app::Op::Contains,
628 ast::BinaryOp::ContainsAll => models::expr::binary_app::Op::ContainsAll,
629 ast::BinaryOp::ContainsAny => models::expr::binary_app::Op::ContainsAny,
630 ast::BinaryOp::GetTag => models::expr::binary_app::Op::GetTag,
631 ast::BinaryOp::HasTag => models::expr::binary_app::Op::HasTag,
632 }
633 }
634}
635
636impl From<&models::expr::like::PatternElem> for ast::PatternElem {
637 #[allow(clippy::expect_used)]
639 fn from(v: &models::expr::like::PatternElem) -> Self {
640 match v.data.as_ref().expect("data field should exist") {
641 models::expr::like::pattern_elem::Data::C(c) => {
642 ast::PatternElem::Char(c.chars().next().expect("c is non-empty"))
643 }
644
645 models::expr::like::pattern_elem::Data::Ty(ty) => {
646 match models::expr::like::pattern_elem::Ty::try_from(ty.to_owned())
647 .expect("decode should succeed")
648 {
649 models::expr::like::pattern_elem::Ty::Wildcard => ast::PatternElem::Wildcard,
650 }
651 }
652 }
653 }
654}
655
656impl From<&ast::PatternElem> for models::expr::like::PatternElem {
657 fn from(v: &ast::PatternElem) -> Self {
658 match v {
659 ast::PatternElem::Char(c) => Self {
660 data: Some(models::expr::like::pattern_elem::Data::C(c.to_string())),
661 },
662 ast::PatternElem::Wildcard => Self {
663 data: Some(models::expr::like::pattern_elem::Data::Ty(
664 models::expr::like::pattern_elem::Ty::Wildcard.into(),
665 )),
666 },
667 }
668 }
669}
670
671impl From<&models::Request> for ast::Request {
672 #[allow(clippy::expect_used)]
674 fn from(v: &models::Request) -> Self {
675 ast::Request::new_unchecked(
676 ast::EntityUIDEntry::from(v.principal.as_ref().expect("principal.as_ref()")),
677 ast::EntityUIDEntry::from(v.action.as_ref().expect("action.as_ref()")),
678 ast::EntityUIDEntry::from(v.resource.as_ref().expect("resource.as_ref()")),
679 v.context.as_ref().map(ast::Context::from),
680 )
681 }
682}
683
684impl From<&ast::Request> for models::Request {
685 fn from(v: &ast::Request) -> Self {
686 Self {
687 principal: Some(models::EntityUidEntry::from(v.principal())),
688 action: Some(models::EntityUidEntry::from(v.action())),
689 resource: Some(models::EntityUidEntry::from(v.resource())),
690 context: v.context().map(models::Context::from),
691 }
692 }
693}
694
695impl From<&models::Context> for ast::Context {
696 fn from(v: &models::Context) -> Self {
697 #[allow(clippy::expect_used)]
699 ast::Context::from_expr(
700 ast::BorrowedRestrictedExpr::new(&ast::Expr::from(
701 v.context.as_ref().expect("context.as_ref()"),
702 ))
703 .expect("Expr::from"),
704 Extensions::none(),
705 )
706 .expect("Context::from_expr")
707 }
708}
709
710impl From<&ast::Context> for models::Context {
711 fn from(v: &ast::Context) -> Self {
712 Self {
713 context: Some(models::Expr::from(&ast::Expr::from(
714 ast::PartialValue::from(v.to_owned()),
715 ))),
716 }
717 }
718}
719
720#[cfg(test)]
721mod test {
722 use std::collections::HashMap;
723
724 use super::*;
725
726 #[test]
727 fn entity_roundtrip() {
728 let name = ast::Name::from_normalized_str("B::C::D").unwrap();
729 let ety_specified = ast::EntityType::from(name);
730 assert_eq!(
731 ety_specified,
732 ast::EntityType::from(&models::EntityType::from(&ety_specified))
733 );
734
735 let euid1 = ast::EntityUID::with_eid_and_type("A", "foo").unwrap();
736 assert_eq!(
737 euid1,
738 ast::EntityUID::from(&models::EntityUid::from(&euid1))
739 );
740
741 let euid2 = ast::EntityUID::from_normalized_str("Foo::Action::\"view\"").unwrap();
742 assert_eq!(
743 euid2,
744 ast::EntityUID::from(&models::EntityUid::from(&euid2))
745 );
746
747 let euid3 = ast::EntityUID::from_components(
748 ast::EntityType::from_normalized_str("A").unwrap(),
749 ast::Eid::new("\0\n \' \"+-$^!"),
750 None,
751 );
752 assert_eq!(
753 euid3,
754 ast::EntityUID::from(&models::EntityUid::from(&euid3))
755 );
756
757 let attrs = (1..=7)
758 .map(|id| (format!("{id}").into(), ast::RestrictedExpr::val(true)))
759 .collect::<HashMap<SmolStr, _>>();
760 let entity = ast::Entity::new(
761 r#"Foo::"bar""#.parse().unwrap(),
762 attrs,
763 HashSet::new(),
764 BTreeMap::new(),
765 Extensions::none(),
766 )
767 .unwrap();
768 assert_eq!(entity, ast::Entity::from(&models::Entity::from(&entity)));
769 }
770
771 #[test]
772 fn expr_roundtrip() {
773 let e1 = ast::Expr::val(33);
774 assert_eq!(e1, ast::Expr::from(&models::Expr::from(&e1)));
775 let e2 = ast::Expr::val("hello");
776 assert_eq!(e2, ast::Expr::from(&models::Expr::from(&e2)));
777 let e3 = ast::Expr::val(ast::EntityUID::with_eid_and_type("A", "foo").unwrap());
778 assert_eq!(e3, ast::Expr::from(&models::Expr::from(&e3)));
779 let e4 = ast::Expr::var(ast::Var::Principal);
780 assert_eq!(e4, ast::Expr::from(&models::Expr::from(&e4)));
781 let e5 = ast::Expr::ite(
782 ast::Expr::val(true),
783 ast::Expr::val(88),
784 ast::Expr::val(-100),
785 );
786 assert_eq!(e5, ast::Expr::from(&models::Expr::from(&e5)));
787 let e6 = ast::Expr::not(ast::Expr::val(false));
788 assert_eq!(e6, ast::Expr::from(&models::Expr::from(&e6)));
789 let e7 = ast::Expr::get_attr(
790 ast::Expr::val(ast::EntityUID::with_eid_and_type("A", "foo").unwrap()),
791 "some_attr".into(),
792 );
793 assert_eq!(e7, ast::Expr::from(&models::Expr::from(&e7)));
794 let e8 = ast::Expr::has_attr(
795 ast::Expr::val(ast::EntityUID::with_eid_and_type("A", "foo").unwrap()),
796 "some_attr".into(),
797 );
798 assert_eq!(e8, ast::Expr::from(&models::Expr::from(&e8)));
799 let e9 = ast::Expr::is_entity_type(
800 ast::Expr::val(ast::EntityUID::with_eid_and_type("A", "foo").unwrap()),
801 "Type".parse().unwrap(),
802 );
803 assert_eq!(e9, ast::Expr::from(&models::Expr::from(&e9)));
804 }
805
806 #[test]
807 fn literal_roundtrip() {
808 let bool_literal_f = ast::Literal::from(false);
809 assert_eq!(
810 bool_literal_f,
811 ast::Literal::from(&models::expr::Literal::from(&bool_literal_f))
812 );
813
814 let bool_literal_t = ast::Literal::from(true);
815 assert_eq!(
816 bool_literal_t,
817 ast::Literal::from(&models::expr::Literal::from(&bool_literal_t))
818 );
819
820 let long_literal0 = ast::Literal::from(0);
821 assert_eq!(
822 long_literal0,
823 ast::Literal::from(&models::expr::Literal::from(&long_literal0))
824 );
825
826 let long_literal1 = ast::Literal::from(1);
827 assert_eq!(
828 long_literal1,
829 ast::Literal::from(&models::expr::Literal::from(&long_literal1))
830 );
831
832 let str_literal0 = ast::Literal::from("");
833 assert_eq!(
834 str_literal0,
835 ast::Literal::from(&models::expr::Literal::from(&str_literal0))
836 );
837
838 let str_literal1 = ast::Literal::from("foo");
839 assert_eq!(
840 str_literal1,
841 ast::Literal::from(&models::expr::Literal::from(&str_literal1))
842 );
843
844 let euid_literal =
845 ast::Literal::from(ast::EntityUID::with_eid_and_type("A", "foo").unwrap());
846 assert_eq!(
847 euid_literal,
848 ast::Literal::from(&models::expr::Literal::from(&euid_literal))
849 );
850 }
851
852 #[test]
853 fn name_and_slot_roundtrip() {
854 let orig_name = ast::Name::from_normalized_str("B::C::D").unwrap();
855 assert_eq!(orig_name, ast::Name::from(&models::Name::from(&orig_name)));
856
857 let orig_slot1 = ast::SlotId::principal();
858 assert_eq!(
859 orig_slot1,
860 ast::SlotId::from(&models::SlotId::from(&orig_slot1))
861 );
862
863 let orig_slot2 = ast::SlotId::resource();
864 assert_eq!(
865 orig_slot2,
866 ast::SlotId::from(&models::SlotId::from(&orig_slot2))
867 );
868 }
869
870 #[test]
871 fn request_roundtrip() {
872 let context = ast::Context::from_expr(
873 ast::RestrictedExpr::record([("foo".into(), ast::RestrictedExpr::val(37))])
874 .expect("Error creating restricted record.")
875 .as_borrowed(),
876 Extensions::none(),
877 )
878 .expect("Error creating context");
879 let request = ast::Request::new_unchecked(
880 ast::EntityUIDEntry::Known {
881 euid: Arc::new(ast::EntityUID::with_eid_and_type("User", "andrew").unwrap()),
882 loc: None,
883 },
884 ast::EntityUIDEntry::Known {
885 euid: Arc::new(ast::EntityUID::with_eid_and_type("Action", "read").unwrap()),
886 loc: None,
887 },
888 ast::EntityUIDEntry::Known {
889 euid: Arc::new(
890 ast::EntityUID::with_eid_and_type("Book", "tale of two cities").unwrap(),
891 ),
892 loc: None,
893 },
894 Some(context.clone()),
895 );
896 let request_rt = ast::Request::from(&models::Request::from(&request));
897 assert_eq!(
898 context,
899 ast::Context::from(&models::Context::from(&context))
900 );
901 assert_eq!(request.principal().uid(), request_rt.principal().uid());
902 assert_eq!(request.action().uid(), request_rt.action().uid());
903 assert_eq!(request.resource().uid(), request_rt.resource().uid());
904 }
905}