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