1use std::{
18 collections::{HashMap, HashSet},
19 fmt::Display,
20 iter::{Chain, Once},
21 sync::Arc,
22 vec,
23};
24
25use cedar_policy_core::{
26 ast::AnyId,
27 impl_diagnostic_from_source_loc_field, impl_diagnostic_from_two_source_loc_fields,
28 impl_diagnostic_from_two_source_loc_opt_fields,
29 parser::{
30 err::{expected_to_string, ExpectedTokenConfig},
31 unescape::UnescapeError,
32 Loc, Node,
33 },
34};
35use lalrpop_util as lalr;
36use lazy_static::lazy_static;
37use miette::{Diagnostic, LabeledSpan, SourceSpan};
38use nonempty::NonEmpty;
39use smol_str::{SmolStr, ToSmolStr};
40use thiserror::Error;
41
42use super::ast::PR;
43
44#[derive(Debug, Clone, PartialEq, Eq, Error)]
45pub enum UserError {
46 #[error("An empty list was passed")]
47 EmptyList(Node<()>),
48 #[error("Invalid escape codes")]
49 StringEscape(Node<NonEmpty<UnescapeError>>),
50 #[error("`{0}` is a reserved identifier")]
51 ReservedIdentifierUsed(Node<SmolStr>),
52 #[error("duplicate annotations: `{}`", .0)]
53 DuplicateAnnotations(AnyId, Node<()>, Node<()>),
54}
55
56impl UserError {
57 pub(crate) fn primary_source_span(&self) -> SourceSpan {
59 match self {
60 Self::EmptyList(n) => n.loc.span,
61 Self::StringEscape(n) => n.loc.span,
62 Self::ReservedIdentifierUsed(n) => n.loc.span,
63 Self::DuplicateAnnotations(_, n, _) => n.loc.span,
65 }
66 }
67}
68
69pub(crate) type RawLocation = usize;
70pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
71
72pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, UserError>;
73pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, UserError>;
74
75type OwnedRawParseError = lalr::ParseError<RawLocation, String, UserError>;
76
77lazy_static! {
78 static ref SCHEMA_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
79 friendly_token_names: HashMap::from([
80 ("IN", "`in`"),
81 ("PRINCIPAL", "`principal`"),
82 ("ACTION", "`action`"),
83 ("RESOURCE", "`resource`"),
84 ("CONTEXT", "`context`"),
85 ("STRINGLIT", "string literal"),
86 ("ENTITY", "`entity`"),
87 ("NAMESPACE", "`namespace`"),
88 ("TYPE", "`type`"),
89 ("SET", "`Set`"),
90 ("IDENTIFIER", "identifier"),
91 ("TAGS", "`tags`"),
92 ]),
93 impossible_tokens: HashSet::new(),
94 special_identifier_tokens: HashSet::from([
95 "NAMESPACE",
96 "ENTITY",
97 "IN",
98 "TYPE",
99 "APPLIESTO",
100 "PRINCIPAL",
101 "ACTION",
102 "RESOURCE",
103 "CONTEXT",
104 "ATTRIBUTES",
105 "TAGS",
106 "LONG",
107 "STRING",
108 "BOOL",
109 ]),
110 identifier_sentinel: "IDENTIFIER",
111 first_set_identifier_tokens: HashSet::from(["SET"]),
112 first_set_sentinel: "\"{\"",
113 };
114}
115
116#[derive(Clone, Debug, PartialEq, Eq)]
118pub struct ParseError {
119 pub(crate) err: OwnedRawParseError,
121 src: Arc<str>,
123}
124
125impl ParseError {
126 pub(crate) fn from_raw_parse_error(err: RawParseError<'_>, src: Arc<str>) -> Self {
127 Self {
128 err: err.map_token(|token| token.to_string()),
129 src,
130 }
131 }
132
133 pub(crate) fn from_raw_error_recovery(recovery: RawErrorRecovery<'_>, src: Arc<str>) -> Self {
134 Self::from_raw_parse_error(recovery.error, src)
135 }
136}
137
138impl ParseError {
139 pub fn primary_source_span(&self) -> SourceSpan {
141 match &self.err {
142 OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
143 OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
144 OwnedRawParseError::UnrecognizedToken {
145 token: (token_start, _, token_end),
146 ..
147 } => SourceSpan::from(*token_start..*token_end),
148 OwnedRawParseError::ExtraToken {
149 token: (token_start, _, token_end),
150 } => SourceSpan::from(*token_start..*token_end),
151 OwnedRawParseError::User { error } => error.primary_source_span(),
152 }
153 }
154}
155
156impl Display for ParseError {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 let Self { err, .. } = self;
159 match err {
160 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
161 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
162 OwnedRawParseError::UnrecognizedToken {
163 token: (_, token, _),
164 ..
165 } => write!(f, "unexpected token `{token}`"),
166 OwnedRawParseError::ExtraToken {
167 token: (_, token, _),
168 ..
169 } => write!(f, "extra token `{token}`"),
170 OwnedRawParseError::User { error } => write!(f, "{error}"),
171 }
172 }
173}
174
175impl std::error::Error for ParseError {}
176
177impl Diagnostic for ParseError {
178 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
179 Some(&self.src as &dyn miette::SourceCode)
180 }
181
182 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
183 let primary_source_span = self.primary_source_span();
184 match &self.err {
185 OwnedRawParseError::InvalidToken { .. } => Some(Box::new(std::iter::once(
186 LabeledSpan::underline(primary_source_span),
187 ))),
188 OwnedRawParseError::UnrecognizedEof { expected, .. } => {
189 Some(Box::new(std::iter::once(LabeledSpan::new_with_span(
190 expected_to_string(expected, &SCHEMA_TOKEN_CONFIG),
191 primary_source_span,
192 ))))
193 }
194 OwnedRawParseError::UnrecognizedToken { expected, .. } => {
195 Some(Box::new(std::iter::once(LabeledSpan::new_with_span(
196 expected_to_string(expected, &SCHEMA_TOKEN_CONFIG),
197 primary_source_span,
198 ))))
199 }
200 OwnedRawParseError::ExtraToken { .. } => Some(Box::new(std::iter::once(
201 LabeledSpan::underline(primary_source_span),
202 ))),
203 OwnedRawParseError::User {
204 error: UserError::DuplicateAnnotations(_, n1, n2),
205 } => Some(Box::new(
206 std::iter::once(n1.loc.span)
207 .chain(std::iter::once(n2.loc.span))
208 .map(LabeledSpan::underline),
209 )),
210 OwnedRawParseError::User { .. } => Some(Box::new(std::iter::once(
211 LabeledSpan::underline(primary_source_span),
212 ))),
213 }
214 }
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
219pub struct ParseErrors(pub(crate) Box<NonEmpty<ParseError>>);
220
221impl ParseErrors {
222 pub fn new(first: ParseError, tail: impl IntoIterator<Item = ParseError>) -> Self {
223 Self(Box::new(NonEmpty {
224 head: first,
225 tail: tail.into_iter().collect(),
226 }))
227 }
228
229 pub fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
230 let v = i.into_iter().collect::<Vec<_>>();
231 Some(Self(Box::new(NonEmpty::from_vec(v)?)))
232 }
233
234 pub fn iter(&self) -> impl Iterator<Item = &ParseError> {
236 self.0.iter()
237 }
238}
239
240impl Display for ParseErrors {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 write!(f, "{}", self.0.first())
243 }
244}
245
246impl IntoIterator for ParseErrors {
247 type Item = ParseError;
248 type IntoIter = Chain<Once<ParseError>, vec::IntoIter<ParseError>>;
249
250 fn into_iter(self) -> Self::IntoIter {
251 self.0.into_iter()
252 }
253}
254
255impl std::error::Error for ParseErrors {
256 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
257 std::error::Error::source(self.0.first())
258 }
259}
260
261impl Diagnostic for ParseErrors {
266 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
267 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
269 errs.next().map(move |first_err| match first_err.related() {
270 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
271 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
272 })
273 }
274
275 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
276 Diagnostic::code(self.0.first())
277 }
278
279 fn severity(&self) -> Option<miette::Severity> {
280 Diagnostic::severity(self.0.first())
281 }
282
283 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
284 Diagnostic::help(self.0.first())
285 }
286
287 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
288 Diagnostic::url(self.0.first())
289 }
290
291 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
292 Diagnostic::source_code(self.0.first())
293 }
294
295 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
296 Diagnostic::labels(self.0.first())
297 }
298
299 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
300 Diagnostic::diagnostic_source(self.0.first())
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct ToJsonSchemaErrors(NonEmpty<ToJsonSchemaError>);
308
309impl ToJsonSchemaErrors {
310 pub fn new(errs: NonEmpty<ToJsonSchemaError>) -> Self {
312 Self(errs)
313 }
314
315 pub fn iter(&self) -> impl Iterator<Item = &ToJsonSchemaError> {
317 self.0.iter()
318 }
319}
320
321impl IntoIterator for ToJsonSchemaErrors {
322 type Item = ToJsonSchemaError;
323 type IntoIter = <NonEmpty<ToJsonSchemaError> as IntoIterator>::IntoIter;
324
325 fn into_iter(self) -> Self::IntoIter {
326 self.0.into_iter()
327 }
328}
329
330impl From<ToJsonSchemaError> for ToJsonSchemaErrors {
331 fn from(value: ToJsonSchemaError) -> Self {
332 Self(NonEmpty::singleton(value))
333 }
334}
335
336impl Display for ToJsonSchemaErrors {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 write!(f, "{}", self.0.first()) }
340}
341
342impl std::error::Error for ToJsonSchemaErrors {
343 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
344 self.0.first().source()
345 }
346
347 #[allow(deprecated)]
348 fn description(&self) -> &str {
349 self.0.first().description()
350 }
351
352 #[allow(deprecated)]
353 fn cause(&self) -> Option<&dyn std::error::Error> {
354 self.0.first().cause()
355 }
356}
357
358impl Diagnostic for ToJsonSchemaErrors {
363 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
364 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
366 errs.next().map(move |first_err| match first_err.related() {
367 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
368 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
369 })
370 }
371
372 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
373 self.0.first().code()
374 }
375
376 fn severity(&self) -> Option<miette::Severity> {
377 self.0.first().severity()
378 }
379
380 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
381 self.0.first().help()
382 }
383
384 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
385 self.0.first().url()
386 }
387
388 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
389 self.0.first().source_code()
390 }
391
392 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
393 self.0.first().labels()
394 }
395
396 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
397 self.0.first().diagnostic_source()
398 }
399}
400
401#[derive(Clone, Debug, Error, PartialEq, Eq, Diagnostic)]
404pub enum ToJsonSchemaError {
405 #[error(transparent)]
407 #[diagnostic(transparent)]
408 DuplicateDeclarations(#[from] DuplicateDeclarations),
409 #[error(transparent)]
411 #[diagnostic(transparent)]
412 DuplicateContext(#[from] DuplicateContext),
413 #[error(transparent)]
415 #[diagnostic(transparent)]
416 DuplicatePrincipalOrResource(#[from] DuplicatePrincipalOrResource),
417 #[error(transparent)]
419 #[diagnostic(transparent)]
420 NoPrincipalOrResource(#[from] NoPrincipalOrResource),
421 #[error(transparent)]
423 #[diagnostic(transparent)]
424 DuplicateNamespaces(#[from] DuplicateNamespace),
425 #[error(transparent)]
427 #[diagnostic(transparent)]
428 UnknownTypeName(#[from] UnknownTypeName),
429 #[error(transparent)]
431 #[diagnostic(transparent)]
432 ReservedName(#[from] ReservedName),
433 #[error(transparent)]
435 #[diagnostic(transparent)]
436 ReservedSchemaKeyword(#[from] ReservedSchemaKeyword),
437}
438
439impl ToJsonSchemaError {
440 pub(crate) fn duplicate_context(name: &impl ToSmolStr, loc1: Loc, loc2: Loc) -> Self {
441 Self::DuplicateContext(DuplicateContext {
442 name: name.to_smolstr(),
443 loc1,
444 loc2,
445 })
446 }
447
448 pub(crate) fn duplicate_decls(decl: &impl ToSmolStr, loc1: Loc, loc2: Loc) -> Self {
449 Self::DuplicateDeclarations(DuplicateDeclarations {
450 decl: decl.to_smolstr(),
451 loc1,
452 loc2,
453 })
454 }
455
456 pub(crate) fn duplicate_namespace(
457 namespace_id: &impl ToSmolStr,
458 loc1: Option<Loc>,
459 loc2: Option<Loc>,
460 ) -> Self {
461 Self::DuplicateNamespaces(DuplicateNamespace {
462 namespace_id: namespace_id.to_smolstr(),
463 loc1,
464 loc2,
465 })
466 }
467
468 pub(crate) fn duplicate_principal(name: &impl ToSmolStr, loc1: Loc, loc2: Loc) -> Self {
469 Self::DuplicatePrincipalOrResource(DuplicatePrincipalOrResource {
470 name: name.to_smolstr(),
471 kind: PR::Principal,
472 loc1,
473 loc2,
474 })
475 }
476
477 pub(crate) fn duplicate_resource(name: &impl ToSmolStr, loc1: Loc, loc2: Loc) -> Self {
478 Self::DuplicatePrincipalOrResource(DuplicatePrincipalOrResource {
479 name: name.to_smolstr(),
480 kind: PR::Resource,
481 loc1,
482 loc2,
483 })
484 }
485
486 pub(crate) fn no_principal(name: &impl ToSmolStr, loc: Loc) -> Self {
487 Self::NoPrincipalOrResource(NoPrincipalOrResource {
488 kind: PR::Principal,
489 name: name.to_smolstr(),
490 loc,
491 })
492 }
493
494 pub(crate) fn no_resource(name: &impl ToSmolStr, loc: Loc) -> Self {
495 Self::NoPrincipalOrResource(NoPrincipalOrResource {
496 kind: PR::Resource,
497 name: name.to_smolstr(),
498 loc,
499 })
500 }
501
502 pub(crate) fn reserved_name(name: &impl ToSmolStr, loc: Loc) -> Self {
503 Self::ReservedName(ReservedName {
504 name: name.to_smolstr(),
505 loc,
506 })
507 }
508
509 pub(crate) fn reserved_keyword(keyword: &impl ToSmolStr, loc: Loc) -> Self {
510 Self::ReservedSchemaKeyword(ReservedSchemaKeyword {
511 keyword: keyword.to_smolstr(),
512 loc,
513 })
514 }
515}
516
517#[derive(Debug, Clone, PartialEq, Eq, Error)]
518#[error("this uses a reserved schema keyword: `{keyword}`")]
519pub struct ReservedSchemaKeyword {
520 keyword: SmolStr,
521 loc: Loc,
522}
523
524impl Diagnostic for ReservedSchemaKeyword {
525 impl_diagnostic_from_source_loc_field!(loc);
526
527 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
528 Some(Box::new("Keywords such as `entity`, `extension`, `set` and `record` cannot be used as common type names"))
529 }
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Error)]
533#[error("use of the reserved `__cedar` namespace")]
534pub struct ReservedName {
535 name: SmolStr,
536 loc: Loc,
537}
538
539impl Diagnostic for ReservedName {
540 impl_diagnostic_from_source_loc_field!(loc);
541
542 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
543 Some(Box::new(
544 "Names containing `__cedar` (for example: `__cedar::A`, `A::__cedar`, or `A::__cedar::B`) are reserved",
545 ))
546 }
547}
548
549#[derive(Debug, Clone, PartialEq, Eq, Error)]
550#[error("unknown type name: `{name}`")]
551pub struct UnknownTypeName {
552 name: SmolStr,
553 loc: Loc,
554}
555
556impl Diagnostic for UnknownTypeName {
557 impl_diagnostic_from_source_loc_field!(loc);
558
559 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
560 let msg = format!(
561 "Did you mean to define `{}` as an entity type or common type?",
562 self.name
563 );
564 Some(Box::new(msg))
565 }
566}
567
568#[derive(Debug, Clone, PartialEq, Eq, Error)]
569#[error("duplicate `{kind}` declaration in action `{name}`")]
570pub struct DuplicatePrincipalOrResource {
571 name: SmolStr,
572 kind: PR,
573 loc1: Loc,
574 loc2: Loc,
575}
576
577impl DuplicatePrincipalOrResource {
578 #[cfg(test)]
579 pub(crate) fn kind(&self) -> PR {
580 self.kind
581 }
582}
583
584impl Diagnostic for DuplicatePrincipalOrResource {
585 impl_diagnostic_from_two_source_loc_fields!(loc1, loc2);
586
587 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
588 let msg = format!("Actions may only have a single {} declaration. If you need it to apply to multiple types, try creating a parent type and using the `in` keyword", self.kind);
589 Some(Box::new(msg))
590 }
591}
592
593#[derive(Debug, Clone, PartialEq, Eq, Error)]
594#[error("duplicate context declaration in action `{name}`")]
595pub struct DuplicateContext {
596 name: SmolStr,
597 loc1: Loc,
598 loc2: Loc,
599}
600
601impl Diagnostic for DuplicateContext {
602 impl_diagnostic_from_two_source_loc_fields!(loc1, loc2);
603
604 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
605 Some(Box::new(
606 "Try either deleting one of the declarations, or merging into a single declaration",
607 ))
608 }
609}
610#[derive(Debug, Clone, PartialEq, Eq, Error)]
611#[error("`{decl}` is declared twice")]
612pub struct DuplicateDeclarations {
613 decl: SmolStr,
614 loc1: Loc,
615 loc2: Loc,
616}
617
618impl Diagnostic for DuplicateDeclarations {
619 impl_diagnostic_from_two_source_loc_fields!(loc1, loc2);
620}
621
622#[derive(Debug, Clone, PartialEq, Eq, Error)]
623#[error("missing `{kind}` declaration for `{name}`")]
624pub struct NoPrincipalOrResource {
625 kind: PR,
626 name: SmolStr,
627 loc: Loc,
628}
629
630pub const NO_PR_HELP_MSG: &str =
631 "Every action must define both `principal` and `resource` targets.";
632
633impl Diagnostic for NoPrincipalOrResource {
634 impl_diagnostic_from_source_loc_field!(loc);
635
636 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
637 Some(Box::new(NO_PR_HELP_MSG))
638 }
639}
640
641#[derive(Debug, Clone, Error, PartialEq, Eq)]
642#[error("duplicate namespace id: `{namespace_id}`")]
643pub struct DuplicateNamespace {
644 namespace_id: SmolStr,
645 loc1: Option<Loc>,
647 loc2: Option<Loc>,
648}
649
650impl Diagnostic for DuplicateNamespace {
651 impl_diagnostic_from_two_source_loc_opt_fields!(loc1, loc2);
652}
653
654pub mod schema_warnings {
656 use cedar_policy_core::{impl_diagnostic_from_source_loc_field, parser::Loc};
657 use miette::Diagnostic;
658 use smol_str::SmolStr;
659 use thiserror::Error;
660
661 #[derive(Eq, PartialEq, Debug, Clone, Error)]
667 #[error("The name `{name}` shadows a builtin Cedar name. You'll have to refer to the builtin as `__cedar::{name}`.")]
668 pub struct ShadowsBuiltinWarning {
669 pub(crate) name: SmolStr,
670 pub(crate) loc: Loc,
671 }
672
673 impl Diagnostic for ShadowsBuiltinWarning {
674 impl_diagnostic_from_source_loc_field!(loc);
675
676 fn severity(&self) -> Option<miette::Severity> {
677 Some(miette::Severity::Warning)
678 }
679 }
680
681 #[derive(Eq, PartialEq, Debug, Clone, Error)]
687 #[error("The common type name {name} shadows an entity name")]
688 pub struct ShadowsEntityWarning {
689 pub(crate) name: SmolStr,
690 pub(crate) entity_loc: Loc,
691 pub(crate) common_loc: Loc,
692 }
693
694 impl Diagnostic for ShadowsEntityWarning {
695 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
696 Some(Box::new(
697 std::iter::once(&self.entity_loc)
698 .chain(std::iter::once(&self.common_loc))
699 .map(miette::LabeledSpan::underline),
700 ))
701 }
702
703 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
704 Some(&self.entity_loc.src as _)
708 }
709
710 fn severity(&self) -> Option<miette::Severity> {
711 Some(miette::Severity::Warning)
712 }
713 }
714}
715
716#[derive(Eq, PartialEq, Debug, Clone, Error, Diagnostic)]
722#[non_exhaustive]
723pub enum SchemaWarning {
724 #[error(transparent)]
726 #[diagnostic(transparent)]
727 ShadowsBuiltin(#[from] schema_warnings::ShadowsBuiltinWarning),
728 #[error(transparent)]
730 #[diagnostic(transparent)]
731 ShadowsEntity(#[from] schema_warnings::ShadowsEntityWarning),
732}