cedar-policy-core 4.10.0

Core implementation of the Cedar policy language
Documentation
//
// Copyright Cedar Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use std::str::FromStr;
use std::sync::Arc;
use crate::validator::cedar_schema::err::{RawErrorRecovery, UserError};
use crate::parser::{Node, Loc, unescape::to_unescaped_string, cst::Ref};
use crate::ast::{Id, AnyId, Annotations};
use smol_str::SmolStr;
use smol_str::ToSmolStr;
use crate::validator::cedar_schema::ast::{
    Path,
    EntityDecl,
    StandardEntityDecl,
    EnumEntityDecl,
    Declaration,
    Namespace,
    Schema as ASchema,
    Type as SType,
    AttrDecl,
    ActionDecl,
    PR,
    AppDecl,
    TypeDecl,
    PrimitiveType,
    QualName,
    PRAppDecl,
    deduplicate_annotations,
    Annotated,
};
use nonempty::{NonEmpty, nonempty};
use itertools::Either;
use std::collections::BTreeMap;

use lalrpop_util::{ParseError, ErrorRecovery};


/// `errors` collects generated errors.
///
/// `src` is the (full) original source being parsed, which the source locations l,r index into.
grammar<'err, 's>(errors: &'err mut Vec<RawErrorRecovery<'input>>, src: &'s Arc<str>, keep_src: bool);

extern {
    type Error = UserError;
}

match {
    // Whitespace and comments
    r"\s*" => { }, // The default whitespace skipping is disabled an `ignore pattern` is specified
    r"//[^\n\r]*[\n\r]*" => { }, // Skip `// comments`

    // keywords
    "namespace" => NAMESPACE,
    "entity" => ENTITY,
    "in" => IN,
    "type" => TYPE,
    "Set" => SET,
    "appliesTo" => APPLIESTO,
    "principal" => PRINCIPAL,
    "action" => ACTION,
    "resource" => RESOURCE,
    "context" => CONTEXT,
    "attributes" => ATTRIBUTES,
    "tags" => TAGS,
    "Long" => LONG,
    "String" => STRING,
    "Bool" => BOOL,
    "enum" => ENUM,

    // data input
    r"[_a-zA-Z][_a-zA-Z0-9]*" => IDENTIFIER,
    // The `NUMBER` token is a positive integer.
    // Negative number literals are negation operations.
    r"[0-9]+" => NUMBER,
    r#""(\\.|[^"\\])*""# => STRINGLIT,

    // other tokens
    ",", ";", ":", "::", "{", "}", "[", "]",
    "<", ">", "=", "?", "@", "(", ")",

}

// Annotations := {'@' AnyIdent '(' String ')'}
Annotation: Node<(Node<AnyId>, Option<Node<SmolStr>>)> = {
    <l:@L> "@" <key:AnyIdent> <value: ("(" <STR> ")")?> <r:@R> => Node::with_source_loc((key, value), Loc::new(l..r, Arc::clone(src)))
}

Annotated<E>: Annotated<E> = {
   <annotations: Annotation*> <e:E> =>? {
        Ok(deduplicate_annotations(e, annotations)?)
    },
}

// Schema := {Namespace}
pub Schema: ASchema = {
    <ns: Namespace*> => ns,
}

#[inline]
Namedspace: Namespace = {
     <l:@L> NAMESPACE <p: Path> "{" <decls: Annotated<Decl>*> "}" <r:@R> 
        => Namespace { name: Some(p), decls, loc: Some(Loc::new(l..r, Arc::clone(src)))},
}

// Namespace := 'namespace' Path '{' {Decl} '}'
Namespace: Annotated<Namespace> = {
    <ns: Annotated<Namedspace>> => ns,
    <l:@L> <decl: Annotated<Decl>> <r:@R> => Annotated {data: Namespace {name: None, decls: vec![decl], loc: Some(Loc::new(l..r, Arc::clone(src)))}, annotations: Annotations::new()},
}

// Decl := Entity | Action | TypeDecl
Decl: Node<Declaration> = {
    <e:Entity> => e,
    <a:Action> => a,
    <t:TypeDecl> => t,
}

// Entity := 'entity' Idents ['in' EntTypes] [['='] RecType] ';' <r:@R>
Entity: Node<Declaration> = {
    <l1:@L> ENTITY <ets: Idents> <ps:(IN <EntTypes>)?> <l2:@L> <ds:("="? "{" <AttrDecls?> "}")?> <r2:@R> <ts:(TAGS <Type>)?> ";" <r1:@R>
        => Node::with_source_loc(Declaration::Entity(EntityDecl::Standard(StandardEntityDecl {
            names: ets,
            member_of_types: ps.unwrap_or_default(),
            attrs: Node::with_source_loc(ds.map(|ds| ds.unwrap_or_default()).unwrap_or_default(), Loc::new(l2..r2, Arc::clone(src))),
            tags: ts,
            })), Loc::new(l1..r1, Arc::clone(src))),
    <l:@L> ENTITY <ets: Idents> ENUM "[" <choices: STRs> "]" ";" <r:@R> => Node::with_source_loc(Declaration::Entity(EntityDecl::Enum(EnumEntityDecl {
        names: ets,
        choices,
    })), Loc::new(l..r, Arc::clone(src))),
}

// Action := 'action' Names ['in' QualNameOrNames]
Action: Node<Declaration> = {
    <l:@L> ACTION <ns:Names> <ps:(IN <QualNameOrQualNames>)?> <ads:(APPLIESTO "{" <AppDecls> "}")?> <attrs:(ATTRIBUTES "{" "}")?>";" <r:@R>
        => Node::with_source_loc(Declaration::Action(ActionDecl { names: ns, parents: ps, app_decls: ads}), Loc::new(l..r, Arc::clone(src))),
}

TypeDecl: Node<Declaration> = {
    <l:@L> TYPE <i:Ident> "=" <t:Type> ";" <r:@R>
        => Node::with_source_loc(Declaration::Type(TypeDecl { name : i, def : t}), Loc::new(l..r, Arc::clone(src))),
}

// AppDecls := ('principal' | 'resource') ':' EntTypes [',' | ',' AppDecls]
//          | 'context' ':' (Path | RecType) [',' | ',' AppDecls]
AppDecls: Node<NonEmpty<Node<AppDecl>>> = {
    <l:@L> <pr: PrincipalOrResource> ":" <ets:EntTypes> <r:@R> ","?
        => {
            let entity_tys: Option<NonEmpty<Path>> = NonEmpty::collect(ets.into_iter());
            Node::with_source_loc(
                nonempty![Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys }), Loc::new(l..r, Arc::clone(src)))],
                Loc::new(l..r, Arc::clone(src)))
        },
    <l:@L> <pr: PrincipalOrResource> ":" <ets:EntTypes> <r:@R> "," <mut ds: AppDecls>
        => {
            let (mut ds, _) = ds.into_inner();
            let entity_tys: Option<NonEmpty<Path>> = NonEmpty::collect(ets.into_iter());
            ds.insert(0, Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys }), Loc::new(l..r, Arc::clone(src))));
            Node::with_source_loc(ds, Loc::new(l..r, Arc::clone(src)))
        },
    <l:@L> CONTEXT ":" <p:Path> ","? <r:@R>
        =>  Node::with_source_loc(
                nonempty![Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src)))],
                Loc::new(l..r, Arc::clone(src))),
    <l:@L> CONTEXT ":" <p:Path> "," <r:@R> <mut ds: AppDecls>
        => {
            let (mut ds, _) = ds.into_inner();
            ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src))));
            Node::with_source_loc(
                ds,
                Loc::new(l..r, Arc::clone(src)))
        },
    <l1:@L> CONTEXT ":" <l2:@L> "{" <attrs:AttrDecls?> "}" <r2:@R> ","? <r1:@R>
        =>
            Node::with_source_loc(
                nonempty![Node::with_source_loc(AppDecl::Context(Either::Right(Node::with_source_loc(attrs.unwrap_or_default(), Loc::new(l2..r2, Arc::clone(src))))), Loc::new(l1..r1, Arc::clone(src)))],
                Loc::new(l1..r1, Arc::clone(src))),
    <l1:@L> CONTEXT ":" <l2:@L> "{" <attrs:AttrDecls?> "}" <r2:@R> "," <r1:@R> <mut ds: AppDecls>
        => {
            let (mut ds, _) = ds.into_inner();
            ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Right(Node::with_source_loc(attrs.unwrap_or_default(), Loc::new(l2..r2, Arc::clone(src))))), Loc::new(l1..r1, Arc::clone(src))));
            Node::with_source_loc(
                ds,
                Loc::new(l1..r1, Arc::clone(src)))
        },
}

