1use std::collections::{BTreeSet, HashMap, HashSet};
18use std::fmt::{self, Display, Write};
19use std::iter;
20use std::ops::{Deref, DerefMut};
21
22use either::Either;
23use lalrpop_util as lalr;
24use lazy_static::lazy_static;
25use miette::{Diagnostic, LabeledSpan, SourceSpan};
26use smol_str::SmolStr;
27use thiserror::Error;
28
29use crate::ast::{self, ExprConstructionError, InputInteger, PolicyID, RestrictedExprError, Var};
30use crate::parser::fmt::join_with_conjunction;
31use crate::parser::loc::Loc;
32use crate::parser::node::Node;
33use crate::parser::unescape::UnescapeError;
34
35use super::cst;
36
37pub(crate) type RawLocation = usize;
38pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
39pub(crate) type RawUserError = Node<String>;
40
41pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, RawUserError>;
42pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, RawUserError>;
43
44type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
45
46#[derive(Clone, Debug, Diagnostic, Error, PartialEq, Eq)]
48pub enum ParseError {
49 #[error(transparent)]
51 #[diagnostic(transparent)]
52 ToCST(#[from] ToCSTError),
53 #[error(transparent)]
55 #[diagnostic(transparent)]
56 ToAST(#[from] ToASTError),
57 #[error(transparent)]
59 #[diagnostic(transparent)]
60 RestrictedExpr(#[from] RestrictedExprError),
61 #[error(transparent)]
63 #[diagnostic(transparent)]
64 ParseLiteral(#[from] ParseLiteralError),
65}
66
67impl ParseError {
68 #[deprecated(
70 since = "3.3.0",
71 note = "Use the location information provided by miette::Diagnostic via `.labels()` and `.source_code()` instead"
72 )]
73 pub fn primary_source_span(&self) -> Option<SourceSpan> {
74 match self {
75 ParseError::ToCST(to_cst_err) => Some(to_cst_err.primary_source_span()),
76 ParseError::ToAST(to_ast_err) => Some(to_ast_err.source_loc().span),
77 ParseError::RestrictedExpr(restricted_expr_err) => match restricted_expr_err {
78 RestrictedExprError::InvalidRestrictedExpression { expr, .. } => {
79 expr.source_loc().map(|loc| loc.span)
80 }
81 },
82 ParseError::ParseLiteral(parse_lit_err) => parse_lit_err
83 .labels()
84 .and_then(|mut it| it.next().map(|lspan| *lspan.inner())),
85 }
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq)]
91pub enum ParseLiteralError {
92 #[error("`{0}` is not a literal")]
95 ParseLiteral(String),
96}
97
98#[derive(Debug, Error, Clone, PartialEq, Eq)]
100#[error("{kind}")]
101pub struct ToASTError {
102 kind: ToASTErrorKind,
103 loc: Loc,
104}
105
106impl Diagnostic for ToASTError {
109 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
110 Some(Box::new(iter::once(LabeledSpan::underline(self.loc.span))))
111 }
112
113 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
114 self.kind.code()
115 }
116
117 fn severity(&self) -> Option<miette::Severity> {
118 self.kind.severity()
119 }
120
121 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
122 self.kind.help()
123 }
124
125 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
126 self.kind.url()
127 }
128
129 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
130 Some(&self.loc.src as &dyn miette::SourceCode)
131 }
132
133 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
134 self.kind.diagnostic_source()
135 }
136}
137
138impl ToASTError {
139 pub fn new(kind: ToASTErrorKind, loc: Loc) -> Self {
141 Self { kind, loc }
142 }
143
144 pub fn kind(&self) -> &ToASTErrorKind {
146 &self.kind
147 }
148
149 pub(crate) fn source_loc(&self) -> &Loc {
150 &self.loc
151 }
152}
153
154#[derive(Debug, Diagnostic, Error, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum ToASTErrorKind {
158 #[error("a template with id `{0}` already exists in the policy set")]
160 DuplicateTemplateId(PolicyID),
161 #[error("a policy with id `{0}` already exists in the policy set")]
163 DuplicatePolicyId(PolicyID),
164 #[error(transparent)]
166 #[diagnostic(transparent)]
167 ExpectedStaticPolicy(#[from] parse_errors::ExpectedStaticPolicy),
168 #[error(transparent)]
170 #[diagnostic(transparent)]
171 ExpectedTemplate(#[from] parse_errors::ExpectedTemplate),
172 #[error("duplicate annotation: @{0}")]
175 DuplicateAnnotation(ast::AnyId),
176 #[error("found template slot {} in a `{clausetype}` clause", slot.id)]
178 #[diagnostic(help("slots are currently unsupported in `{clausetype}` clauses"))]
179 SlotsInConditionClause {
180 slot: ast::Slot,
182 clausetype: &'static str,
184 },
185 #[error("this policy is missing the `{0}` variable in the scope")]
187 MissingScopeConstraint(Var),
188 #[error("this policy has an extra constraint in the scope: `{0}`")]
190 #[diagnostic(help(
191 "a policy must have exactly `principal`, `action`, and `resource` constraints"
192 ))]
193 ExtraScopeConstraints(cst::VariableDef),
194 #[error("this identifier is reserved and cannot be used: `{0}`")]
196 ReservedIdentifier(cst::Ident),
197 #[error("not a valid identifier: `{0}`")]
201 InvalidIdentifier(String),
202 #[error("'=' is not a valid operator in Cedar")]
205 #[diagnostic(help("try using '==' instead"))]
206 InvalidSingleEq,
207 #[error("not a valid policy effect: `{0}`")]
209 #[diagnostic(help("effect must be either `permit` or `forbid`"))]
210 InvalidEffect(cst::Ident),
211 #[error("not a valid policy condition: `{0}`")]
213 #[diagnostic(help("condition must be either `when` or `unless`"))]
214 InvalidCondition(cst::Ident),
215 #[error("expected a variable that is valid in the policy scope; found: `{0}`")]
217 #[diagnostic(help(
218 "policy scopes must contain a `principal`, `action`, and `resource` element in that order"
219 ))]
220 InvalidScopeConstraintVariable(cst::Ident),
221 #[error("not a valid method name: `{0}`")]
223 InvalidMethodName(String),
224 #[error("found the variable `{got}` where the variable `{expected}` must be used")]
227 #[diagnostic(help(
228 "policy scopes must contain a `principal`, `action`, and `resource` element in that order"
229 ))]
230 IncorrectVariable {
231 expected: Var,
233 got: Var,
235 },
236 #[error("not a valid policy scope constraint: {0}")]
238 #[diagnostic(help(
239 "policy scope constraints must be either `==`, `in`, `is`, or `_ is _ in _`"
240 ))]
241 InvalidConstraintOperator(cst::RelOp),
242 #[error("expected an entity uid with the type `Action` but got `{0}`")]
244 #[diagnostic(help("action entities must have type `Action`, optionally in a namespace"))]
245 InvalidActionType(crate::ast::EntityUID),
246 #[error("{}condition clause cannot be empty", match .0 { Some(ident) => format!("`{}` ", ident), None => "".to_string() })]
248 EmptyClause(Option<cst::Ident>),
249 #[error("internal invariant violated. No parse errors were reported but annotation information was missing")]
251 AnnotationInvariantViolation,
252 #[error("internal invariant violated. Membership chain did not resolve to an expression")]
254 MembershipInvariantViolation,
255 #[error("invalid string literal: `{0}`")]
257 InvalidString(String),
258 #[error("arbitrary variables are not supported; the valid Cedar variables are `principal`, `action`, `resource`, and `context`")]
260 #[diagnostic(help("did you mean to enclose `{0}` in quotes to make a string?"))]
261 ArbitraryVariable(SmolStr),
262 #[error("not a valid attribute name: `{0}`")]
264 #[diagnostic(help("attribute names can either be identifiers or string literals"))]
265 InvalidAttribute(SmolStr),
266 #[error("record literal has invalid attributes")]
268 InvalidAttributesInRecordLiteral,
269 #[error("`{0}` cannot be used as an attribute as it contains a namespace")]
271 PathAsAttribute(String),
272 #[error("`{0}` is a method, not a function")]
274 #[diagnostic(help("use a method-style call: `e.{0}(..)`"))]
275 FunctionCallOnMethod(crate::ast::Id),
276 #[error("`{0}` is a function, not a method")]
278 #[diagnostic(help("use a function-style call: `{0}(..)`"))]
279 MethodCallOnFunction(crate::ast::Id),
280 #[error("right hand side of a `like` expression must be a pattern literal, but got `{0}`")]
282 InvalidPattern(String),
283 #[error("right hand side of an `is` expression must be an entity type name, but got `{0}`")]
285 #[diagnostic(help("try using `==` to test for equality"))]
286 IsInvalidName(String),
287 #[error("expected {expected}, found {got}")]
289 WrongNode {
290 expected: &'static str,
292 got: String,
294 #[help]
296 suggestion: Option<String>,
297 },
298 #[error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")]
301 AmbiguousOperators,
302 #[error("division is not supported")]
304 UnsupportedDivision,
305 #[error("remainder/modulo is not supported")]
307 UnsupportedModulo,
308 #[error(transparent)]
310 #[diagnostic(transparent)]
311 ExprConstructionError(#[from] ExprConstructionError),
312 #[error("integer literal `{0}` is too large")]
314 #[diagnostic(help("maximum allowed integer literal is `{}`", InputInteger::MAX))]
315 IntegerLiteralTooLarge(u64),
316 #[error("too many occurrences of `{0}`")]
318 #[diagnostic(help("cannot chain more the 4 applications of a unary operator"))]
319 UnaryOpLimit(crate::ast::UnaryOp),
320 #[error("`{0}(...)` is not a valid function call")]
323 #[diagnostic(help("variables cannot be called as functions"))]
324 VariableCall(crate::ast::Var),
325 #[error("attempted to call `{0}.{1}`, but `{0}` does not have any methods")]
327 NoMethods(crate::ast::Name, ast::Id),
328 #[error("`{0}` is not a function")]
330 NotAFunction(crate::ast::Name),
331 #[error("entity literals are not supported")]
333 UnsupportedEntityLiterals,
334 #[error("function calls must be of the form: `<name>(arg1, arg2, ...)`")]
337 ExpressionCall,
338 #[error("incorrect member access `{0}.{1}`, `{0}` has no fields or methods")]
340 InvalidAccess(crate::ast::Name, SmolStr),
341 #[error("incorrect indexing expression `{0}[{1}]`, `{0}` has no fields")]
343 InvalidIndex(crate::ast::Name, SmolStr),
344 #[error("the contents of an index expression must be a string literal")]
346 NonStringIndex,
347 #[error("type constraints using `:` are not supported")]
351 #[diagnostic(help("try using `is` instead"))]
352 TypeConstraints,
353 #[error("a path is not valid in this context")]
355 InvalidPath,
356 #[error("`{kind}` needs to be normalized (e.g., whitespace removed): `{src}`")]
358 #[diagnostic(help("the normalized form is `{normalized_src}`"))]
359 NonNormalizedString {
360 kind: &'static str,
362 src: String,
364 normalized_src: String,
366 },
367 #[error("data should not be empty")]
369 MissingNodeData,
370 #[error("the right hand side of a `has` expression must be a field name or string literal")]
372 HasNonLiteralRHS,
373 #[error("`{0}` is not a valid expression")]
375 InvalidExpression(cst::Name),
376 #[error("call to `{name}` requires exactly {expected} argument{}, but got {got} argument{}", if .expected == &1 { "" } else { "s" }, if .got == &1 { "" } else { "s" })]
378 WrongArity {
379 name: &'static str,
381 expected: usize,
383 got: usize,
385 },
386 #[error(transparent)]
388 #[diagnostic(transparent)]
389 Unescape(#[from] UnescapeError),
390 #[error(transparent)]
392 #[diagnostic(transparent)]
393 RefCreation(#[from] RefCreationError),
394 #[error(transparent)]
396 #[diagnostic(transparent)]
397 InvalidIs(#[from] InvalidIsError),
398 #[error("`{0}` is not a valid template slot")]
400 #[diagnostic(help("a template slot may only be `?principal` or `?resource`"))]
401 InvalidSlot(SmolStr),
402}
403
404impl ToASTErrorKind {
405 pub fn wrong_node(
407 expected: &'static str,
408 got: impl Into<String>,
409 suggestion: Option<impl Into<String>>,
410 ) -> Self {
411 Self::WrongNode {
412 expected,
413 got: got.into(),
414 suggestion: suggestion.map(Into::into),
415 }
416 }
417
418 pub fn wrong_arity(name: &'static str, expected: usize, got: usize) -> Self {
420 Self::WrongArity {
421 name,
422 expected,
423 got,
424 }
425 }
426
427 pub fn expected_static_policy(slot: ast::Slot) -> Self {
429 parse_errors::ExpectedStaticPolicy { slot }.into()
430 }
431
432 pub fn expected_template() -> Self {
434 parse_errors::ExpectedTemplate::new().into()
435 }
436}
437
438pub mod parse_errors {
440 use super::*;
441
442 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
444 #[error("expected a static policy, got a template containing the slot {}", slot.id)]
445 #[diagnostic(help("try removing the template slot(s) from this policy"))]
446 pub struct ExpectedStaticPolicy {
447 pub(crate) slot: ast::Slot,
449 }
450
451 impl From<ast::UnexpectedSlotError> for ExpectedStaticPolicy {
452 fn from(err: ast::UnexpectedSlotError) -> Self {
453 match err {
454 ast::UnexpectedSlotError::FoundSlot(slot) => Self { slot },
455 }
456 }
457 }
458
459 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
461 #[error("expected a template, got a static policy")]
462 #[diagnostic(help("a template should include slot(s) `?principal` or `?resource`"))]
463 pub struct ExpectedTemplate {
464 _dummy: (),
469 }
470
471 impl ExpectedTemplate {
472 pub(crate) fn new() -> Self {
473 Self { _dummy: () }
474 }
475 }
476}
477
478#[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
480pub enum RefCreationError {
481 #[error("expected {}, got: {got}", match .expected { Either::Left(r) => r.to_string(), Either::Right((r1, r2)) => format!("{r1} or {r2}") })]
483 RefCreation {
484 expected: Either<Ref, (Ref, Ref)>,
487 got: Ref,
489 },
490}
491
492impl RefCreationError {
493 pub fn one_expected(expected: Ref, got: Ref) -> Self {
495 Self::RefCreation {
496 expected: Either::Left(expected),
497 got,
498 }
499 }
500
501 pub fn two_expected(r1: Ref, r2: Ref, got: Ref) -> Self {
503 let expected = Either::Right((r1, r2));
504 Self::RefCreation { expected, got }
505 }
506}
507
508#[derive(Debug, Clone, PartialEq, Eq)]
510pub enum Ref {
511 Single,
513 Set,
515 Template,
517}
518
519impl std::fmt::Display for Ref {
520 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521 match self {
522 Ref::Single => write!(f, "single entity uid"),
523 Ref::Template => write!(f, "template slot"),
524 Ref::Set => write!(f, "set of entity uids"),
525 }
526 }
527}
528
529#[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
532pub enum InvalidIsError {
533 #[error("`is` cannot appear in the action scope")]
535 #[diagnostic(help("try moving `action is ..` into a `when` condition"))]
536 ActionScope,
537 #[error("`is` cannot appear in the scope at the same time as `{0}`")]
539 #[diagnostic(help("try moving `is` into a `when` condition"))]
540 WrongOp(cst::RelOp),
541}
542
543#[derive(Clone, Debug, Error, PartialEq, Eq)]
545pub struct ToCSTError {
546 err: OwnedRawParseError,
547}
548
549impl ToCSTError {
550 pub fn primary_source_span(&self) -> SourceSpan {
552 match &self.err {
553 OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
554 OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
555 OwnedRawParseError::UnrecognizedToken {
556 token: (token_start, _, token_end),
557 ..
558 } => SourceSpan::from(*token_start..*token_end),
559 OwnedRawParseError::ExtraToken {
560 token: (token_start, _, token_end),
561 } => SourceSpan::from(*token_start..*token_end),
562 OwnedRawParseError::User { error } => error.loc.span,
563 }
564 }
565
566 pub(crate) fn from_raw_parse_err(err: RawParseError<'_>) -> Self {
567 Self {
568 err: err.map_token(|token| token.to_string()),
569 }
570 }
571
572 pub(crate) fn from_raw_err_recovery(recovery: RawErrorRecovery<'_>) -> Self {
573 Self::from_raw_parse_err(recovery.error)
574 }
575}
576
577impl Display for ToCSTError {
578 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579 match &self.err {
580 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
581 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
582 OwnedRawParseError::UnrecognizedToken {
583 token: (_, token, _),
584 ..
585 } => write!(f, "unexpected token `{token}`"),
586 OwnedRawParseError::ExtraToken {
587 token: (_, token, _),
588 ..
589 } => write!(f, "extra token `{token}`"),
590 OwnedRawParseError::User { error } => write!(f, "{error}"),
591 }
592 }
593}
594
595impl Diagnostic for ToCSTError {
596 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
597 let primary_source_span = self.primary_source_span();
598 let labeled_span = match &self.err {
599 OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
600 OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
601 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
602 primary_source_span,
603 ),
604 OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
605 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
606 primary_source_span,
607 ),
608 OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
609 OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
610 };
611 Some(Box::new(iter::once(labeled_span)))
612 }
613}
614
615#[derive(Debug)]
618pub struct ExpectedTokenConfig {
619 pub friendly_token_names: HashMap<&'static str, &'static str>,
623
624 pub impossible_tokens: HashSet<&'static str>,
629
630 pub special_identifier_tokens: HashSet<&'static str>,
637
638 pub identifier_sentinel: &'static str,
641
642 pub first_set_identifier_tokens: HashSet<&'static str>,
647
648 pub first_set_sentinel: &'static str,
652}
653
654lazy_static! {
655 static ref POLICY_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
656 friendly_token_names: HashMap::from([
657 ("TRUE", "`true`"),
658 ("FALSE", "`false`"),
659 ("IF", "`if`"),
660 ("PERMIT", "`permit`"),
661 ("FORBID", "`forbid`"),
662 ("WHEN", "`when`"),
663 ("UNLESS", "`unless`"),
664 ("IN", "`in`"),
665 ("HAS", "`has`"),
666 ("LIKE", "`like`"),
667 ("IS", "`is`"),
668 ("THEN", "`then`"),
669 ("ELSE", "`else`"),
670 ("PRINCIPAL", "`principal`"),
671 ("ACTION", "`action`"),
672 ("RESOURCE", "`resource`"),
673 ("CONTEXT", "`context`"),
674 ("PRINCIPAL_SLOT", "`?principal`"),
675 ("RESOURCE_SLOT", "`?resource`"),
676 ("IDENTIFIER", "identifier"),
677 ("NUMBER", "number"),
678 ("STRINGLIT", "string literal"),
679 ]),
680 impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
681 special_identifier_tokens: HashSet::from([
682 "PERMIT",
683 "FORBID",
684 "WHEN",
685 "UNLESS",
686 "IN",
687 "HAS",
688 "LIKE",
689 "IS",
690 "THEN",
691 "ELSE",
692 "PRINCIPAL",
693 "ACTION",
694 "RESOURCE",
695 "CONTEXT",
696 ]),
697 identifier_sentinel: "IDENTIFIER",
698 first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
699 first_set_sentinel: "\"!\"",
700 };
701}
702
703pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
705 let mut expected = expected
706 .iter()
707 .filter(|e| !config.impossible_tokens.contains(e.as_str()))
708 .map(|e| e.as_str())
709 .collect::<BTreeSet<_>>();
710 if expected.contains(config.identifier_sentinel) {
711 for token in config.special_identifier_tokens.iter() {
712 expected.remove(*token);
713 }
714 if !expected.contains(config.first_set_sentinel) {
715 for token in config.first_set_identifier_tokens.iter() {
716 expected.remove(*token);
717 }
718 }
719 }
720 if expected.is_empty() {
721 return None;
722 }
723
724 let mut expected_string = "expected ".to_owned();
725 #[allow(clippy::expect_used)]
727 join_with_conjunction(
728 &mut expected_string,
729 "or",
730 expected,
731 |f, token| match config.friendly_token_names.get(token) {
732 Some(friendly_token_name) => write!(f, "{}", friendly_token_name),
733 None => write!(f, "{}", token.replace('"', "`")),
734 },
735 )
736 .expect("failed to format expected tokens");
737 Some(expected_string)
738}
739
740#[derive(Clone, Debug, Default, PartialEq, Eq)]
742pub struct ParseErrors(pub Vec<ParseError>);
743
744impl ParseErrors {
745 const DESCRIPTION_IF_EMPTY: &'static str = "unknown parse error";
746
747 pub fn new() -> Self {
749 ParseErrors(Vec::new())
750 }
751
752 pub fn with_capacity(capacity: usize) -> Self {
754 ParseErrors(Vec::with_capacity(capacity))
755 }
756
757 pub(super) fn push(&mut self, err: impl Into<ParseError>) {
759 self.0.push(err.into());
760 }
761
762 #[deprecated(
764 since = "3.3.0",
765 note = "Use `.iter().map(ToString::to_string)` instead; note that converting to string discards some information from the error which is available through `miette::Diagnostic`"
766 )]
767 pub fn errors_as_strings(&self) -> Vec<String> {
768 self.0.iter().map(ToString::to_string).collect()
769 }
770}
771
772impl Display for ParseErrors {
773 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774 match self.first() {
775 Some(first_err) => write!(f, "{first_err}"), None => write!(f, "{}", Self::DESCRIPTION_IF_EMPTY),
777 }
778 }
779}
780
781impl std::error::Error for ParseErrors {
782 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
783 self.first().and_then(std::error::Error::source)
784 }
785
786 #[allow(deprecated)]
787 fn description(&self) -> &str {
788 match self.first() {
789 Some(first_err) => first_err.description(),
790 None => Self::DESCRIPTION_IF_EMPTY,
791 }
792 }
793
794 #[allow(deprecated)]
795 fn cause(&self) -> Option<&dyn std::error::Error> {
796 self.first().and_then(std::error::Error::cause)
797 }
798}
799
800impl Diagnostic for ParseErrors {
805 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
806 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
808 errs.next().map(move |first_err| match first_err.related() {
809 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
810 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
811 })
812 }
813
814 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
815 self.first().and_then(Diagnostic::code)
816 }
817
818 fn severity(&self) -> Option<miette::Severity> {
819 self.first().and_then(Diagnostic::severity)
820 }
821
822 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
823 self.first().and_then(Diagnostic::help)
824 }
825
826 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
827 self.first().and_then(Diagnostic::url)
828 }
829
830 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
831 self.first().and_then(Diagnostic::source_code)
832 }
833
834 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
835 self.first().and_then(Diagnostic::labels)
836 }
837
838 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
839 self.first().and_then(Diagnostic::diagnostic_source)
840 }
841}
842
843impl AsRef<Vec<ParseError>> for ParseErrors {
844 fn as_ref(&self) -> &Vec<ParseError> {
845 &self.0
846 }
847}
848
849impl AsMut<Vec<ParseError>> for ParseErrors {
850 fn as_mut(&mut self) -> &mut Vec<ParseError> {
851 &mut self.0
852 }
853}
854
855impl AsRef<[ParseError]> for ParseErrors {
856 fn as_ref(&self) -> &[ParseError] {
857 self.0.as_ref()
858 }
859}
860
861impl AsMut<[ParseError]> for ParseErrors {
862 fn as_mut(&mut self) -> &mut [ParseError] {
863 self.0.as_mut()
864 }
865}
866
867impl Deref for ParseErrors {
868 type Target = Vec<ParseError>;
869
870 fn deref(&self) -> &Self::Target {
871 &self.0
872 }
873}
874
875impl DerefMut for ParseErrors {
876 fn deref_mut(&mut self) -> &mut Self::Target {
877 &mut self.0
878 }
879}
880
881impl<T: Into<ParseError>> From<T> for ParseErrors {
882 fn from(err: T) -> Self {
883 vec![err.into()].into()
884 }
885}
886
887impl From<Vec<ParseError>> for ParseErrors {
888 fn from(errs: Vec<ParseError>) -> Self {
889 ParseErrors(errs)
890 }
891}
892
893impl<T: Into<ParseError>> FromIterator<T> for ParseErrors {
894 fn from_iter<I: IntoIterator<Item = T>>(errs: I) -> Self {
895 ParseErrors(errs.into_iter().map(Into::into).collect())
896 }
897}
898
899impl<T: Into<ParseError>> Extend<T> for ParseErrors {
900 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
901 self.0.extend(iter.into_iter().map(Into::into))
902 }
903}
904
905impl IntoIterator for ParseErrors {
906 type Item = ParseError;
907 type IntoIter = std::vec::IntoIter<Self::Item>;
908
909 fn into_iter(self) -> Self::IntoIter {
910 self.0.into_iter()
911 }
912}
913
914impl<'a> IntoIterator for &'a ParseErrors {
915 type Item = &'a ParseError;
916 type IntoIter = std::slice::Iter<'a, ParseError>;
917
918 fn into_iter(self) -> Self::IntoIter {
919 self.0.iter()
920 }
921}
922
923impl<'a> IntoIterator for &'a mut ParseErrors {
924 type Item = &'a mut ParseError;
925 type IntoIter = std::slice::IterMut<'a, ParseError>;
926
927 fn into_iter(self) -> Self::IntoIter {
928 self.0.iter_mut()
929 }
930}