Skip to main content

cedar_policy_core/parser/
fmt.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
17use std::fmt::{self, Write};
18
19use super::cst::*;
20use super::node::Node;
21
22/// Helper struct to handle non-existent nodes
23struct View<'a, T>(&'a Node<Option<T>>);
24impl<T: fmt::Display> fmt::Display for View<'_, T> {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        if let Some(n) = &self.0.as_inner() {
27            if f.alternate() {
28                write!(f, "{n:#}")
29            } else {
30                write!(f, "{n}")
31            }
32        } else {
33            write!(f, "[error]")
34        }
35    }
36}
37
38impl fmt::Display for Policies {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        let mut ps = self.0.iter();
41        if f.alternate() {
42            if let Some(p) = ps.next() {
43                write!(f, "{:#}", View(p))?;
44            }
45            for p in ps {
46                write!(f, "\n\n{:#}", View(p))?;
47            }
48        } else {
49            if let Some(p) = ps.next() {
50                write!(f, "{}", View(p))?;
51            }
52            for p in ps {
53                write!(f, " {}", View(p))?;
54            }
55        }
56        Ok(())
57    }
58}
59impl fmt::Display for Policy {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        #[cfg_attr(
62            not(feature = "tolerant-ast"),
63            expect(
64                clippy::infallible_destructuring_match,
65                reason = "this is not a destrucuring match when `toleran-ast` is enabled"
66            )
67        )]
68        let policy = match self {
69            Policy::Policy(p) => p,
70            #[cfg(feature = "tolerant-ast")]
71            Policy::PolicyError => {
72                writeln!(f, "Policy::PolicyError")?;
73                return Ok(());
74            }
75        };
76        // start with annotations
77        for anno in policy.annotations.iter() {
78            if f.alternate() {
79                // each annotation on a new line
80                writeln!(f, "{:#}", View(anno))?;
81            } else {
82                write!(f, "{} ", View(anno))?;
83            }
84        }
85        // main policy body
86        if f.alternate() {
87            write!(f, "{:#}(", View(&policy.effect))?;
88            let mut vars = policy.variables.iter();
89            // if at least one var ...
90            if let Some(v) = vars.next() {
91                // write out the first one ...
92                write!(f, "\n  {:#}", View(v))?;
93                // ... and write out the others after commas
94                for v in vars {
95                    write!(f, ",\n  {:#}", View(v))?;
96                }
97                // close up the vars
98                write!(f, "\n)")?;
99            } else {
100                // no vars: stay on the same line
101                write!(f, ")")?;
102            }
103            // include conditions on their own lines
104            for c in policy.conds.iter() {
105                write!(f, "\n{:#}", View(c))?;
106            }
107        } else {
108            write!(f, "{}(", View(&policy.effect))?;
109            let mut vars = policy.variables.iter();
110            // if at least one var ...
111            if let Some(v) = vars.next() {
112                // write out the first one ...
113                write!(f, "{}", View(v))?;
114                // ... and write out the others after commas
115                for v in vars {
116                    write!(f, ",  {}", View(v))?;
117                }
118            }
119            write!(f, ")")?;
120
121            for c in policy.conds.iter() {
122                write!(f, " {}", View(c))?;
123            }
124        }
125        write!(f, ";")?;
126        Ok(())
127    }
128}
129
130impl fmt::Display for Annotation {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self.value.as_ref() {
133            Some(value) => write!(f, "@{}({})", View(&self.key), View(value)),
134            None => write!(f, "@{}", View(&self.key)),
135        }
136    }
137}
138
139impl fmt::Display for VariableDef {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(f, "{}", View(&self.variable))?;
142        if let Some(name) = &self.unused_type_name {
143            write!(f, ": {}", View(name))?;
144        }
145        if let Some((op, expr)) = &self.ineq {
146            write!(f, " {} {}", op, View(expr))?;
147        }
148        Ok(())
149    }
150}
151impl fmt::Display for Cond {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        match self.expr.as_ref() {
154            Some(expr_ref) => {
155                if f.alternate() {
156                    write!(f, "{} {{\n  {:#}\n}}", View(&self.cond), View(expr_ref))
157                } else {
158                    write!(f, "{} {{{}}}", View(&self.cond), View(expr_ref))
159                }
160            }
161            None => write!(f, "{} {{ }}", View(&self.cond)),
162        }
163    }
164}
165impl fmt::Display for Expr {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        // let expr_opt: &_ = &*self.expr;
168        let expr = match self {
169            Expr::Expr(expr_impl) => &*expr_impl.expr,
170            #[cfg(feature = "tolerant-ast")]
171            Expr::ErrorExpr => return write!(f, "Expr::Error"),
172        };
173        match expr {
174            ExprData::Or(or) => write!(f, "{}", View(or)),
175            ExprData::If(ex1, ex2, ex3) => {
176                write!(f, "if {} then {} else {}", View(ex1), View(ex2), View(ex3))
177            }
178        }
179    }
180}
181impl fmt::Display for Or {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        write!(f, "{}", View(&self.initial))?;
184        for or in self.extended.iter() {
185            write!(f, " || {}", View(or))?;
186        }
187        Ok(())
188    }
189}
190impl fmt::Display for And {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        write!(f, "{}", View(&self.initial))?;
193        for and in self.extended.iter() {
194            write!(f, " && {}", View(and))?;
195        }
196        Ok(())
197    }
198}
199impl fmt::Display for Relation {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            Relation::Common { initial, extended } => {
203                write!(f, "{}", View(initial))?;
204                for (op, add) in extended.iter() {
205                    write!(f, " {} {}", op, View(add))?;
206                }
207            }
208            Relation::Has { target, field } => {
209                write!(f, "{} has {}", View(target), View(field))?;
210            }
211            Relation::Like { target, pattern } => {
212                write!(f, "{} like {}", View(target), View(pattern))?;
213            }
214            Relation::IsIn {
215                target,
216                entity_type,
217                in_entity: None,
218            } => {
219                write!(f, "{} is {}", View(target), View(entity_type))?;
220            }
221            Relation::IsIn {
222                target,
223                entity_type,
224                in_entity: Some(in_entity),
225            } => {
226                write!(
227                    f,
228                    "{} is {} in {}",
229                    View(target),
230                    View(entity_type),
231                    View(in_entity)
232                )?;
233            }
234        }
235        Ok(())
236    }
237}
238impl fmt::Display for RelOp {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        match self {
241            RelOp::Less => write!(f, "<"),
242            RelOp::LessEq => write!(f, "<="),
243            RelOp::GreaterEq => write!(f, ">="),
244            RelOp::Greater => write!(f, ">"),
245            RelOp::NotEq => write!(f, "!="),
246            RelOp::Eq => write!(f, "=="),
247            RelOp::In => write!(f, "in"),
248            RelOp::InvalidSingleEq => write!(f, "="),
249        }
250    }
251}
252impl fmt::Display for AddOp {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        match self {
255            AddOp::Plus => write!(f, "+"),
256            AddOp::Minus => write!(f, "-"),
257        }
258    }
259}
260impl fmt::Display for MultOp {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        match self {
263            MultOp::Times => write!(f, "*"),
264            MultOp::Divide => write!(f, "/"),
265            MultOp::Mod => write!(f, "%"),
266        }
267    }
268}
269impl fmt::Display for NegOp {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match self {
272            NegOp::Bang(cnt) => {
273                for _ in 0..*cnt {
274                    write!(f, "!")?;
275                }
276            }
277            // represents too many, current parser accepts a max of 4
278            NegOp::OverBang => write!(f, "!!!!!!!!!!")?,
279            NegOp::Dash(cnt) => {
280                for _ in 0..*cnt {
281                    write!(f, "-")?;
282                }
283            }
284            // represents too many, current parser accepts a max of 4
285            NegOp::OverDash => write!(f, "----------")?,
286        }
287        Ok(())
288    }
289}
290impl fmt::Display for Add {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        write!(f, "{}", View(&self.initial))?;
293        for (op, mult) in self.extended.iter() {
294            write!(f, " {} {}", op, View(mult))?;
295        }
296        Ok(())
297    }
298}
299impl fmt::Display for Mult {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        write!(f, "{}", View(&self.initial))?;
302        for (op, un) in self.extended.iter() {
303            write!(f, " {} {}", op, View(un))?;
304        }
305        Ok(())
306    }
307}
308impl fmt::Display for Unary {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        if let Some(op) = &self.op {
311            write!(f, "{}{}", op, View(&self.item))
312        } else {
313            write!(f, "{}", View(&self.item))
314        }
315    }
316}
317impl fmt::Display for Member {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        write!(f, "{}", View(&self.item))?;
320        for m in self.access.iter() {
321            write!(f, "{}", View(m))?;
322        }
323        Ok(())
324    }
325}
326impl fmt::Display for MemAccess {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        match self {
329            MemAccess::Field(id) => write!(f, ".{}", View(id))?,
330            MemAccess::Call(exprs) => {
331                write!(f, "(")?;
332                let mut es = exprs.iter();
333                if let Some(ex) = es.next() {
334                    write!(f, "{}", View(ex))?;
335                }
336                for e in es {
337                    write!(f, ", {}", View(e))?;
338                }
339                write!(f, ")")?;
340            }
341            MemAccess::Index(e) => write!(f, "[{}]", View(e))?,
342        }
343        Ok(())
344    }
345}
346impl fmt::Display for Primary {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        match self {
349            Primary::Literal(lit) => write!(f, "{}", View(lit)),
350            Primary::Ref(rf) => write!(f, "{}", View(rf)),
351            Primary::Name(nm) => write!(f, "{}", View(nm)),
352            Primary::Expr(expr) => write!(f, "({})", View(expr)),
353            Primary::EList(exs) => {
354                write!(f, "[")?;
355                let mut es = exs.iter();
356                if let Some(ex) = es.next() {
357                    write!(f, "{}", View(ex))?;
358                }
359                for e in es {
360                    write!(f, ", {}", View(e))?;
361                }
362                write!(f, "]")
363            }
364            Primary::RInits(mis) => {
365                write!(f, "{{")?;
366                let mut ms = mis.iter();
367                if let Some(i) = ms.next() {
368                    write!(f, "{}", View(i))?;
369                }
370                for i in ms {
371                    write!(f, ", {}", View(i))?;
372                }
373                write!(f, "}}")
374            }
375            Primary::Slot(s) => write!(f, "{}", View(s)),
376        }
377    }
378}
379impl fmt::Display for Name {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        for n in self.path.iter() {
382            write!(f, "{}::", View(n))?;
383        }
384        write!(f, "{}", View(&self.name))?;
385        Ok(())
386    }
387}
388impl fmt::Display for Ref {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        match self {
391            Ref::Uid { path, eid } => {
392                write!(f, "{}::{}", View(path), View(eid))?;
393            }
394            Ref::Ref { path, rinits } => {
395                write!(f, "{}::{{", View(path))?;
396                let mut ris = rinits.iter();
397                if let Some(r) = ris.next() {
398                    write!(f, "{}", View(r))?;
399                }
400                for r in ris {
401                    write!(f, ", {}", View(r))?;
402                }
403                write!(f, "}}")?;
404            }
405        }
406        Ok(())
407    }
408}
409impl fmt::Display for RefInit {
410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411        write!(f, "{}: {}", View(&self.0), View(&self.1))
412    }
413}
414impl fmt::Display for RecInit {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        write!(f, "{}: {}", View(&self.0), View(&self.1))
417    }
418}
419impl fmt::Display for Ident {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        match self {
422            Ident::Principal => write!(f, "principal"),
423            Ident::Action => write!(f, "action"),
424            Ident::Resource => write!(f, "resource"),
425            Ident::Context => write!(f, "context"),
426            Ident::True => write!(f, "true"),
427            Ident::False => write!(f, "false"),
428            Ident::Permit => write!(f, "permit"),
429            Ident::Forbid => write!(f, "forbid"),
430            Ident::When => write!(f, "when"),
431            Ident::Unless => write!(f, "unless"),
432            Ident::In => write!(f, "in"),
433            Ident::Has => write!(f, "has"),
434            Ident::Like => write!(f, "like"),
435            Ident::Is => write!(f, "is"),
436            Ident::If => write!(f, "if"),
437            Ident::Then => write!(f, "then"),
438            Ident::Else => write!(f, "else"),
439            Ident::Ident(s) => write!(f, "{s}"),
440            Ident::Invalid(s) => write!(f, "{s}"),
441        }
442    }
443}
444impl fmt::Display for Literal {
445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446        match self {
447            Literal::True => write!(f, "true"),
448            Literal::False => write!(f, "false"),
449            Literal::Num(n) => write!(f, "{n}"),
450            Literal::Str(s) => write!(f, "{}", View(s)),
451        }
452    }
453}
454impl fmt::Display for Str {
455    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456        match self {
457            Str::String(s) | Str::Invalid(s) => {
458                write!(f, "\"{s}\"")
459            }
460        }
461    }
462}
463
464impl std::fmt::Display for Slot {
465    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466        let src = match self {
467            Slot::Principal => "?principal",
468            Slot::Resource => "?resource",
469            Slot::Other(slot) => slot.as_ref(),
470        };
471        write!(f, "{src}")
472    }
473}
474
475/// Format an iterator as a natural-language string, separating items with
476/// commas and a conjunction (e.g., "and", "or") between the last two items.
477pub fn join_with_conjunction<T, W: Write>(
478    f: &mut W,
479    conjunction: &str,
480    items: impl IntoIterator<Item = T>,
481    fmt_item: impl Fn(&mut W, T) -> fmt::Result,
482) -> fmt::Result {
483    let mut iter = items.into_iter().peekable();
484
485    if let Some(first_item) = iter.next() {
486        fmt_item(f, first_item)?;
487
488        if let Some(second_item) = iter.next() {
489            match iter.peek() {
490                Some(_) => write!(f, ", "),
491                None => write!(f, " {conjunction} "),
492            }?;
493
494            fmt_item(f, second_item)?;
495
496            while let Some(item) = iter.next() {
497                match iter.peek() {
498                    Some(_) => write!(f, ", "),
499                    None => write!(f, ", {conjunction} "),
500                }?;
501
502                fmt_item(f, item)?;
503            }
504        }
505    }
506
507    Ok(())
508}
509
510#[cfg(test)]
511mod test {
512    use crate::parser::*;
513
514    // Currently, hese tests supplement the ones in the main test
515    // directory, rather than testing everything themselves
516
517    #[test]
518    fn idempotent1() {
519        // Note: the context field in the scope is no longer supported and
520        // will produce an error during CST -> AST conversion. But it is
521        // still correctly parsed & displayed by the CST code.
522        let cstnode1 = text_to_cst::parse_policies(
523            r#"
524
525        permit(principal,action,resource,context)
526        when {
527            -3 != !!2
528        };
529
530        "#,
531        )
532        .expect("parse fail");
533        let cst1 = cstnode1.as_inner().expect("no data");
534        let revert = format!("{cst1}");
535        let cstnode2 = text_to_cst::parse_policies(&revert).expect("parse fail");
536        let cst2 = cstnode2.as_inner().expect("no data");
537        println!("{cst2:#}");
538        assert!(cst1 == cst2);
539    }
540    #[test]
541    fn idempotent2() {
542        let cstnode1 = text_to_cst::parse_policies(
543            r#"
544
545        permit(principal,action,resource,context)
546        when {
547            context.contains(3,"four",five(6,7))
548        };
549
550        "#,
551        )
552        .expect("parse fail");
553        let cst1 = cstnode1.as_inner().expect("no data");
554        let revert = format!("{cst1}");
555        let cstnode2 = text_to_cst::parse_policies(&revert).expect("parse fail");
556        let cst2 = cstnode2.as_inner().expect("no data");
557        assert!(cst1 == cst2);
558    }
559    #[test]
560    fn idempotent3() {
561        let cstnode1 = text_to_cst::parse_policies(
562            r#"
563
564        permit(principal,action,resource,context)
565        when {
566            context == {3: 14, "true": false || true }
567        };
568
569        "#,
570        )
571        .expect("parse fail");
572        let cst1 = cstnode1.as_inner().expect("no data");
573        let revert = format!("{cst1}");
574        let cstnode2 = text_to_cst::parse_policies(&revert).expect("parse fail");
575        let cst2 = cstnode2.as_inner().expect("no data");
576        assert!(cst1 == cst2);
577    }
578    #[test]
579    fn idempotent4() {
580        let cstnode1 = text_to_cst::parse_policies(
581            r#"
582
583        permit(principal,action,resource,context)
584        when {
585            contains() ||
586            containsAll() ||
587            containsAny() ||
588            "sometext" like "some*" ||
589            Random::naming::of::foo()
590        };
591
592        "#,
593        )
594        .expect("parse fail");
595        let cst1 = cstnode1.as_inner().expect("no data");
596        let revert = format!("{cst1}");
597        println!("{cst1:#}");
598        let cstnode2 = text_to_cst::parse_policies(&revert).expect("parse fail");
599        let cst2 = cstnode2.as_inner().expect("no data");
600        assert!(cst1 == cst2);
601    }
602
603    #[test]
604    fn idempotent5() {
605        let cstnode1 = text_to_cst::parse_policies(
606            r#"
607
608        permit(principal,action,resource,context)
609        when {
610            principle == Group::{uid:"ajn34-3qg3-g5"}
611        };
612
613        "#,
614        )
615        .expect("parse fail");
616        let cst1 = cstnode1.as_inner().expect("no data");
617        let revert = format!("{cst1}");
618        let cstnode2 = text_to_cst::parse_policies(&revert).expect("parse fail");
619        let cst2 = cstnode2.as_inner().expect("no data");
620        assert!(cst1 == cst2);
621    }
622}