cedar_policy_validator/cedar_schema/
ast.rs1use 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#[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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
40pub struct Annotated<T> {
41 pub data: T,
43 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
83pub struct Path(Node<PathInternal>);
84impl Path {
85 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 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 pub fn iter(&self) -> impl Iterator<Item = &Id> {
110 self.0.node.iter()
111 }
112
113 pub fn loc(&self) -> &Loc {
115 &self.0.loc
116 }
117
118 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 pub fn split_last(self) -> (Vec<Id>, Id) {
129 (self.0.node.namespace, self.0.node.basename)
130 }
131
132 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 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#[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#[derive(Debug, Clone)]
213pub struct Namespace {
214 pub name: Option<Path>,
216 pub decls: Vec<Annotated<Node<Declaration>>>,
218}
219
220impl Namespace {
221 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#[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#[derive(Debug, Clone)]
254pub struct EntityDecl {
255 pub names: NonEmpty<Node<Id>>,
258 pub member_of_types: Vec<Path>,
260 pub attrs: Node<Vec<Node<Annotated<AttrDecl>>>>,
262 pub tags: Option<Node<Type>>,
264}
265
266#[derive(Debug, Clone)]
268pub enum Type {
269 Set(Box<Node<Type>>),
271 Ident(Path),
273 Record(Vec<Node<Annotated<AttrDecl>>>),
275}
276
277#[derive(Debug, Clone)]
279pub enum PrimitiveType {
280 Long,
282 String,
284 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#[derive(Debug, Clone)]
301pub struct AttrDecl {
302 pub name: Node<SmolStr>,
304 pub required: bool,
306 pub ty: Node<Type>,
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum PR {
313 Principal,
315 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#[derive(Debug, Clone)]
330pub struct PRAppDecl {
331 pub kind: Node<PR>,
333 pub entity_tys: NonEmpty<Path>,
335}
336
337#[derive(Debug, Clone)]
339pub enum AppDecl {
340 PR(PRAppDecl),
342 Context(Either<Path, Node<Vec<Node<Annotated<AttrDecl>>>>>),
344}
345
346#[derive(Debug, Clone)]
348pub struct ActionDecl {
349 pub names: NonEmpty<Node<SmolStr>>,
352 pub parents: Option<NonEmpty<Node<QualName>>>,
354 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 #[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}