cedar_policy/proto/
ast.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#![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// PANIC SAFETY: experimental feature
27#[allow(clippy::fallible_impl_from)]
28impl From<&models::Name> for ast::InternalName {
29    // PANIC SAFETY: experimental feature
30    #[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// PANIC SAFETY: experimental feature
42#[allow(clippy::fallible_impl_from)]
43impl From<&models::Name> for ast::Name {
44    // PANIC SAFETY: experimental feature
45    #[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    // PANIC SAFETY: experimental feature
83    #[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        // PANIC SAFETY: experimental feature
105        #[allow(clippy::expect_used)]
106        ast::EntityUIDEntry::known(ast::EntityUID::from(v), None)
107    }
108}
109
110impl From<&ast::EntityUIDEntry> for models::EntityUid {
111    // PANIC SAFETY: experimental feature
112    #[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    // PANIC SAFETY: experimental feature
127    #[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    // PANIC SAFETY: experimental feature
198    #[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    // PANIC SAFETY: experimental feature
330    #[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    // PANIC SAFETY: experimental feature
470    #[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// PANIC SAFETY: experimental feature
514#[allow(clippy::fallible_impl_from)]
515impl From<&ast::SlotId> for models::SlotId {
516    // PANIC SAFETY: experimental feature
517    #[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    // PANIC SAFETY: experimental feature
589    #[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    // PANIC SAFETY: experimental feature
624    #[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    // PANIC SAFETY: experimental feature
649    #[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        // PANIC SAFETY: experimental feature
677        #[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}