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