cedar_policy_validator/cedar_schema/
ast.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use std::{collections::BTreeMap, iter::once};
18
19use cedar_policy_core::{
20    ast::{Annotation, Annotations, AnyId, Id, InternalName},
21    parser::{Loc, Node},
22};
23use itertools::{Either, Itertools};
24use nonempty::NonEmpty;
25use smol_str::SmolStr;
26// We don't need this import on macOS but CI fails without it
27#[allow(unused_imports)]
28use smol_str::ToSmolStr;
29
30use crate::json_schema;
31
32use super::err::UserError;
33
34pub const BUILTIN_TYPES: [&str; 3] = ["Long", "String", "Bool"];
35
36pub(super) const CEDAR_NAMESPACE: &str = "__cedar";
37
38/// A struct that can be annotated, e.g., entity types.
39#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
40pub struct Annotated<T> {
41    /// The struct that's optionally annotated
42    pub data: T,
43    /// Annotations
44    pub annotations: Annotations,
45}
46
47pub type Schema = Vec<Annotated<Namespace>>;
48
49pub fn deduplicate_annotations<T>(
50    data: T,
51    annotations: Vec<Node<(Node<AnyId>, Option<Node<SmolStr>>)>>,
52) -> Result<Annotated<T>, UserError> {
53    let mut unique_annotations: BTreeMap<Node<AnyId>, Option<Node<SmolStr>>> = BTreeMap::new();
54    for annotation in annotations {
55        let (key, value) = annotation.node;
56        if let Some((old_key, _)) = unique_annotations.get_key_value(&key) {
57            return Err(UserError::DuplicateAnnotations(
58                key.node,
59                Node::with_source_loc((), old_key.loc.clone()),
60                Node::with_source_loc((), key.loc),
61            ));
62        } else {
63            unique_annotations.insert(key, value);
64        }
65    }
66    Ok(Annotated {
67        data,
68        annotations: unique_annotations
69            .into_iter()
70            .map(|(key, value)| {
71                let (val, loc) = match value {
72                    Some(n) => (Some(n.node), Some(n.loc)),
73                    None => (None, None),
74                };
75                (key.node, Annotation::with_optional_value(val, loc))
76            })
77            .collect(),
78    })
79}
80
81/// A path is a non empty list of identifiers that forms a namespace + type
82#[derive(Debug, Clone, PartialEq, Eq, Hash)]
83pub struct Path(Node<PathInternal>);
84impl Path {
85    /// Create a [`Path`] with a single entry
86    pub fn single(basename: Id, loc: Loc) -> Self {
87        Self(Node::with_source_loc(
88            PathInternal {
89                basename,
90                namespace: vec![],
91            },
92            loc,
93        ))
94    }
95
96    /// Create [`Path`] with a head and an iterator. Most significant name first.
97    pub fn new(basename: Id, namespace: impl IntoIterator<Item = Id>, loc: Loc) -> Self {
98        let namespace = namespace.into_iter().collect();
99        Self(Node::with_source_loc(
100            PathInternal {
101                basename,
102                namespace,
103            },
104            loc,
105        ))
106    }
107
108    /// Borrowed iteration of the [`Path`]'s elements. Most significant name first
109    pub fn iter(&self) -> impl Iterator<Item = &Id> {
110        self.0.node.iter()
111    }
112
113    /// Source [`Loc`] of this [`Path`]
114    pub fn loc(&self) -> &Loc {
115        &self.0.loc
116    }
117
118    /// Consume the [`Path`] and get an owned iterator over the elements. Most significant name first
119    pub fn into_iter(self) -> impl Iterator<Item = Node<Id>> {
120        let loc = self.0.loc;
121        self.0
122            .node
123            .into_iter()
124            .map(move |x| Node::with_source_loc(x, loc.clone()))
125    }
126
127    /// Get the base type name as well as the (potentially empty) prefix
128    pub fn split_last(self) -> (Vec<Id>, Id) {
129        (self.0.node.namespace, self.0.node.basename)
130    }
131
132    /// Is this referring to a name in the `__cedar` namespace (eg: `__cedar::Bool`)
133    pub fn is_in_cedar(&self) -> bool {
134        self.0.node.is_in_cedar()
135    }
136}
137
138impl From<Path> for InternalName {
139    fn from(value: Path) -> Self {
140        InternalName::new(
141            value.0.node.basename,
142            value.0.node.namespace,
143            Some(value.0.loc),
144        )
145    }
146}
147
148impl std::fmt::Display for Path {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(f, "{}", self.0.node)
151    }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Hash)]
155struct PathInternal {
156    basename: Id,
157    namespace: Vec<Id>,
158}
159
160impl PathInternal {
161    fn iter(&self) -> impl Iterator<Item = &Id> {
162        self.namespace.iter().chain(once(&self.basename))
163    }
164
165    fn into_iter(self) -> impl Iterator<Item = Id> {
166        self.namespace.into_iter().chain(once(self.basename))
167    }
168
169    /// Is this referring to a name _in_ the `__cedar` namespace (ex: `__cedar::Bool`)
170    fn is_in_cedar(&self) -> bool {
171        match self.namespace.as_slice() {
172            [id] => id.as_ref() == CEDAR_NAMESPACE,
173            _ => false,
174        }
175    }
176}
177
178impl std::fmt::Display for PathInternal {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        if self.namespace.is_empty() {
181            write!(f, "{}", self.basename)
182        } else {
183            let namespace = self.namespace.iter().map(|id| id.as_ref()).join("::");
184            write!(f, "{namespace}::{}", self.basename)
185        }
186    }
187}
188
189/// This struct represents Entity Uids in the Schema Syntax
190#[derive(Debug, Clone)]
191pub struct QualName {
192    pub path: Option<Path>,
193    pub eid: SmolStr,
194}
195
196impl QualName {
197    pub fn unqualified(eid: SmolStr) -> Self {
198        Self { path: None, eid }
199    }
200
201    pub fn qualified(path: Path, eid: SmolStr) -> Self {
202        Self {
203            path: Some(path),
204            eid,
205        }
206    }
207}
208
209/// A [`Namespace`] has a name and a collection declaration
210/// A schema is made up of a series of fragments
211/// A fragment is a series of namespaces
212#[derive(Debug, Clone)]
213pub struct Namespace {
214    /// The name of this namespace. If [`None`], then this is the unqualified namespace
215    pub name: Option<Path>,
216    /// The [`Declaration`]s contained in this namespace
217    pub decls: Vec<Annotated<Node<Declaration>>>,
218}
219
220impl Namespace {
221    /// Is this [`Namespace`] unqualfied?
222    pub fn is_unqualified(&self) -> bool {
223        self.name.is_none()
224    }
225}
226
227pub trait Decl {
228    fn names(&self) -> Vec<Node<SmolStr>>;
229}
230
231/// Schema Declarations,
232/// Defines either entity types, action types, or common types
233#[derive(Debug, Clone)]
234pub enum Declaration {
235    Entity(EntityDecl),
236    Action(ActionDecl),
237    Type(TypeDecl),
238}
239
240#[derive(Debug, Clone)]
241pub struct TypeDecl {
242    pub name: Node<Id>,
243    pub def: Node<Type>,
244}
245
246impl Decl for TypeDecl {
247    fn names(&self) -> Vec<Node<SmolStr>> {
248        vec![self.name.clone().map(|id| id.to_smolstr())]
249    }
250}
251
252/// Declaration of an entity type
253#[derive(Debug, Clone)]
254pub struct EntityDecl {
255    /// Entity Type Names bound by this declaration.
256    /// More than one name can be bound if they have the same definition, for convenience
257    pub names: NonEmpty<Node<Id>>,
258    /// Entity Types this type is allowed to be related to via the `in` relation
259    pub member_of_types: Vec<Path>,
260    /// Attributes this entity has
261    pub attrs: Node<Vec<Node<Annotated<AttrDecl>>>>,
262    /// Tag type for this entity (`None` means no tags on this entity)
263    pub tags: Option<Node<Type>>,
264}
265
266/// Type definitions
267#[derive(Debug, Clone)]
268pub enum Type {
269    /// A set of types
270    Set(Box<Node<Type>>),
271    /// A [`Path`] that could either refer to a Common Type or an Entity Type
272    Ident(Path),
273    /// A Record
274    Record(Vec<Node<Annotated<AttrDecl>>>),
275}
276
277/// Primitive Type Definitions
278#[derive(Debug, Clone)]
279pub enum PrimitiveType {
280    /// Cedar Longs
281    Long,
282    /// Cedar Strings
283    String,
284    /// Cedar booleans
285    Bool,
286}
287
288impl<N> From<PrimitiveType> for json_schema::TypeVariant<N> {
289    fn from(value: PrimitiveType) -> Self {
290        match value {
291            PrimitiveType::Long => json_schema::TypeVariant::Long,
292            PrimitiveType::String => json_schema::TypeVariant::String,
293            PrimitiveType::Bool => json_schema::TypeVariant::Boolean,
294        }
295    }
296}
297
298/// Attribute declarations, used in records and entity types.
299/// One [`AttrDecl`] is one key-value pair.
300#[derive(Debug, Clone)]
301pub struct AttrDecl {
302    /// Name of this attribute
303    pub name: Node<SmolStr>,
304    /// Whether or not it is a required attribute (default `true`)
305    pub required: bool,
306    /// The type of this attribute
307    pub ty: Node<Type>,
308}
309
310/// The target of a [`PRAppDecl`]
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum PR {
313    /// Applies to the `principal` variable
314    Principal,
315    /// Applies to the `resource` variable
316    Resource,
317}
318
319impl std::fmt::Display for PR {
320    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321        match self {
322            PR::Principal => write!(f, "principal"),
323            PR::Resource => write!(f, "resource"),
324        }
325    }
326}
327
328/// A declaration that defines what kind of entities this action can be applied against
329#[derive(Debug, Clone)]
330pub struct PRAppDecl {
331    /// Is this constraining the `principal` or the `resource`
332    pub kind: Node<PR>,
333    /// What entity types are allowed?
334    pub entity_tys: NonEmpty<Path>,
335}
336
337/// A declaration of constraints on an action type
338#[derive(Debug, Clone)]
339pub enum AppDecl {
340    /// Constraints on the `principal` or `resource`
341    PR(PRAppDecl),
342    /// Constraints on the `context`
343    Context(Either<Path, Node<Vec<Node<Annotated<AttrDecl>>>>>),
344}
345
346/// An action declaration
347#[derive(Debug, Clone)]
348pub struct ActionDecl {
349    /// The names this declaration is binding.
350    /// More than one name can be bound if they have the same definition, for convenience.
351    pub names: NonEmpty<Node<SmolStr>>,
352    /// The parents of this action
353    pub parents: Option<NonEmpty<Node<QualName>>>,
354    /// The constraining clauses in this declarations
355    pub app_decls: Option<Node<NonEmpty<Node<AppDecl>>>>,
356}
357
358impl Decl for ActionDecl {
359    fn names(&self) -> Vec<Node<SmolStr>> {
360        self.names.iter().cloned().collect()
361    }
362}
363
364#[cfg(test)]
365mod test {
366    use std::sync::Arc;
367
368    use super::*;
369
370    fn loc() -> Loc {
371        Loc::new((1, 1), Arc::from("foo"))
372    }
373
374    // Ensure the iterators over [`Path`]s return most significant names first
375    #[test]
376    fn path_iter() {
377        let p = Path::new(
378            "baz".parse().unwrap(),
379            ["foo".parse().unwrap(), "bar".parse().unwrap()],
380            loc(),
381        );
382
383        let expected: Vec<Id> = vec![
384            "foo".parse().unwrap(),
385            "bar".parse().unwrap(),
386            "baz".parse().unwrap(),
387        ];
388
389        let expected_borrowed = expected.iter().collect::<Vec<_>>();
390
391        let borrowed = p.iter().collect::<Vec<_>>();
392        assert_eq!(borrowed, expected_borrowed);
393        let moved = p.into_iter().map(|n| n.node).collect::<Vec<_>>();
394        assert_eq!(moved, expected);
395    }
396}