cedar_policy_core/parser/
text_to_cst.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//! This module contains step one of the parser for the Cedar language.
18//! It converts text to a CST
19
20lalrpop_mod!(
21    #[allow(warnings, unused, missing_debug_implementations)]
22    //PANIC SAFETY: lalrpop uses unwraps, and we are trusting lalrpop to generate correct code
23    #[allow(clippy::unwrap_used)]
24    //PANIC SAFETY: lalrpop uses slicing, and we are trusting lalrpop to generate correct code
25    #[allow(clippy::indexing_slicing)]
26    //PANIC SAFETY: lalrpop uses unreachable, and we are trusting lalrpop to generate correct code
27    #[allow(clippy::unreachable)]
28    //PANIC SAFETY: lalrpop uses panic, and we are trusting lalrpop to generate correct code
29    #[allow(clippy::panic)]
30    pub grammar,
31    "/src/parser/grammar.rs"
32);
33
34use super::*;
35use std::sync::Arc;
36
37/// This helper function calls a generated parser, collects errors that could be
38/// generated multiple ways, and returns a single Result where the error type is
39/// [`err::ParseErrors`].
40fn parse_collect_errors<'a, P, T>(
41    parser: &P,
42    parse: impl FnOnce(
43        &P,
44        &mut Vec<err::RawErrorRecovery<'a>>,
45        &Arc<str>,
46        &'a str,
47    ) -> Result<T, err::RawParseError<'a>>,
48    text: &'a str,
49) -> Result<T, err::ParseErrors> {
50    let mut errs = Vec::new();
51    let result = parse(parser, &mut errs, &Arc::from(text), text);
52
53    let errors = errs
54        .into_iter()
55        .map(|rc| err::ToCSTError::from_raw_err_recovery(rc, Arc::from(text)))
56        .map(Into::into);
57    let parsed = match result {
58        Ok(parsed) => parsed,
59        Err(e) => {
60            return Err(err::ParseErrors::new(
61                err::ToCSTError::from_raw_parse_err(e, Arc::from(text)).into(),
62                errors,
63            ));
64        }
65    };
66    match err::ParseErrors::from_iter(errors) {
67        Some(errors) => Err(errors),
68        None => Ok(parsed),
69    }
70}
71
72// Thread-safe "global" parsers, initialized at first use
73lazy_static::lazy_static! {
74    static ref POLICIES_PARSER: grammar::PoliciesParser = grammar::PoliciesParser::new();
75    static ref POLICY_PARSER: grammar::PolicyParser = grammar::PolicyParser::new();
76    static ref EXPR_PARSER: grammar::ExprParser = grammar::ExprParser::new();
77    static ref REF_PARSER: grammar::RefParser = grammar::RefParser::new();
78    static ref PRIMARY_PARSER: grammar::PrimaryParser = grammar::PrimaryParser::new();
79    static ref NAME_PARSER: grammar::NameParser = grammar::NameParser::new();
80    static ref IDENT_PARSER: grammar::IdentParser = grammar::IdentParser::new();
81}
82
83/// Create CST for multiple policies from text
84pub fn parse_policies(text: &str) -> Result<Node<Option<cst::Policies>>, err::ParseErrors> {
85    parse_collect_errors(&*POLICIES_PARSER, grammar::PoliciesParser::parse, text)
86}
87
88/// Create CST for one policy statement from text
89pub fn parse_policy(text: &str) -> Result<Node<Option<cst::Policy>>, err::ParseErrors> {
90    parse_collect_errors(&*POLICY_PARSER, grammar::PolicyParser::parse, text)
91}
92
93/// Create CST for one Expression from text
94pub fn parse_expr(text: &str) -> Result<Node<Option<cst::Expr>>, err::ParseErrors> {
95    parse_collect_errors(&*EXPR_PARSER, grammar::ExprParser::parse, text)
96}
97
98/// Create CST for one Entity Ref (i.e., UID) from text
99pub fn parse_ref(text: &str) -> Result<Node<Option<cst::Ref>>, err::ParseErrors> {
100    parse_collect_errors(&*REF_PARSER, grammar::RefParser::parse, text)
101}
102
103/// Create CST for one Primary value from text
104pub fn parse_primary(text: &str) -> Result<Node<Option<cst::Primary>>, err::ParseErrors> {
105    parse_collect_errors(&*PRIMARY_PARSER, grammar::PrimaryParser::parse, text)
106}
107
108/// Parse text as a Name, or fail if it does not parse as a Name
109pub fn parse_name(text: &str) -> Result<Node<Option<cst::Name>>, err::ParseErrors> {
110    parse_collect_errors(&*NAME_PARSER, grammar::NameParser::parse, text)
111}
112
113/// Parse text as an identifier, or fail if it does not parse as an identifier
114pub fn parse_ident(text: &str) -> Result<Node<Option<cst::Ident>>, err::ParseErrors> {
115    parse_collect_errors(&*IDENT_PARSER, grammar::IdentParser::parse, text)
116}
117
118// PANIC SAFETY unit test code
119#[allow(clippy::panic)]
120// PANIC SAFETY unit test code
121#[allow(clippy::indexing_slicing)]
122#[cfg(test)]
123mod tests {
124    use crate::parser::test_utils::*;
125    use crate::test_utils::*;
126
127    use super::*;
128
129    #[track_caller]
130    fn assert_parse_succeeds<T>(
131        parse: impl FnOnce(&str) -> Result<Node<Option<T>>, err::ParseErrors>,
132        text: &str,
133    ) -> T {
134        parse(text)
135            .unwrap_or_else(|errs| panic!("failed to parse:\n{:?}", miette::Report::new(errs)))
136            .node
137            .expect("failed get CST")
138    }
139
140    #[track_caller]
141    fn assert_parse_fails<T: std::fmt::Debug>(
142        parse: impl FnOnce(&str) -> Result<Node<Option<T>>, err::ParseErrors>,
143        text: &str,
144    ) -> err::ParseErrors {
145        match parse(text) {
146            Ok(node) => {
147                panic!("parsing should have failed, but succeeded with:\n{node:?}")
148            }
149            Err(errs) => errs,
150        }
151    }
152
153    #[test]
154    fn expr1() {
155        assert_parse_succeeds(
156            parse_expr,
157            r#"
158        1
159        "#,
160        );
161    }
162
163    #[test]
164    fn expr2() {
165        assert_parse_succeeds(
166            parse_expr,
167            r#"
168        "string"
169        "#,
170        );
171    }
172
173    #[test]
174    fn expr3() {
175        assert_parse_succeeds(
176            parse_expr,
177            r#"
178        "string".foo == !7
179        "#,
180        );
181    }
182
183    #[test]
184    fn expr4() {
185        assert_parse_succeeds(
186            parse_expr,
187            r#"
188        5 < 3 || -7 == 2 && 3 >= 6
189        "#,
190        );
191    }
192
193    #[test]
194    fn expr5() {
195        assert_parse_succeeds(
196            parse_expr,
197            r#"
198        if 7 then 6 > 5 else !5 || "thursday"
199        "#,
200        );
201    }
202
203    #[test]
204    fn expr6() {
205        assert_parse_succeeds(
206            parse_expr,
207            r#"
208        if 7 then 6 > 5 else !5 || "thursday" && ((8) >= "fish")
209        "#,
210        );
211    }
212
213    #[test]
214    fn expr_overflow() {
215        // an error is not a crash!
216        let src = r#"
217            principal == -5555555555555555555555
218        "#;
219        let errs = assert_parse_fails(parse_expr, src);
220        expect_exactly_one_error(
221            src,
222            &errs,
223            &ExpectedErrorMessageBuilder::error(
224                "integer parse error: number too large to fit in target type",
225            )
226            .exactly_one_underline("5555555555555555555555")
227            .build(),
228        );
229        let src = r#"
230            principal == 5555555555555555555555
231        "#;
232        let errs = assert_parse_fails(parse_expr, src);
233        expect_exactly_one_error(
234            src,
235            &errs,
236            &ExpectedErrorMessageBuilder::error(
237                "integer parse error: number too large to fit in target type",
238            )
239            .exactly_one_underline("5555555555555555555555")
240            .build(),
241        );
242    }
243
244    #[test]
245    fn variable1() {
246        assert_parse_succeeds(
247            parse_policy,
248            r#"
249                permit(principal, action, var:h in 1);
250            "#,
251        );
252    }
253
254    #[test]
255    fn variable2() {
256        assert_parse_succeeds(
257            parse_policy,
258            r#"
259                permit(principal, action, more in 2);
260            "#,
261        );
262    }
263
264    #[test]
265    fn variable3() {
266        assert_parse_succeeds(
267            parse_policy,
268            r#"
269                permit(principal, action:a_name, resource);
270            "#,
271        );
272    }
273
274    #[test]
275    fn variable4() {
276        assert_parse_succeeds(
277            parse_policy,
278            r#"
279                permit(principalorsomeotherident, action, resource);
280            "#,
281        );
282    }
283
284    #[test]
285    fn variable6() {
286        let src = r#"
287            permit(var : in 6, action, resource);
288        "#;
289        let errs = assert_parse_fails(parse_policy, src);
290        expect_exactly_one_error(
291            src,
292            &errs,
293            &ExpectedErrorMessageBuilder::error("unexpected token `6`")
294                .exactly_one_underline_with_label(
295                    "6",
296                    "expected `!=`, `)`, `,`, `::`, `<`, `<=`, `==`, `>`, `>=`, `in`, or `is`",
297                )
298                .build(),
299        );
300    }
301
302    #[test]
303    fn member1() {
304        assert_parse_succeeds(
305            parse_policy,
306            r#"
307                permit(principal, action, resource)
308                when{
309                    2._field // oh, look, comments!
310                };
311            "#,
312        );
313    }
314
315    #[test]
316    fn member2() {
317        assert_parse_succeeds(
318            parse_policy,
319            r#"
320                permit(principal, action, resource)
321                when{
322                    "first".some_ident()
323                };
324            "#,
325        );
326    }
327
328    #[test]
329    fn member3() {
330        assert_parse_succeeds(
331            parse_policy,
332            r#"
333                permit(principal, action, resource)
334                when{
335                    [2,3,4].foo[2]
336                };
337            "#,
338        );
339    }
340
341    #[test]
342    fn member4() {
343        assert_parse_succeeds(
344            parse_policy,
345            r#"
346                permit(principal, action, resource)
347                when{
348                    {3<-4:"what?","ok then":-5>4}
349                };
350            "#,
351        );
352    }
353
354    #[test]
355    fn member5() {
356        assert_parse_succeeds(
357            parse_policy,
358            r#"
359                permit(principal, action, resource)
360                when{
361                    [3<4,"ok then",17,("none")]
362                };
363            "#,
364        );
365    }
366
367    #[test]
368    fn member6() {
369        assert_parse_succeeds(
370            parse_policy,
371            r#"
372                permit(principal, action, resource)
373                when{
374                    one.two
375                };
376            "#,
377        );
378    }
379
380    #[test] // we no longer support named structs
381    fn member7() {
382        let src = r#"
383            permit(principal, action, resource)
384            when{
385                one{num:true,trivia:"first!"}
386            };
387        "#;
388        let errs = assert_parse_fails(parse_policy, src);
389        expect_n_errors(src, &errs, 2);
390        expect_some_error_matches(
391            src,
392            &errs,
393            &ExpectedErrorMessageBuilder::error("unexpected token `{`")
394                .exactly_one_underline_with_label("{", "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `}`, `has`, `in`, `is`, or `like`")
395                .build(),
396        );
397        expect_some_error_matches(
398            src,
399            &errs,
400            &ExpectedErrorMessageBuilder::error("unexpected token `}`")
401                .exactly_one_underline_with_label("}", "expected `;` or identifier")
402                .build(),
403        );
404    }
405
406    #[test]
407    fn member8() {
408        assert_parse_succeeds(
409            parse_policy,
410            r#"
411                permit(principal, action, resource)
412                when{
413                    {2:true,4:me}.with["pizza"]
414                };
415            "#,
416        );
417    }
418
419    #[test]
420    fn member9() {
421        assert_parse_succeeds(
422            parse_policy,
423            r#"
424                permit(principal, action, resource)
425                when{
426                    AllRects({two:2,four:3+5/5})
427                };
428            "#,
429        );
430    }
431
432    #[test]
433    fn ident1() {
434        assert_parse_succeeds(
435            parse_ident,
436            r#"
437                principal
438            "#,
439        );
440    }
441
442    #[test]
443    fn ident2() {
444        // specialized parser for idents does not care about keywords
445        assert_parse_succeeds(
446            parse_ident,
447            r#"
448                if
449            "#,
450        );
451        // specialized parser for idents does not care about keywords
452        assert_parse_succeeds(
453            parse_ident,
454            r#"
455                false
456            "#,
457        );
458    }
459
460    #[test]
461    fn ident3() {
462        // keywords are not valid variable names
463        let src = r#"
464            if
465        "#;
466        let errs = assert_parse_fails(parse_expr, src);
467        expect_exactly_one_error(
468            src,
469            &errs,
470            &ExpectedErrorMessageBuilder::error("unexpected end of input")
471                .exactly_one_underline_with_label("", "expected `!`, `(`, `-`, `::`, `[`, `{`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`")
472                .build(),
473        );
474        // other random variable names are fine at this stage, although an error
475        // will be raised during the CST->AST step
476        assert_parse_succeeds(
477            parse_expr,
478            r#"
479                foo
480            "#,
481        );
482        // valid variable names are obviously ok
483        assert_parse_succeeds(
484            parse_expr,
485            r#"
486                foo
487            "#,
488        );
489        // keywords are ok to use in paths at this stage, although an error will
490        // be raised during the CST->AST step
491        assert_parse_succeeds(
492            parse_expr,
493            r#"
494                if::then::else
495            "#,
496        );
497        assert_parse_succeeds(
498            parse_expr,
499            r#"
500                if::true::then::false::else::true
501            "#,
502        );
503    }
504
505    #[test]
506    fn ident4() {
507        // some keywords can be used as functions
508        assert_parse_succeeds(
509            parse_expr,
510            r#"
511                true(true)
512            "#,
513        );
514        // but some keywords cannot because of parse confusion
515        let src = r#"
516            if(true)
517        "#;
518        let errs = assert_parse_fails(parse_expr, src);
519        expect_exactly_one_error(
520            src,
521            &errs,
522            &ExpectedErrorMessageBuilder::error("unexpected end of input")
523                .exactly_one_underline_with_label("", "expected `then`")
524                .build(),
525        );
526    }
527
528    #[test]
529    fn ident5() {
530        // keywords are ok to use as attributes at this stage, although an error
531        // will be raised during the CST->AST step
532        assert_parse_succeeds(
533            parse_expr,
534            r#"
535                {true : false}
536            "#,
537        );
538        assert_parse_succeeds(
539            parse_expr,
540            r#"
541                { if : true }
542            "#,
543        );
544    }
545
546    #[test]
547    fn ident6() {
548        // keywords are ok to use as attributes at this stage, although an error
549        // will be raised during the CST->AST step
550        assert_parse_succeeds(
551            parse_expr,
552            r#"
553                {true : false} has false
554            "#,
555        );
556        assert_parse_succeeds(
557            parse_expr,
558            r#"
559                { if : true } has if
560            "#,
561        );
562    }
563
564    #[test]
565    fn comments_has() {
566        // single line comments (`// ...`) are valid anywhere
567        assert_parse_succeeds(
568            parse_policy,
569            r#"
570                permit(principal, action,resource)
571                when{ principal //comment p
572                has //comment has
573                age //comment
574                };
575            "#,
576        );
577    }
578
579    #[test]
580    fn comments_like() {
581        // single line comments (`// ...`) are valid anywhere
582        assert_parse_succeeds(
583            parse_policy,
584            r#"
585                permit(principal, action,resource)
586                when{ principal //comment p
587                like //comment like
588
589                age //comment
590                };
591            "#,
592        );
593    }
594
595    #[test]
596    fn comments_and() {
597        // single line comments (`// ...`) are valid anywhere
598        assert_parse_succeeds(
599            parse_policy,
600            r#"
601                permit(principal, action,resource)
602                when{ 1 //comment p
603                &&  //comment &&
604                    //comment &&
605                "hello" //comment
606                };
607            "#,
608        );
609    }
610
611    #[test]
612    fn comments_or() {
613        // single line comments (`// ...`) are valid anywhere
614        assert_parse_succeeds(
615            parse_policy,
616            r#"
617                permit(principal, action,resource)
618                when{ 1 //comment 1
619                      //  comment 1
620                ||  //comment ||
621                    //comments ||
622                "hello" //comment
623                        //comment hello
624                };
625            "#,
626        );
627    }
628
629    #[test]
630    fn comments_add() {
631        // single line comments (`// ...`) are valid anywhere
632        assert_parse_succeeds(
633            parse_policy,
634            r#"
635                permit(principal, action,resource)
636                when{ 1 //comment 1
637                        //comment 1_2
638                + //comment +
639                   //comment +
640                 2 //comment 2
641                    //comment 2
642                };
643            "#,
644        );
645    }
646
647    #[test]
648    fn comments_paren() {
649        // single line comments (`// ...`) are valid anywhere
650        assert_parse_succeeds(
651            parse_policy,
652            r#"
653                permit(principal, action,resource)
654                when{
655                ( //comment 1
656                    ( //comment 2
657                 1
658                    ) //comment 3
659                ) //comment 4
660                };
661            "#,
662        );
663    }
664
665    #[test]
666    fn comments_set() {
667        // single line comments (`// ...`) are valid anywhere
668        assert_parse_succeeds(
669            parse_policy,
670            r#"
671                permit(principal, action,resource)
672                when{
673                [ // comment 1
674                "hello" //comment 2
675                , // comment 3
676                 // comment 3-2
677                1 //comment 4
678                    //comment 5
679                ]  //comment 5-0
680
681                .  //comment 5-1
682
683                contains //comment 5-2
684
685                ( //comment 6
686
687                "a"  //comment 7
688
689                ) //comment 20
690                };
691            "#,
692        );
693    }
694
695    #[test]
696    fn comments_if() {
697        // single line comments (`// ...`) are valid anywhere
698        assert_parse_succeeds(
699            parse_policy,
700            r#"
701                permit(principal, action,resource)
702                when{
703                ( //comment open outer
704                ( //comment open inner
705                 if //comment if
706                  1             //comment
707                  < //comment <
708                  2 //commment 2
709                  then // comment then
710                  "hello" //comment hello
711                else  //comment else
712                    1 //comment 1
713                    ) //comment close inner
714                    ) //comment close outer
715                };
716            "#,
717        );
718    }
719
720    #[test]
721    fn comments_member_access() {
722        // single line comments (`// ...`) are valid anywhere
723        assert_parse_succeeds(
724            parse_policy,
725            r#"
726                permit(principal, action,resource)
727                when{ principal. //comment .
728                age // comment age
729                };
730            "#,
731        );
732    }
733
734    #[test]
735    fn comments_principal() {
736        // single line comments (`// ...`) are valid anywhere
737        assert_parse_succeeds(
738            parse_policy,
739            r#"
740                permit(principal //comment 1
741                 ==
742                  User::"alice" //comment 3
743                  ,  //comment 4
744                   action,resource);
745            "#,
746        );
747    }
748
749    #[test]
750    fn comments_annotation() {
751        // single line comments (`// ...`) are valid anywhere
752        assert_parse_succeeds(
753            parse_policy,
754            r#"
755        //comment policy
756        // comment policy 2
757        @anno("good annotation")  // comments after annotation
758        // comments after annotation 2
759                permit(principal //comment 1
760                 ==
761                  User::"alice" //comment 3
762                  ,  //comment 4
763                   action,resource);
764            "#,
765        );
766    }
767
768    #[test]
769    fn comments_policy() {
770        // single line comments (`// ...`) are valid anywhere
771        assert_parse_succeeds(
772            parse_policy,
773            r#"
774                //comment policy 1
775                //comment policy 2
776                permit( //comment 3
777                   // comment 4
778                principal //comment principal
779                == //comment == 1
780                   //comment == 2
781                User::"alice" //comment alice
782                , //comment comma 1
783                            //comment comma 2
784                action //comment action 1
785                //comment action 2
786                , //comment comma action
787                resource // comment resource
788                )
789                //comment 5
790                //comment 6
791                ;
792            "#,
793        );
794        //multi-line comments (`/* ... */`) are not allowed
795        let src = r#" /* multi-line
796            comment */
797                permit(principal, action, resource)
798                when{
799                    one.two
800                };
801            "#;
802        let errs = assert_parse_fails(parse_policy, src);
803        expect_exactly_one_error(
804            src,
805            &errs,
806            &ExpectedErrorMessageBuilder::error("unexpected token `/`")
807                .exactly_one_underline_with_label("/", "expected `@` or identifier")
808                .build(),
809        );
810        let src = r#"
811            1 /* multi-line
812            comment */d
813            "#;
814        let errs = assert_parse_fails(parse_expr, src);
815        expect_exactly_one_error(
816            src,
817            &errs,
818            &ExpectedErrorMessageBuilder::error("unexpected token `*`")
819                .exactly_one_underline_with_label("*", "expected `!`, `(`, `-`, `[`, `{`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`")
820                .build(),
821        );
822    }
823
824    #[test]
825    fn no_comments_policy() {
826        // single line comments (`// ...`) are valid anywhere
827        assert_parse_succeeds(
828            parse_policy,
829            r#"
830               permit(
831                principal
832                ==
833                User::"alice"
834                ,
835                action
836
837                ,
838                resource
839                )
840                ;
841            "#,
842        );
843    }
844
845    #[test]
846    fn no_comments_policy2() {
847        assert_parse_succeeds(
848            parse_policy,
849            r#"permit (
850    principal == IAM::Principal::"arn:aws:iam::12345678901:user/Dave",
851    action == S3::Action::"GetAccountPublicAccessBlock",
852    resource == Account::"12345678901"
853    );"#,
854        );
855    }
856
857    #[test]
858    fn no_comments_policy4() {
859        assert_parse_succeeds(
860            parse_policy,
861            r#"
862    permit(principal,action,resource,context)
863    when {
864    context.contains(3,"four",five(6,7))
865};"#,
866        );
867    }
868    #[test]
869    fn no_comments_policy5() {
870        assert_parse_succeeds(
871            parse_policy,
872            r#"
873    permit (
874    principal,
875    action,
876    resource == Album::{uid: "772358b3-de11-42dc-8681-f0a32e34aab8",
877    displayName: "vacation_photos"}
878);"#,
879        );
880    }
881
882    #[test]
883    fn policies1() {
884        assert_parse_succeeds(
885            parse_policy,
886            r#"
887                permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
888            "#,
889        );
890    }
891
892    #[test]
893    fn policies2() {
894        assert_parse_succeeds(
895            parse_policies,
896            r#"
897                permit(
898                    principal in Group::"jane_friends",  // Policy c1
899                    action in [PhotoOp::"view", PhotoOp::"comment"],
900                    resource in Album::"jane_trips",
901                    context:Group
902                );
903            "#,
904        );
905    }
906
907    #[test]
908    fn policies3() {
909        let policies = assert_parse_succeeds(
910            parse_policies,
911            r#"
912            forbid(principal, action, resource)           // Policy c2
913            when   { "private" in resource.tags }  // resource.tags is a set of strings
914            unless { resource in user.account };
915        "#,
916        );
917
918        // Check that internal nodes successfully parsed
919        assert!(
920            policies.0.iter().all(|p| p.node.is_some()),
921            "Unexpected parser failure"
922        );
923    }
924
925    #[test]
926    // repeat of prior test but with a typo
927    // typos are not caught by the cst parser
928    fn policies3p() {
929        let policies = assert_parse_succeeds(
930            parse_policies,
931            r#"
932            forbid(principality, action, resource)           // Policy c2
933            when   { "private" in resource.tags }  // resource.tags is a set of strings
934            unless { resource in user.account };
935        "#,
936        );
937
938        // Check that internal nodes successfully parsed
939        assert!(
940            policies.0.iter().all(|p| p.node.is_some()),
941            "Unexpected parser failure"
942        );
943    }
944
945    #[test]
946    fn policies4() {
947        let policies = assert_parse_succeeds(
948            parse_policies,
949            r#"
950            permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
951
952            permit(principal in Group::"jane_friends",  // Policy c1
953            action in [PhotoOp::"view", PhotoOp::"comment"],
954            resource in Album::"jane_trips");
955
956            forbid(principal, action, resource)           // Policy c2
957            when   { "private" in resource.tags }  // resource.tags is a set of strings
958            unless { resource in user.account };
959        "#,
960        );
961
962        // Check that internal nodes successfully parsed
963        assert!(
964            policies.0.iter().all(|p| p.node.is_some()),
965            "Unexpected parser failure"
966        );
967    }
968
969    #[test]
970    fn policies5() {
971        let policies = assert_parse_succeeds(
972            parse_policies,
973            r#"
974            permit (
975                principal == User::"alice",
976                action in PhotoflashRole::"viewer",
977                resource in Account::"jane"
978            )
979            advice {
980                "{\"type\":\"PhotoFilterInstruction\", \"anonymize\":true}"
981            };
982        "#,
983        );
984
985        // Check that internal nodes successfully parsed
986        assert!(
987            policies.0.iter().all(|p| p.node.is_some()),
988            "Unexpected parser failure"
989        );
990    }
991
992    #[test]
993    fn policies6() {
994        // test that an error doesn't stop the parser
995        let src = r#"
996            // use a number to error
997            3(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
998            permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
999            permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
1000            "#;
1001        let policies = POLICIES_PARSER
1002            .parse(&mut Vec::new(), &Arc::from(src), src)
1003            .expect("parser error")
1004            .node
1005            .expect("no data");
1006        assert_eq!(policies.0.into_iter().filter_map(|p| p.node).count(), 2);
1007    }
1008
1009    #[test]
1010    fn policy_annotations_ok() {
1011        let policies = assert_parse_succeeds(
1012            parse_policies,
1013            r#"
1014            @anno("good annotation") permit (principal, action, resource);
1015            @anno1("good")@anno2("annotation") permit (principal, action, resource);
1016            @long6wordphraseisident007("good annotation") permit (principal, action, resource);
1017            @   spacy  (  "  good  annotation  "  )   permit (principal, action, resource);
1018        "#,
1019        );
1020        // should have successfully parsed 4 policies
1021        assert_eq!(policies.0.into_iter().filter_map(|p| p.node).count(), 4);
1022    }
1023
1024    #[test]
1025    fn policy_annotations_no_value_ok() {
1026        let policy = assert_parse_succeeds(
1027            parse_policy,
1028            r#"@foo permit (principal, action, resource);"#,
1029        );
1030        let annotation = policy.annotations.first().unwrap().as_inner().unwrap();
1031        assert_eq!(annotation.value, None);
1032        assert_eq!(
1033            annotation.key.as_inner().unwrap().to_string(),
1034            "foo".to_string()
1035        );
1036        assert_eq!(policy.annotations.len(), 1);
1037    }
1038
1039    #[test]
1040    fn policy_annotations_bad_id() {
1041        let src = r#"
1042            @bad-annotation("bad") permit (principal, action, resource);
1043        "#;
1044        let errs = assert_parse_fails(parse_policies, src);
1045        expect_exactly_one_error(
1046            src,
1047            &errs,
1048            &ExpectedErrorMessageBuilder::error("unexpected token `-`")
1049                .exactly_one_underline_with_label("-", "expected `(`, `@`, or identifier")
1050                .build(),
1051        );
1052
1053        let src = r#"
1054            @hi mom("this should be invalid")
1055            permit(principal, action, resource);
1056        "#;
1057        let errs = assert_parse_fails(parse_policies, src);
1058        expect_exactly_one_error(
1059            src,
1060            &errs,
1061            &ExpectedErrorMessageBuilder::error("unexpected token `\"this should be invalid\"`")
1062                .exactly_one_underline_with_label(
1063                    "\"this should be invalid\"",
1064                    "expected `)` or identifier",
1065                )
1066                .build(),
1067        );
1068
1069        let src = r#"
1070            @hi+mom("this should be invalid")
1071            permit(principal, action, resource);
1072        "#;
1073        let errs = assert_parse_fails(parse_policies, src);
1074        expect_exactly_one_error(
1075            src,
1076            &errs,
1077            &ExpectedErrorMessageBuilder::error("unexpected token `+`")
1078                .exactly_one_underline_with_label("+", "expected `(`, `@`, or identifier")
1079                .build(),
1080        );
1081    }
1082
1083    #[test]
1084    fn policy_annotations_bad_val() {
1085        let src = r#"
1086            @bad_annotation("bad","annotation") permit (principal, action, resource);
1087        "#;
1088        let errs = assert_parse_fails(parse_policies, src);
1089        expect_exactly_one_error(
1090            src,
1091            &errs,
1092            &ExpectedErrorMessageBuilder::error("unexpected token `,`")
1093                .exactly_one_underline_with_label(",", "expected `)`")
1094                .build(),
1095        );
1096
1097        let src = r#"
1098            @bad_annotation() permit (principal, action, resource);
1099        "#;
1100        let errs = assert_parse_fails(parse_policies, src);
1101        expect_exactly_one_error(
1102            src,
1103            &errs,
1104            &ExpectedErrorMessageBuilder::error("unexpected token `)`")
1105                .exactly_one_underline_with_label(")", "expected string literal")
1106                .build(),
1107        );
1108
1109        let src = r#"
1110            @bad_annotation(bad_annotation) permit (principal, action, resource);
1111        "#;
1112        let errs = assert_parse_fails(parse_policies, src);
1113        expect_exactly_one_error(
1114            src,
1115            &errs,
1116            &ExpectedErrorMessageBuilder::error("unexpected token `bad_annotation`")
1117                .exactly_one_underline_with_label("bad_annotation", "expected string literal")
1118                .build(),
1119        );
1120    }
1121
1122    #[test]
1123    fn policy_annotation_bad_position() {
1124        let src = r#"
1125            permit (@comment("your name here") principal, action, resource);
1126        "#;
1127        let errs = assert_parse_fails(parse_policies, src);
1128        expect_exactly_one_error(
1129            src,
1130            &errs,
1131            &ExpectedErrorMessageBuilder::error("unexpected token `@`")
1132                .exactly_one_underline_with_label("@", "expected `)` or identifier")
1133                .build(),
1134        );
1135    }
1136
1137    #[test]
1138    fn parse_idempotent() {
1139        let many_policies =
1140            std::fs::read_to_string("src/parser/testfiles/policies.cedar").expect("missing file");
1141        let cst1 = assert_parse_succeeds(parse_policies, &many_policies);
1142        let revert = format!("{}", cst1);
1143        let cst2 = assert_parse_succeeds(parse_policies, &revert);
1144        assert_eq!(cst1, cst2);
1145    }
1146
1147    #[test]
1148    fn error_recovery() {
1149        // After hitting an unexpected `!`, the parser skips ahead until it
1150        // finds a `;`, skipping over the body of the policy where it used to
1151        // emit a lot of useless parse errors, after which it attempts to parse
1152        // another policy. There is no error in that policy, so it reports
1153        // exactly one error.
1154        let src = r#"
1155            permit(principal, action, !) when { principal.foo == resource.bar};
1156            permit(principal, action, resource);
1157        "#;
1158        let errs = assert_parse_fails(parse_policies, src);
1159        expect_exactly_one_error(
1160            src,
1161            &errs,
1162            &ExpectedErrorMessageBuilder::error("unexpected token `!`")
1163                .exactly_one_underline_with_label("!", "expected identifier")
1164                .build(),
1165        );
1166
1167        // Now there is another error which should also be reported.
1168        let src = r#"
1169            permit(principal, action, !) when { principal.foo == resource.bar};
1170            permit(principal, action, +);
1171        "#;
1172        let errs = assert_parse_fails(parse_policies, src);
1173        expect_some_error_matches(
1174            src,
1175            &errs,
1176            &ExpectedErrorMessageBuilder::error("unexpected token `!`")
1177                .exactly_one_underline_with_label("!", "expected identifier")
1178                .build(),
1179        );
1180        expect_some_error_matches(
1181            src,
1182            &errs,
1183            &ExpectedErrorMessageBuilder::error("unexpected token `+`")
1184                .exactly_one_underline_with_label("+", "expected identifier")
1185                .build(),
1186        );
1187        expect_n_errors(src, &errs, 2);
1188
1189        // Make sure nothing strange happens when there's no semicolon to be found.
1190        let src = r#"
1191            permit(principal, action, !) when { principal.foo == resource.bar}
1192        "#;
1193        let errs = assert_parse_fails(parse_policies, src);
1194        expect_exactly_one_error(
1195            src,
1196            &errs,
1197            &ExpectedErrorMessageBuilder::error("unexpected token `!`")
1198                .exactly_one_underline_with_label("!", "expected identifier")
1199                .build(),
1200        );
1201    }
1202
1203    #[test]
1204    fn extended_has() {
1205        assert_parse_succeeds(
1206            parse_policy,
1207            r#"
1208        permit(principal, action, resource) when {
1209          principal has a.b
1210        };
1211        "#,
1212        );
1213        assert_parse_succeeds(
1214            parse_policy,
1215            r#"
1216        permit(principal, action, resource) when {
1217          principal has a.if
1218        };
1219        "#,
1220        );
1221        assert_parse_succeeds(
1222            parse_policy,
1223            r#"
1224        permit(principal, action, resource) when {
1225          principal has if.a
1226        };
1227        "#,
1228        );
1229        assert_parse_succeeds(
1230            parse_policy,
1231            r#"
1232        permit(principal, action, resource) when {
1233          principal has if.if
1234        };
1235        "#,
1236        );
1237        assert_parse_succeeds(
1238            parse_policy,
1239            r#"
1240        permit(principal, action, resource) when {
1241          principal has true.if
1242        };
1243        "#,
1244        );
1245        assert_parse_succeeds(
1246            parse_policy,
1247            r#"
1248        permit(principal, action, resource) when {
1249          principal has if.true
1250        };
1251        "#,
1252        );
1253        assert_parse_succeeds(
1254            parse_policy,
1255            r#"
1256        permit(principal, action, resource) when {
1257          principal has if.then.else.in.like.has.is.__cedar
1258        };
1259        "#,
1260        );
1261        assert_parse_succeeds(
1262            parse_policy,
1263            r#"
1264        permit(principal, action, resource) when {
1265          principal has 1+1
1266        };
1267        "#,
1268        );
1269        assert_parse_succeeds(
1270            parse_policy,
1271            r#"permit(principal, action, resource) when {
1272            principal has a - 1
1273          };"#,
1274        );
1275        assert_parse_succeeds(
1276            parse_policy,
1277            r#"permit(principal, action, resource) when {
1278            principal has a*3 + 1
1279          };"#,
1280        );
1281        assert_parse_succeeds(
1282            parse_policy,
1283            r#"permit(principal, action, resource) when {
1284            principal has 3*a
1285          };"#,
1286        );
1287        assert_parse_succeeds(
1288            parse_policy,
1289            r#"permit(principal, action, resource) when {
1290                principal has -a.b
1291              };"#,
1292        );
1293        assert_parse_succeeds(
1294            parse_policy,
1295            r#"permit(principal, action, resource) when {
1296            principal has !a.b
1297          };"#,
1298        );
1299        assert_parse_succeeds(
1300            parse_policy,
1301            r#"permit(principal, action, resource) when {
1302            principal has a::b.c
1303          };"#,
1304        );
1305        assert_parse_succeeds(
1306            parse_policy,
1307            r#"permit(principal, action, resource) when {
1308            principal has A::""
1309          };"#,
1310        );
1311        assert_parse_succeeds(
1312            parse_policy,
1313            r#"permit(principal, action, resource) when {
1314            principal has A::"".a
1315          };"#,
1316        );
1317        assert_parse_succeeds(
1318            parse_policy,
1319            r#"permit(principal, action, resource) when {
1320            principal has ?principal
1321          };"#,
1322        );
1323        assert_parse_succeeds(
1324            parse_policy,
1325            r#"permit(principal, action, resource) when {
1326            principal has ?principal.a
1327          };"#,
1328        );
1329        assert_parse_succeeds(
1330            parse_policy,
1331            r#"
1332        permit(principal, action, resource) when {
1333            principal has (b).a
1334          };
1335        "#,
1336        );
1337        assert_parse_fails(
1338            parse_policy,
1339            r#"
1340        permit(principal, action, resource) when {
1341          principal has a.(b)
1342        };
1343        "#,
1344        );
1345        assert_parse_fails(
1346            parse_policy,
1347            r#"
1348        permit(principal, action, resource) when {
1349          principal has a.1
1350        };
1351        "#,
1352        );
1353    }
1354}