// SetType := 'Set' '<' Type '>'
// RecType := '{' [AttrDecls] '}'
// Type := PRIMTYPE | Path | SetType | RecType
pub Type: Node<SType> = {
    <p:Path>
        => { let loc = p.loc().cloned(); Node::with_maybe_source_loc(SType::Ident(p), loc) },
    <l:@L> SET "<" <t:Type> ">" <r:@R>
        => Node::with_source_loc(SType::Set(Box::new(t)), Loc::new(l..r, Arc::clone(src))),
    <l:@L> "{" <ds:AttrDecls?> "}" <r:@R>
        => Node::with_source_loc(SType::Record(ds.unwrap_or_default()), Loc::new(l..r, Arc::clone(src))),
}

// AttrDecls := Annotation* Name ['?'] ':' Type [',' | ',' AttrDecls]
AttrDecls: Vec<Node<Annotated<AttrDecl>>> = {
    <l:@L> <annotations: Annotation*> <name: Name> <required:"?"?> ":" <ty:Type> ","? <r:@R>
        =>? Ok(deduplicate_annotations(AttrDecl { name, required: required.is_none(), ty}, annotations).map(|decl| vec![Node::with_source_loc(decl, Loc::new(l..r, Arc::clone(src)))])?),
    <l:@L> <annotations: Annotation*> <name: Name> <required:"?"?> ":" <ty:Type> "," <r:@R> <mut ds: AttrDecls>
        =>? {ds.insert(0, deduplicate_annotations(AttrDecl { name, required: required.is_none(), ty}, annotations).map(|decl| Node::with_source_loc(decl, Loc::new(l..r, Arc::clone(src))))?); Ok(ds)},
}

