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