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::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// PANIC SAFETY: experimental feature
47#[allow(clippy::fallible_impl_from)]
48impl From<&models::Name> for ast::InternalName {
49    // PANIC SAFETY: experimental feature
50    #[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// PANIC SAFETY: experimental feature
62#[allow(clippy::fallible_impl_from)]
63impl From<&models::Name> for ast::Name {
64    // PANIC SAFETY: experimental feature
65    #[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    // PANIC SAFETY: experimental feature
103    #[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    // PANIC SAFETY: experimental feature
125    #[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        // PANIC SAFETY: experimental feature
147        #[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    // PANIC SAFETY: experimental feature
157    #[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    // PANIC SAFETY: experimental feature
174    #[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    // PANIC SAFETY: experimental feature
252    #[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    // PANIC SAFETY: experimental feature
385    #[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    // PANIC SAFETY: experimental feature
519    #[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// PANIC SAFETY: experimental feature
563#[allow(clippy::fallible_impl_from)]
564impl From<&ast::SlotId> for models::SlotId {
565    // PANIC SAFETY: experimental feature
566    #[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    // PANIC SAFETY: experimental feature
638    #[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    // PANIC SAFETY: experimental feature
673    #[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        // PANIC SAFETY: experimental feature
698        #[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}