Comma<E>: Vec<E> = {
    <e:E?> => e.into_iter().collect(),
    <mut es:(<E> ",")+> <e:E> => {
        es.push(e);
        es
    },
}

// IDENT := ['_''a'-'z''A'-'Z']['_''a'-'z''A'-'Z''0'-'9']* - RESERVED
Ident: Node<Id> = {
    <id: AnyIdent> =>? Id::from_str(id.node.as_ref()).map(|i| Node::with_maybe_source_loc(i, id.loc.clone())).map_err(|err : crate::parser::err::ParseErrors| ParseError::User {
        error: UserError::ReservedIdentifierUsed(Node::with_maybe_source_loc(id.node.to_smolstr(), id.loc.clone()))
    }),
}

// AnyIdent := ['_''a'-'z''A'-'Z']['_''a'-'z''A'-'Z''0'-'9']*
AnyIdent: Node<AnyId> = {
    <l:@L> NAMESPACE <r:@R>
        => Node::with_source_loc("namespace".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> ENTITY <r:@R>
        => Node::with_source_loc("entity".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> SET <r:@R>
        => Node::with_source_loc("Set".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> APPLIESTO <r:@R>
        => Node::with_source_loc("appliesTo".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> PRINCIPAL <r:@R>
        => Node::with_source_loc("principal".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> ACTION <r:@R>
        => Node::with_source_loc("action".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> RESOURCE <r:@R>
        => Node::with_source_loc("resource".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> CONTEXT <r:@R>
        => Node::with_source_loc("context".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> ATTRIBUTES <r:@R>
        => Node::with_source_loc("attributes".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> TAGS <r:@R>
        => Node::with_source_loc("tags".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> BOOL <r:@R>
        => Node::with_source_loc("Bool".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> LONG <r:@R>
        => Node::with_source_loc("Long".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> STRING <r:@R>
        => Node::with_source_loc("String".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> TYPE <r:@R>
        => Node::with_source_loc("type".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> IN <r:@R>
        => Node::with_source_loc("in".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> ENUM <r:@R>
        => Node::with_source_loc("enum".parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
    <l:@L> <i:IDENTIFIER> <r:@R>
        => Node::with_source_loc(i.parse().unwrap(), Loc::new(l..r, Arc::clone(src))),
}

STR: Node<SmolStr> = {
    <l:@L> <s:STRINGLIT> <r:@R> =>? {
        #[allow(clippy::string_slice, reason = "STRINGLIT token has at least two characters, starting and ending with the ASCII character `\"`")]
        to_unescaped_string(&s[1..(s.len() - 1)]).map_or_else(|e| Err(ParseError::User {
            error: UserError::StringEscape(Node::with_source_loc(e, Loc::new(l..r, Arc::clone(src)))),
        }), |v| Ok(Node::with_source_loc(v, Loc::new(l..r, Arc::clone(src)))))
    },
}

// Name := IDENT | STR
Name: Node<SmolStr> = {
   <id: Ident> => id.map(|id| id.to_smolstr()),
   <s: STR> => s,
}

// QualName      := Name | Path '::' STR
QualName : Node<QualName> = {
    <l:@L> <p : PathInline> "::" <s:STR> <r:@R> => Node::with_source_loc(QualName::qualified(p, s.node), Loc::new(l..r, Arc::clone(src))),
    <l:@L> <i : Name> <r:@R> => Node::with_source_loc(QualName::unqualified(i.node), Loc::new(l..r, Arc::clone(src))),
}

// Path := IDENT {'::' IDENT}
#[inline]
PathInline: Path = {
    <l:@L> <i:Ident> <r:@R>
        => Path::single(i.node, Some(Loc::new(l..r, Arc::clone(src)))),
    <l:@L> <is:(<Ident> "::")+> <i:Ident> <r:@R>
        => Path::new(i.node, is.into_iter().map(|n| n.node), Some(Loc::new(l..r, Arc::clone(src)))),
}

NonEmptyComma<E>: NonEmpty<E> = {
    <e:E> => NonEmpty::singleton(e),
    <es:(<E> ",")+> <e:E> => {
        let mut all = NonEmpty::from_vec(es).unwrap();
        all.push(e);
        all
    },
}

// Idents := IDENT {',' IDENT}
Idents: NonEmpty<Node<Id>> = NonEmptyComma<Ident>;

// Names := Name {',' Name}
Names: NonEmpty<Node<SmolStr>> = NonEmptyComma<Name>;

// Qualnames := Qualname {',' Qualname }
QualNames : NonEmpty<Node<QualName>> = NonEmptyComma<QualName>;

// STRs := STR {',' STR}
STRs: NonEmpty<Node<SmolStr>> = NonEmptyComma<STR>;

PrincipalOrResource: Node<PR> = {
    <l:@L> PRINCIPAL <r:@R> => Node::with_source_loc(PR::Principal, Loc::new(l..r, Arc::clone(src))),
    <l:@L> RESOURCE <r:@R> => Node::with_source_loc(PR::Resource, Loc::new(l..r, Arc::clone(src))),
}

// EntTypes := Path | '[' [Path {',' Path}] ']'
EntTypes: Vec<Path> = {
    <et: Path>
        => vec![et],
    "[" <ets: Comma<Path>> "]"
        => ets,
}

QualNameOrQualNames: NonEmpty<Node<QualName>> = {
    <qn : QualName> => NonEmpty::singleton(qn),
    "[" <ns: QualNames> "]" => ns,
}

Path: Path = PathInline;