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
49#[allow(clippy::type_complexity)]
50pub fn deduplicate_annotations<T>(
51    data: T,
52    annotations: Vec<Node<(Node<AnyId>, Option<Node<SmolStr>>)>>,
53) -> Result<Annotated<T>, UserError> {
54    let mut unique_annotations: BTreeMap<Node<AnyId>, Option<Node<SmolStr>>> = BTreeMap::new();
55    for annotation in annotations {
56        let (key, value) = annotation.node;
57        if let Some((old_key, _)) = unique_annotations.get_key_value(&key) {
58            return Err(UserError::DuplicateAnnotations(
59                key.node,
60                Node::with_source_loc((), old_key.loc.clone()),
61                Node::with_source_loc((), key.loc),
62            ));
63        } else {
64            unique_annotations.insert(key, value);
65        }
66    }
67    Ok(Annotated {
68        data,
69        annotations: unique_annotations
70            .into_iter()
71            .map(|(key, value)| {
72                let (val, loc) = match value {
73                    Some(n) => (Some(n.node), Some(n.loc)),
74                    None => (None, None),
75                };
76                (key.node, Annotation::with_optional_value(val, loc))
77            })
78            .collect(),
79    })
80}
81
82/// A path is a non empty list of identifiers that forms a namespace + type
83#[derive(Debug, Clone, PartialEq, Eq, Hash)]
84pub struct Path(Node<PathInternal>);
85impl Path {
86    /// Create a [`Path`] with a single entry
87    pub fn single(basename: Id, loc: Loc) -> Self {
88        Self(Node::with_source_loc(
89            PathInternal {
90                basename,
91                namespace: vec![],
92            },
93            loc,
94        ))
95    }
96
97    /// Create [`Path`] with a head and an iterator. Most significant name first.
98    pub fn new(basename: Id, namespace: impl IntoIterator<Item = Id>, loc: Loc) -> Self {
99        let namespace = namespace.into_iter().collect();
100        Self(Node::with_source_loc(
101            PathInternal {
102                basename,
103                namespace,
104            },
105            loc,
106        ))
107    }
108
109    /// Borrowed iteration of the [`Path`]'s elements. Most significant name first
110    pub fn iter(&self) -> impl Iterator<Item = &Id> {
111        self.0.node.iter()
112    }
113
114    /// Source [`Loc`] of this [`Path`]
115    pub fn loc(&self) -> &Loc {
116        &self.0.loc
117    }
118
119    /// Consume the [`Path`] and get an owned iterator over the elements. Most significant name first
120    #[allow(clippy::should_implement_trait)] // difficult to write the `IntoIter` type for this implementation
121    pub fn into_iter(self) -> impl Iterator<Item = Node<Id>> {
122        let loc = self.0.loc;
123        self.0
124            .node
125            .into_iter()
126            .map(move |x| Node::with_source_loc(x, loc.clone()))
127    }
128
129    /// Get the base type name as well as the (potentially empty) prefix
130    pub fn split_last(self) -> (Vec<Id>, Id) {
131        (self.0.node.namespace, self.0.node.basename)
132    }
133
134    /// Is this referring to a name in the `__cedar` namespace (eg: `__cedar::Bool`)
135    pub fn is_in_cedar(&self) -> bool {
136        self.0.node.is_in_cedar()
137    }
138}
139
140impl From<Path> for InternalName {
141    fn from(value: Path) -> Self {
142        InternalName::new(
143            value.0.node.basename,
144            value.0.node.namespace,
145            Some(value.0.loc),
146        )
147    }
148}
149
150impl std::fmt::Display for Path {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        write!(f, "{}", self.0.node)
153    }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Hash)]
157struct PathInternal {
158    basename: Id,
159    namespace: Vec<Id>,
160}
161
162impl PathInternal {
163    fn iter(&self) -> impl Iterator<Item = &Id> {
164        self.namespace.iter().chain(once(&self.basename))
165    }
166
167    /// Is this referring to a name _in_ the `__cedar` namespace (ex: `__cedar::Bool`)
168    fn is_in_cedar(&self) -> bool {
169        match self.namespace.as_slice() {
170            [id] => id.as_ref() == CEDAR_NAMESPACE,
171            _ => false,
172        }
173    }
174}
175
176impl IntoIterator for PathInternal {
177    type Item = Id;
178    type IntoIter = std::iter::Chain<<Vec<Id> as IntoIterator>::IntoIter, std::iter::Once<Id>>;
179
180    fn into_iter(self) -> Self::IntoIter {
181        self.namespace.into_iter().chain(once(self.basename))
182    }
183}
184
185impl std::fmt::Display for PathInternal {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        if self.namespace.is_empty() {
188            write!(f, "{}", self.basename)
189        } else {
190            let namespace = self.namespace.iter().map(|id| id.as_ref()).join("::");
191            write!(f, "{namespace}::{}", self.basename)
192        }
193    }
194}
195
196/// This struct represents Entity Uids in the Schema Syntax
197#[derive(Debug, Clone)]
198pub struct QualName {
199    pub path: Option<Path>,
200    pub eid: SmolStr,
201}
202
203impl QualName {
204    pub fn unqualified(eid: SmolStr) -> Self {
205        Self { path: None, eid }
206    }
207
208    pub fn qualified(path: Path, eid: SmolStr) -> Self {
209        Self {
210            path: Some(path),
211            eid,
212        }
213    }
214}
215
216/// A [`Namespace`] has a name and a collection declaration
217/// A schema is made up of a series of fragments
218/// A fragment is a series of namespaces
219#[derive(Debug, Clone)]
220pub struct Namespace {
221    /// The name of this namespace. If [`None`], then this is the unqualified namespace
222    pub name: Option<Path>,
223    /// The [`Declaration`]s contained in this namespace
224    pub decls: Vec<Annotated<Node<Declaration>>>,
225    pub loc: Option<Loc>,
226}
227
228impl Namespace {
229    /// Is this [`Namespace`] unqualfied?
230    pub fn is_unqualified(&self) -> bool {
231        self.name.is_none()
232    }
233}
234
235pub trait Decl {
236    fn names(&self) -> Vec<Node<SmolStr>>;
237}
238
239/// Schema Declarations,
240/// Defines either entity types, action types, or common types
241#[derive(Debug, Clone)]
242pub enum Declaration {
243    Entity(EntityDecl),
244    Action(ActionDecl),
245    Type(TypeDecl),
246}
247
248#[derive(Debug, Clone)]
249pub struct TypeDecl {
250    pub name: Node<Id>,
251    pub def: Node<Type>,
252}
253
254impl Decl for TypeDecl {
255    fn names(&self) -> Vec<Node<SmolStr>> {
256        vec![self.name.clone().map(|id| id.to_smolstr())]
257    }
258}
259
260#[derive(Debug, Clone)]
261pub enum EntityDecl {
262    Standard(StandardEntityDecl),
263    Enum(EnumEntityDecl),
264}
265
266impl EntityDecl {
267    pub fn names(&self) -> impl Iterator<Item = &Node<Id>> + '_ {
268        match self {
269            Self::Enum(d) => d.names.iter(),
270            Self::Standard(d) => d.names.iter(),
271        }
272    }
273}
274
275/// Declaration of an entity type
276#[derive(Debug, Clone)]
277pub struct StandardEntityDecl {
278    /// Entity Type Names bound by this declaration.
279    /// More than one name can be bound if they have the same definition, for convenience
280    pub names: NonEmpty<Node<Id>>,
281    /// Entity Types this type is allowed to be related to via the `in` relation
282    pub member_of_types: Vec<Path>,
283    /// Attributes this entity has
284    pub attrs: Node<Vec<Node<Annotated<AttrDecl>>>>,
285    /// Tag type for this entity (`None` means no tags on this entity)
286    pub tags: Option<Node<Type>>,
287}
288
289/// Declaration of an entity type
290#[derive(Debug, Clone)]
291pub struct EnumEntityDecl {
292    pub names: NonEmpty<Node<Id>>,
293    pub choices: NonEmpty<Node<SmolStr>>,
294}
295
296/// Type definitions
297#[derive(Debug, Clone)]
298pub enum Type {
299    /// A set of types
300    Set(Box<Node<Type>>),
301    /// A [`Path`] that could either refer to a Common Type or an Entity Type
302    Ident(Path),
303    /// A Record
304    Record(Vec<Node<Annotated<AttrDecl>>>),
305}
306
307/// Primitive Type Definitions
308#[derive(Debug, Clone)]
309pub enum PrimitiveType {
310    /// Cedar Longs
311    Long,
312    /// Cedar Strings
313    String,
314    /// Cedar booleans
315    Bool,
316}
317
318impl<N> From<PrimitiveType> for json_schema::TypeVariant<N> {
319    fn from(value: PrimitiveType) -> Self {
320        match value {
321            PrimitiveType::Long => json_schema::TypeVariant::Long,
322            PrimitiveType::String => json_schema::TypeVariant::String,
323            PrimitiveType::Bool => json_schema::TypeVariant::Boolean,
324        }
325    }
326}
327
328/// Attribute declarations, used in records and entity types.
329/// One [`AttrDecl`] is one key-value pair.
330#[derive(Debug, Clone)]
331pub struct AttrDecl {
332    /// Name of this attribute
333    pub name: Node<SmolStr>,
334    /// Whether or not it is a required attribute (default `true`)
335    pub required: bool,
336    /// The type of this attribute
337    pub ty: Node<Type>,
338}
339
340/// The target of a [`PRAppDecl`]
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum PR {
343    /// Applies to the `principal` variable
344    Principal,
345    /// Applies to the `resource` variable
346    Resource,
347}
348
349impl std::fmt::Display for PR {
350    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351        match self {
352            PR::Principal => write!(f, "principal"),
353            PR::Resource => write!(f, "resource"),
354        }
355    }
356}
357
358/// A declaration that defines what kind of entities this action can be applied against
359#[derive(Debug, Clone)]
360pub struct PRAppDecl {
361    /// Is this constraining the `principal` or the `resource`
362    pub kind: Node<PR>,
363    /// What entity types are allowed? `None` means none
364    pub entity_tys: Option<NonEmpty<Path>>,
365}
366
367/// A declaration of constraints on an action type
368#[derive(Debug, Clone)]
369pub enum AppDecl {
370    /// Constraints on the `principal` or `resource`
371    PR(PRAppDecl),
372    /// Constraints on the `context`
373    Context(Either<Path, Node<Vec<Node<Annotated<AttrDecl>>>>>),
374}
375
376/// An action declaration
377#[derive(Debug, Clone)]
378pub struct ActionDecl {
379    /// The names this declaration is binding.
380    /// More than one name can be bound if they have the same definition, for convenience.
381    pub names: NonEmpty<Node<SmolStr>>,
382    /// The parents of this action
383    pub parents: Option<NonEmpty<Node<QualName>>>,
384    /// The constraining clauses in this declarations
385    pub app_decls: Option<Node<NonEmpty<Node<AppDecl>>>>,
386}
387
388impl Decl for ActionDecl {
389    fn names(&self) -> Vec<Node<SmolStr>> {
390        self.names.iter().cloned().collect()
391    }
392}
393
394#[cfg(test)]
395mod test {
396    use std::sync::Arc;
397
398    use super::*;
399
400    fn loc() -> Loc {
401        Loc::new((1, 1), Arc::from("foo"))
402    }
403
404    // Ensure the iterators over [`Path`]s return most significant names first
405    #[test]
406    fn path_iter() {
407        let p = Path::new(
408            "baz".parse().unwrap(),
409            ["foo".parse().unwrap(), "bar".parse().unwrap()],
410            loc(),
411        );
412
413        let expected: Vec<Id> = vec![
414            "foo".parse().unwrap(),
415            "bar".parse().unwrap(),
416            "baz".parse().unwrap(),
417        ];
418
419        let expected_borrowed = expected.iter().collect::<Vec<_>>();
420
421        let borrowed = p.iter().collect::<Vec<_>>();
422        assert_eq!(borrowed, expected_borrowed);
423        let moved = p.into_iter().map(|n| n.node).collect::<Vec<_>>();
424        assert_eq!(moved, expected);
425    }
426}