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)]
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(err::ToCSTError::from_raw_err_recovery)
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).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        let success = policies
1007            .0
1008            .into_iter()
1009            .filter_map(|p| p.node)
1010            .collect::<Vec<_>>();
1011        assert!(success.len() == 2);
1012    }
1013
1014    #[test]
1015    fn policy_annotations() {
1016        let policies = assert_parse_succeeds(
1017            parse_policies,
1018            r#"
1019            @anno("good annotation") permit (principal, action, resource);
1020            @anno1("good")@anno2("annotation") permit (principal, action, resource);
1021            @long6wordphraseisident007("good annotation") permit (principal, action, resource);
1022            @   spacy  (  "  good  annotation  "  )   permit (principal, action, resource);
1023        "#,
1024        );
1025        // should have successfully parsed 4 policies
1026        assert_eq!(
1027            policies
1028                .0
1029                .into_iter()
1030                .filter_map(|p| p.node)
1031                .collect::<Vec<_>>()
1032                .len(),
1033            4
1034        );
1035
1036        let src = r#"
1037            @bad-annotation("bad") permit (principal, action, resource);
1038        "#;
1039        let errs = assert_parse_fails(parse_policies, src);
1040        expect_exactly_one_error(
1041            src,
1042            &errs,
1043            &ExpectedErrorMessageBuilder::error("unexpected token `-`")
1044                .exactly_one_underline_with_label("-", "expected `(`")
1045                .build(),
1046        );
1047
1048        let src = r#"
1049            @bad_annotation("bad","annotation") permit (principal, action, resource);
1050        "#;
1051        let errs = assert_parse_fails(parse_policies, src);
1052        expect_exactly_one_error(
1053            src,
1054            &errs,
1055            &ExpectedErrorMessageBuilder::error("unexpected token `,`")
1056                .exactly_one_underline_with_label(",", "expected `)`")
1057                .build(),
1058        );
1059
1060        let src = r#"
1061            @bad_annotation(bad_annotation) permit (principal, action, resource);
1062        "#;
1063        let errs = assert_parse_fails(parse_policies, src);
1064        expect_exactly_one_error(
1065            src,
1066            &errs,
1067            &ExpectedErrorMessageBuilder::error("unexpected token `bad_annotation`")
1068                .exactly_one_underline_with_label("bad_annotation", "expected string literal")
1069                .build(),
1070        );
1071
1072        let src = r#"
1073            permit (@comment("your name here") principal, action, resource);
1074        "#;
1075        let errs = assert_parse_fails(parse_policies, src);
1076        expect_exactly_one_error(
1077            src,
1078            &errs,
1079            &ExpectedErrorMessageBuilder::error("unexpected token `@`")
1080                .exactly_one_underline_with_label("@", "expected `)` or identifier")
1081                .build(),
1082        );
1083
1084        let src = r#"
1085            @hi mom("this should be invalid")
1086            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 `mom`")
1093                .exactly_one_underline_with_label("mom", "expected `(`")
1094                .build(),
1095        );
1096
1097        let src = r#"
1098            @hi+mom("this should be invalid")
1099            permit(principal, action, resource);
1100        "#;
1101        let errs = assert_parse_fails(parse_policies, src);
1102        expect_exactly_one_error(
1103            src,
1104            &errs,
1105            &ExpectedErrorMessageBuilder::error("unexpected token `+`")
1106                .exactly_one_underline_with_label("+", "expected `(`")
1107                .build(),
1108        );
1109    }
1110
1111    #[test]
1112    fn parse_idempotent() {
1113        let many_policies =
1114            std::fs::read_to_string("src/parser/testfiles/policies.cedar").expect("missing file");
1115        let cst1 = assert_parse_succeeds(parse_policies, &many_policies);
1116        let revert = format!("{}", cst1);
1117        let cst2 = assert_parse_succeeds(parse_policies, &revert);
1118        assert_eq!(cst1, cst2);
1119    }
1120
1121    #[test]
1122    fn error_recovery() {
1123        // After hitting an unexpected `!`, the parser skips ahead until it
1124        // finds a `;`, skipping over the body of the policy where it used to
1125        // emit a lot of useless parse errors, after which it attempts to parse
1126        // another policy. There is no error in that policy, so it reports
1127        // exactly one error.
1128        let src = r#"
1129            permit(principal, action, !) when { principal.foo == resource.bar};
1130            permit(principal, action, resource);
1131        "#;
1132        let errs = assert_parse_fails(parse_policies, src);
1133        expect_exactly_one_error(
1134            src,
1135            &errs,
1136            &ExpectedErrorMessageBuilder::error("unexpected token `!`")
1137                .exactly_one_underline_with_label("!", "expected identifier")
1138                .build(),
1139        );
1140
1141        // Now there is another error which should also be reported.
1142        let src = r#"
1143            permit(principal, action, !) when { principal.foo == resource.bar};
1144            permit(principal, action, +);
1145        "#;
1146        let errs = assert_parse_fails(parse_policies, src);
1147        expect_some_error_matches(
1148            src,
1149            &errs,
1150            &ExpectedErrorMessageBuilder::error("unexpected token `!`")
1151                .exactly_one_underline_with_label("!", "expected identifier")
1152                .build(),
1153        );
1154        expect_some_error_matches(
1155            src,
1156            &errs,
1157            &ExpectedErrorMessageBuilder::error("unexpected token `+`")
1158                .exactly_one_underline_with_label("+", "expected identifier")
1159                .build(),
1160        );
1161        expect_n_errors(src, &errs, 2);
1162
1163        // Make sure nothing strange happens when there's no semicolon to be found.
1164        let src = r#"
1165            permit(principal, action, !) when { principal.foo == resource.bar}
1166        "#;
1167        let errs = assert_parse_fails(parse_policies, src);
1168        expect_exactly_one_error(
1169            src,
1170            &errs,
1171            &ExpectedErrorMessageBuilder::error("unexpected token `!`")
1172                .exactly_one_underline_with_label("!", "expected identifier")
1173                .build(),
1174        );
1175    }
1176}