cedar_policy_validator/cedar_schema/
err.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::{
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    // Extract a primary source span locating the error.
58    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            // use the first occurrence as the primary source span
64            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/// For errors during parsing
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub struct ParseError {
119    /// Error generated by lalrpop
120    pub(crate) err: OwnedRawParseError,
121    /// Source code
122    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    /// Extract a primary source span locating the error.
140    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/// Multiple parse errors.
218#[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    /// Borrowed Iterator over reported errors
235    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
261// Except for `.related()`, everything else is forwarded to the first error, if it is present.
262// This ensures that users who only use `Display`, `.code()`, `.labels()` etc, still get rich
263// information for the first error, even if they don't realize there are multiple errors here.
264// See cedar-policy/cedar#326.
265impl Diagnostic for ParseErrors {
266    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
267        // the .related() on the first error, and then the 2nd through Nth errors (but not their own .related())
268        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/// Non-empty collection of [`ToJsonSchemaError`]
305// WARNING: This type is publicly exported from [`cedar-policy`]
306#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct ToJsonSchemaErrors(NonEmpty<ToJsonSchemaError>);
308
309impl ToJsonSchemaErrors {
310    /// Constructor. Guaranteed to have at least one error by construction.
311    pub fn new(errs: NonEmpty<ToJsonSchemaError>) -> Self {
312        Self(errs)
313    }
314
315    /// (Borrowed) iterator
316    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()) // intentionally showing only the first error; see #326 for discussion on a similar error type
339    }
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
358// Except for `.related()`, everything else is forwarded to the first error, if it is present.
359// This ensures that users who only use `Display`, `.code()`, `.labels()` etc, still get rich
360// information for the first error, even if they don't realize there are multiple errors here.
361// See #326 for discussion on a similar error type.
362impl Diagnostic for ToJsonSchemaErrors {
363    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
364        // the .related() on the first error, and then the 2nd through Nth errors (but not their own .related())
365        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// WARNING: This error type is publicly exported in `cedar-policy`, so it is part of the public interface
402/// For errors during schema format conversion
403#[derive(Clone, Debug, Error, PartialEq, Eq, Diagnostic)]
404pub enum ToJsonSchemaError {
405    /// Error raised when there are duplicate declarations
406    #[error(transparent)]
407    #[diagnostic(transparent)]
408    DuplicateDeclarations(#[from] DuplicateDeclarations),
409    /// Error raised when an action has multiple context declarations
410    #[error(transparent)]
411    #[diagnostic(transparent)]
412    DuplicateContext(#[from] DuplicateContext),
413    /// Error raised when a `principal` or `resource` is declared multiple times
414    #[error(transparent)]
415    #[diagnostic(transparent)]
416    DuplicatePrincipalOrResource(#[from] DuplicatePrincipalOrResource),
417    /// Error raised when an action does not define either `principal` or `resource`
418    #[error(transparent)]
419    #[diagnostic(transparent)]
420    NoPrincipalOrResource(#[from] NoPrincipalOrResource),
421    /// Error raised when there are duplicate namespace IDs
422    #[error(transparent)]
423    #[diagnostic(transparent)]
424    DuplicateNamespaces(#[from] DuplicateNamespace),
425    /// Error raised when a type name is unknown
426    #[error(transparent)]
427    #[diagnostic(transparent)]
428    UnknownTypeName(#[from] UnknownTypeName),
429    /// Invalid type name
430    #[error(transparent)]
431    #[diagnostic(transparent)]
432    ReservedName(#[from] ReservedName),
433    /// Use reserved schema keywords
434    #[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    // `Loc`s are optional here as the implicit empty namespace has no location
646    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
654/// Error subtypes for [`SchemaWarning`]
655pub 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    /// Warning when a builtin Cedar name is shadowed
662    //
663    // CAUTION: this type is publicly exported in `cedar-policy`.
664    // Don't make fields `pub`, don't make breaking changes, and use caution
665    // when adding public methods.
666    #[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    /// Warning when an entity name is shadowed by a common type name
682    //
683    // CAUTION: this type is publicly exported in `cedar-policy`.
684    // Don't make fields `pub`, don't make breaking changes, and use caution
685    // when adding public methods.
686    #[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            // just have to pick one; we assume `entity_loc` and `common_loc`
705            // have the same source code.
706            // if that isn't true we'll have a confusing underline.
707            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/// Warning when constructing a schema
717//
718// CAUTION: this type is publicly exported in `cedar-policy`.
719// Don't make fields `pub`, don't make breaking changes, and use caution
720// when adding public methods.
721#[derive(Eq, PartialEq, Debug, Clone, Error, Diagnostic)]
722#[non_exhaustive]
723pub enum SchemaWarning {
724    /// Warning when a declaration shadows a builtin type
725    #[error(transparent)]
726    #[diagnostic(transparent)]
727    ShadowsBuiltin(#[from] schema_warnings::ShadowsBuiltinWarning),
728    /// Warning when a declaration shadows an entity type
729    #[error(transparent)]
730    #[diagnostic(transparent)]
731    ShadowsEntity(#[from] schema_warnings::ShadowsEntityWarning),
732}