1use std::{
18 collections::{HashMap, HashSet},
19 fmt::Display,
20};
21
22use cedar_policy_core::parser::{
23 err::{expected_to_string, ExpectedTokenConfig},
24 unescape::UnescapeError,
25 Loc, Node,
26};
27use lalrpop_util as lalr;
28use lazy_static::lazy_static;
29use miette::{Diagnostic, LabeledSpan, SourceSpan};
30use nonempty::NonEmpty;
31use smol_str::SmolStr;
32use thiserror::Error;
33
34use super::ast::PR;
35
36#[derive(Debug, Clone, PartialEq, Eq, Error)]
37pub enum UserError {
38 #[error("An empty list was passed")]
39 EmptyList,
40 #[error("Invalid escape codes")]
41 StringEscape(NonEmpty<UnescapeError>),
42 #[error("`{0}` is a reserved identifier")]
43 ReservedIdentifierUsed(SmolStr),
44}
45
46pub(crate) type RawLocation = usize;
47pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
48pub(crate) type RawUserError = Node<UserError>;
49
50pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, RawUserError>;
51pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, RawUserError>;
52
53type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
54
55lazy_static! {
56 static ref SCHEMA_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
57 friendly_token_names: HashMap::from([
58 ("IN", "`in`"),
59 ("PRINCIPAL", "`principal`"),
60 ("ACTION", "`action`"),
61 ("RESOURCE", "`resource`"),
62 ("CONTEXT", "`context`"),
63 ("STRINGLIT", "string literal"),
64 ("ENTITY", "`entity`"),
65 ("NAMESPACE", "`namespace`"),
66 ("TYPE", "`type`"),
67 ("SET", "`Set`"),
68 ("IDENTIFIER", "identifier"),
69 ]),
70 impossible_tokens: HashSet::new(),
71 special_identifier_tokens: HashSet::from([
72 "NAMESPACE",
73 "ENTITY",
74 "IN",
75 "TYPE",
76 "APPLIESTO",
77 "PRINCIPAL",
78 "ACTION",
79 "RESOURCE",
80 "CONTEXT",
81 "ATTRIBUTES",
82 "LONG",
83 "STRING",
84 "BOOL",
85 ]),
86 identifier_sentinel: "IDENTIFIER",
87 first_set_identifier_tokens: HashSet::from(["SET"]),
88 first_set_sentinel: "\"{\"",
89 };
90}
91
92#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct ParseError {
95 err: OwnedRawParseError,
97}
98
99impl From<RawParseError<'_>> for ParseError {
100 fn from(err: RawParseError<'_>) -> Self {
101 Self {
102 err: err.map_token(|token| token.to_string()),
103 }
104 }
105}
106
107impl From<RawErrorRecovery<'_>> for ParseError {
108 fn from(recovery: RawErrorRecovery<'_>) -> Self {
109 recovery.error.into()
110 }
111}
112
113impl ParseError {
114 pub fn primary_source_span(&self) -> SourceSpan {
116 let Self { err } = self;
117 match err {
118 OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
119 OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
120 OwnedRawParseError::UnrecognizedToken {
121 token: (token_start, _, token_end),
122 ..
123 } => SourceSpan::from(*token_start..*token_end),
124 OwnedRawParseError::ExtraToken {
125 token: (token_start, _, token_end),
126 } => SourceSpan::from(*token_start..*token_end),
127 OwnedRawParseError::User { error } => error.loc.span,
128 }
129 }
130}
131
132impl Display for ParseError {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 let Self { err } = self;
135 match err {
136 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
137 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
138 OwnedRawParseError::UnrecognizedToken {
139 token: (_, token, _),
140 ..
141 } => write!(f, "unexpected token `{token}`"),
142 OwnedRawParseError::ExtraToken {
143 token: (_, token, _),
144 ..
145 } => write!(f, "extra token `{token}`"),
146 OwnedRawParseError::User {
147 error: Node { node, .. },
148 } => write!(f, "{node}"),
149 }
150 }
151}
152
153impl std::error::Error for ParseError {}
154
155impl Diagnostic for ParseError {
156 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
157 let primary_source_span = self.primary_source_span();
158 let Self { err } = self;
159 let labeled_span = match err {
160 OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
161 OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
162 expected_to_string(expected, &SCHEMA_TOKEN_CONFIG),
163 primary_source_span,
164 ),
165 OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
166 expected_to_string(expected, &SCHEMA_TOKEN_CONFIG),
167 primary_source_span,
168 ),
169 OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
170 OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
171 };
172 Some(Box::new(std::iter::once(labeled_span)))
173 }
174}
175
176#[derive(Clone, Debug, PartialEq, Eq)]
178pub struct ParseErrors(Box<NonEmpty<ParseError>>);
179
180impl ParseErrors {
181 pub fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
182 let mut nv = NonEmpty::singleton(first);
183 let mut v = rest.into_iter().collect::<Vec<_>>();
184 nv.append(&mut v);
185 Self(Box::new(nv))
186 }
187
188 pub fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
189 let v = i.into_iter().collect::<Vec<_>>();
190 Some(Self(Box::new(NonEmpty::from_vec(v)?)))
191 }
192
193 pub fn iter(&self) -> impl Iterator<Item = &ParseError> {
194 self.0.iter()
195 }
196}
197
198impl Display for ParseErrors {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 write!(f, "{}", self.0.first())
201 }
202}
203
204impl std::error::Error for ParseErrors {
205 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
206 std::error::Error::source(self.0.first())
207 }
208}
209
210impl Diagnostic for ParseErrors {
215 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
216 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
218 errs.next().map(move |first_err| match first_err.related() {
219 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
220 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
221 })
222 }
223
224 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
225 Diagnostic::code(self.0.first())
226 }
227
228 fn severity(&self) -> Option<miette::Severity> {
229 Diagnostic::severity(self.0.first())
230 }
231
232 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
233 Diagnostic::help(self.0.first())
234 }
235
236 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
237 Diagnostic::url(self.0.first())
238 }
239
240 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
241 Diagnostic::source_code(self.0.first())
242 }
243
244 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
245 Diagnostic::labels(self.0.first())
246 }
247
248 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
249 Diagnostic::diagnostic_source(self.0.first())
250 }
251}
252
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct ToJsonSchemaErrors(NonEmpty<ToJsonSchemaError>);
255
256impl ToJsonSchemaErrors {
257 pub fn new(errs: NonEmpty<ToJsonSchemaError>) -> Self {
258 Self(errs)
259 }
260
261 pub fn iter(&self) -> impl Iterator<Item = &ToJsonSchemaError> {
262 self.0.iter()
263 }
264}
265
266impl IntoIterator for ToJsonSchemaErrors {
267 type Item = ToJsonSchemaError;
268 type IntoIter = <NonEmpty<ToJsonSchemaError> as IntoIterator>::IntoIter;
269
270 fn into_iter(self) -> Self::IntoIter {
271 self.0.into_iter()
272 }
273}
274
275impl From<ToJsonSchemaError> for ToJsonSchemaErrors {
276 fn from(value: ToJsonSchemaError) -> Self {
277 Self(NonEmpty::singleton(value))
278 }
279}
280
281impl Display for ToJsonSchemaErrors {
282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283 write!(f, "{}", self.0.first()) }
285}
286
287impl std::error::Error for ToJsonSchemaErrors {
288 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
289 self.0.first().source()
290 }
291
292 #[allow(deprecated)]
293 fn description(&self) -> &str {
294 self.0.first().description()
295 }
296
297 #[allow(deprecated)]
298 fn cause(&self) -> Option<&dyn std::error::Error> {
299 self.0.first().cause()
300 }
301}
302
303impl Diagnostic for ToJsonSchemaErrors {
308 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
309 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
311 errs.next().map(move |first_err| match first_err.related() {
312 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
313 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
314 })
315 }
316
317 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
318 self.0.first().code()
319 }
320
321 fn severity(&self) -> Option<miette::Severity> {
322 self.0.first().severity()
323 }
324
325 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
326 self.0.first().help()
327 }
328
329 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
330 self.0.first().url()
331 }
332
333 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
334 self.0.first().source_code()
335 }
336
337 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
338 self.0.first().labels()
339 }
340
341 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
342 self.0.first().diagnostic_source()
343 }
344}
345
346#[derive(Clone, Debug, Error, PartialEq, Eq)]
348pub enum ToJsonSchemaError {
349 #[error("Duplicate keys: `{key}`")]
351 DuplicateKeys { key: SmolStr, loc1: Loc, loc2: Loc },
352 #[error("Duplicate declarations: `{decl}`")]
354 DuplicateDeclarations { decl: SmolStr, loc1: Loc, loc2: Loc },
355 #[error("Duplicate context declaration. Action may have at most one context declaration")]
356 DuplicateContext { loc1: Loc, loc2: Loc },
357 #[error("Duplicate {kind} decleration. Action may have at most once {kind} declaration")]
358 DuplicatePR { kind: PR, loc1: Loc, loc2: Loc },
359
360 #[error("Duplicate namespace IDs: `{namespace_id}`")]
362 DuplicateNameSpaces {
363 namespace_id: SmolStr,
364 loc1: Option<Loc>,
365 loc2: Option<Loc>,
366 },
367 #[error("Unknown type name: `{}`", .0.node)]
369 UnknownTypeName(Node<SmolStr>),
370 #[error("Use reserved namespace `__cedar`")]
371 UseReservedNamespace(Loc),
372}
373
374impl ToJsonSchemaError {
375 pub fn duplicate_keys(key: SmolStr, loc1: Loc, loc2: Loc) -> Self {
376 Self::DuplicateKeys { key, loc1, loc2 }
377 }
378 pub fn duplicate_decls(decl: SmolStr, loc1: Loc, loc2: Loc) -> Self {
379 Self::DuplicateDeclarations { decl, loc1, loc2 }
380 }
381 pub fn duplicate_namespace(namespace_id: SmolStr, loc1: Loc, loc2: Loc) -> Self {
382 Self::DuplicateNameSpaces {
383 namespace_id,
384 loc1: Some(loc1),
385 loc2: Some(loc2),
386 }
387 }
388}
389
390impl Diagnostic for ToJsonSchemaError {
391 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
392 match self {
393 ToJsonSchemaError::DuplicateDeclarations { loc1, loc2, .. }
394 | ToJsonSchemaError::DuplicateContext { loc1, loc2 }
395 | ToJsonSchemaError::DuplicatePR { loc1, loc2, .. }
396 | ToJsonSchemaError::DuplicateKeys { loc1, loc2, .. } => Some(Box::new(
397 vec![
398 LabeledSpan::underline(loc1.span),
399 LabeledSpan::underline(loc2.span),
400 ]
401 .into_iter(),
402 )),
403 ToJsonSchemaError::DuplicateNameSpaces { loc1, loc2, .. } => {
404 Some(Box::new([loc1, loc2].into_iter().filter_map(|loc| {
405 Some(LabeledSpan::underline(loc.as_ref()?.span))
406 })))
407 }
408 ToJsonSchemaError::UnknownTypeName(node) => Some(Box::new(std::iter::once(
409 LabeledSpan::underline(node.loc.span),
410 ))),
411 ToJsonSchemaError::UseReservedNamespace(loc) => {
412 Some(Box::new(std::iter::once(LabeledSpan::underline(loc.span))))
413 }
414 }
415 }
416}
417
418#[derive(Debug, Clone, Error, Diagnostic)]
419#[diagnostic(severity(warning))]
420pub enum SchemaWarning {
421 #[error("The name `{name}` shadows a builtin Cedar name. You'll have to refer to the builtin as `__cedar::{name}`.")]
422 ShadowsBuiltin { name: SmolStr, loc: Loc },
423 #[error("The common type name {name} shadows an entity name")]
424 ShadowsEntity {
425 name: SmolStr,
426 entity_loc: Loc,
427 common_loc: Loc,
428 },
429 #[error("The namespace {name} uses a name that will be reserved in the future. All namespaces beginning with `__` will be reserved in a future version.")]
430 UsesBuiltinNamespace { name: SmolStr, loc: Loc },
431}