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