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 nonempty::NonEmpty;
27use smol_str::SmolStr;
28use thiserror::Error;
29
30use crate::ast::{self, ReservedNameError};
31use crate::parser::fmt::join_with_conjunction;
32use crate::parser::loc::Loc;
33use crate::parser::node::Node;
34use crate::parser::unescape::UnescapeError;
35
36use super::cst;
37
38pub(crate) type RawLocation = usize;
39pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
40pub(crate) type RawUserError = Node<String>;
41
42pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, RawUserError>;
43pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, RawUserError>;
44
45type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
46
47#[derive(Clone, Debug, Diagnostic, Error, PartialEq, Eq)]
49pub enum ParseError {
50 #[error(transparent)]
52 #[diagnostic(transparent)]
53 ToCST(#[from] ToCSTError),
54 #[error(transparent)]
56 #[diagnostic(transparent)]
57 ToAST(#[from] ToASTError),
58}
59
60#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq)]
62pub enum LiteralParseError {
63 #[error(transparent)]
65 #[diagnostic(transparent)]
66 Parse(#[from] ParseErrors),
67 #[error("invalid literal: {0}")]
69 InvalidLiteral(ast::Expr),
70}
71
72#[derive(Debug, Error, Clone, PartialEq, Eq)]
74#[error("{kind}")]
75pub struct ToASTError {
76 kind: ToASTErrorKind,
77 loc: Loc,
78}
79
80impl Diagnostic for ToASTError {
83 impl_diagnostic_from_source_loc_field!(loc);
84
85 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
86 self.kind.code()
87 }
88
89 fn severity(&self) -> Option<miette::Severity> {
90 self.kind.severity()
91 }
92
93 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
94 self.kind.help()
95 }
96
97 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
98 self.kind.url()
99 }
100
101 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
102 self.kind.diagnostic_source()
103 }
104}
105
106impl ToASTError {
107 pub fn new(kind: ToASTErrorKind, loc: Loc) -> Self {
109 Self { kind, loc }
110 }
111
112 pub fn kind(&self) -> &ToASTErrorKind {
114 &self.kind
115 }
116
117 pub(crate) fn source_loc(&self) -> &Loc {
118 &self.loc
119 }
120}
121
122const POLICY_SCOPE_HELP: &str =
123 "policy scopes must contain a `principal`, `action`, and `resource` element in that order";
124
125#[derive(Debug, Diagnostic, Error, Clone, PartialEq, Eq)]
127#[non_exhaustive]
128pub enum ToASTErrorKind {
129 #[error("a template with id `{0}` already exists in the policy set")]
131 DuplicateTemplateId(ast::PolicyID),
132 #[error("a policy with id `{0}` already exists in the policy set")]
134 DuplicatePolicyId(ast::PolicyID),
135 #[error(transparent)]
137 #[diagnostic(transparent)]
138 ExpectedStaticPolicy(#[from] parse_errors::ExpectedStaticPolicy),
139 #[error(transparent)]
141 #[diagnostic(transparent)]
142 ExpectedTemplate(#[from] parse_errors::ExpectedTemplate),
143 #[error("duplicate annotation: @{0}")]
146 DuplicateAnnotation(ast::AnyId),
147 #[error(transparent)]
150 #[diagnostic(transparent)]
151 SlotsInConditionClause(#[from] parse_errors::SlotsInConditionClause),
152 #[error("this policy is missing the `{0}` variable in the scope")]
155 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
156 MissingScopeVariable(ast::Var),
157 #[error("this policy has an extra element in the scope: {0}")]
159 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
160 ExtraScopeElement(cst::VariableDef),
161 #[error("this identifier is reserved and cannot be used: {0}")]
163 ReservedIdentifier(cst::Ident),
164 #[error("invalid identifier: {0}")]
168 InvalidIdentifier(String),
169 #[error("'=' is not a valid operator in Cedar")]
172 #[diagnostic(help("try using '==' instead"))]
173 InvalidSingleEq,
174 #[error("invalid policy effect: {0}")]
176 #[diagnostic(help("effect must be either `permit` or `forbid`"))]
177 InvalidEffect(cst::Ident),
178 #[error("invalid policy condition: {0}")]
180 #[diagnostic(help("condition must be either `when` or `unless`"))]
181 InvalidCondition(cst::Ident),
182 #[error("found an invalid variable in the policy scope: {0}")]
185 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
186 InvalidScopeVariable(cst::Ident),
187 #[error("found the variable `{got}` where the variable `{expected}` must be used")]
190 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
191 IncorrectVariable {
192 expected: ast::Var,
194 got: ast::Var,
196 },
197 #[error("invalid operator in the policy scope: {0}")]
199 #[diagnostic(help("policy scope clauses can only use `==`, `in`, `is`, or `_ is _ in _`"))]
200 InvalidScopeOperator(cst::RelOp),
201 #[error("invalid operator in the action scope: {0}")]
204 #[diagnostic(help("action scope clauses can only use `==` or `in`"))]
205 InvalidActionScopeOperator(cst::RelOp),
206 #[error("`is` cannot appear in the action scope")]
208 #[diagnostic(help("try moving `action is ..` into a `when` condition"))]
209 IsInActionScope,
210 #[error("`is` cannot be used together with `==`")]
212 #[diagnostic(help("try using `_ is _ in _`"))]
213 IsWithEq,
214 #[error(transparent)]
216 #[diagnostic(transparent)]
217 InvalidActionType(#[from] parse_errors::InvalidActionType),
218 #[error("{}condition clause cannot be empty", match .0 { Some(ident) => format!("`{}` ", ident), None => "".to_string() })]
220 EmptyClause(Option<cst::Ident>),
221 #[error("internal invariant violated. Membership chain did not resolve to an expression")]
224 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
225 MembershipInvariantViolation,
226 #[error("invalid string literal: {0}")]
228 InvalidString(String),
229 #[error("invalid variable: {0}")]
232 #[diagnostic(help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `{0}` in quotes to make a string?"))]
233 ArbitraryVariable(SmolStr),
234 #[error("invalid attribute name: {0}")]
236 #[diagnostic(help("attribute names can either be identifiers or string literals"))]
237 InvalidAttribute(SmolStr),
238 #[error("`{0}` cannot be used as an attribute as it contains a namespace")]
240 PathAsAttribute(String),
241 #[error("`{0}` is a method, not a function")]
243 #[diagnostic(help("use a method-style call `e.{0}(..)`"))]
244 FunctionCallOnMethod(ast::UnreservedId),
245 #[error("`{0}` is a function, not a method")]
247 #[diagnostic(help("use a function-style call `{0}(..)`"))]
248 MethodCallOnFunction(ast::UnreservedId),
249 #[error("right hand side of a `like` expression must be a pattern literal, but got `{0}`")]
251 InvalidPattern(String),
252 #[error("right hand side of an `is` expression must be an entity type name, but got `{0}`")]
254 #[diagnostic(help("try using `==` to test for equality"))]
255 InvalidIsType(String),
256 #[error("expected {expected}, found {got}")]
258 WrongNode {
259 expected: &'static str,
261 got: String,
263 #[help]
265 suggestion: Option<String>,
266 },
267 #[error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")]
270 AmbiguousOperators,
271 #[error("division is not supported")]
273 UnsupportedDivision,
274 #[error("remainder/modulo is not supported")]
276 UnsupportedModulo,
277 #[error(transparent)]
279 #[diagnostic(transparent)]
280 ExpressionConstructionError(#[from] ast::ExpressionConstructionError),
281 #[error("integer literal `{0}` is too large")]
283 #[diagnostic(help("maximum allowed integer literal is `{}`", ast::InputInteger::MAX))]
284 IntegerLiteralTooLarge(u64),
285 #[error("too many occurrences of `{0}`")]
287 #[diagnostic(help("cannot chain more the 4 applications of a unary operator"))]
288 UnaryOpLimit(ast::UnaryOp),
289 #[error("`{0}(...)` is not a valid function call")]
292 #[diagnostic(help("variables cannot be called as functions"))]
293 VariableCall(ast::Var),
294 #[error("attempted to call `{0}.{1}(...)`, but `{0}` does not have any methods")]
296 NoMethods(ast::Name, ast::UnreservedId),
297 #[error("`{0}` is not a valid method")]
299 UnknownMethod(ast::UnreservedId),
300 #[error("`{0}` is not a valid function")]
302 UnknownFunction(ast::Name),
303 #[error("invalid entity literal: {0}")]
305 #[diagnostic(help("entity literals should have a form like `Namespace::User::\"alice\"`"))]
306 InvalidEntityLiteral(String),
307 #[error("function calls must be of the form `<name>(arg1, arg2, ...)`")]
310 ExpressionCall,
311 #[error("invalid member access `{0}.{1}`, `{0}` has no fields or methods")]
313 InvalidAccess(ast::Name, SmolStr),
314 #[error("invalid indexing expression `{0}[\"{}\"]`, `{0}` has no fields", .1.escape_debug())]
316 InvalidIndex(ast::Name, SmolStr),
317 #[error("the contents of an index expression must be a string literal")]
319 NonStringIndex,
320 #[error("type constraints using `:` are not supported")]
324 #[diagnostic(help("try using `is` instead"))]
325 TypeConstraints,
326 #[error("`{kind}` needs to be normalized (e.g., whitespace removed): {src}")]
328 #[diagnostic(help("the normalized form is `{normalized_src}`"))]
329 NonNormalizedString {
330 kind: &'static str,
332 src: String,
334 normalized_src: String,
336 },
337 #[error("internal invariant violated. Parsed data node should not be empty")]
342 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
343 EmptyNodeInvariantViolation,
344 #[error("call to `{name}` requires exactly {expected} argument{}, but got {got} argument{}", if .expected == &1 { "" } else { "s" }, if .got == &1 { "" } else { "s" })]
346 WrongArity {
347 name: &'static str,
349 expected: usize,
351 got: usize,
353 },
354 #[error(transparent)]
356 #[diagnostic(transparent)]
357 Unescape(#[from] UnescapeError),
358 #[error(transparent)]
360 #[diagnostic(transparent)]
361 WrongEntityArgument(#[from] parse_errors::WrongEntityArgument),
362 #[error("`{0}` is not a valid template slot")]
364 #[diagnostic(help("a template slot may only be `?principal` or `?resource`"))]
365 InvalidSlot(SmolStr),
366 #[error(transparent)]
368 #[diagnostic(transparent)]
369 ReservedNamespace(#[from] ReservedNameError),
370 #[error("when `is` and `in` are used together, `is` must come first")]
372 #[diagnostic(help("try `_ is _ in _`"))]
373 InvertedIsIn,
374}
375
376impl ToASTErrorKind {
377 pub fn wrong_node(
379 expected: &'static str,
380 got: impl Into<String>,
381 suggestion: Option<impl Into<String>>,
382 ) -> Self {
383 Self::WrongNode {
384 expected,
385 got: got.into(),
386 suggestion: suggestion.map(Into::into),
387 }
388 }
389
390 pub fn wrong_arity(name: &'static str, expected: usize, got: usize) -> Self {
392 Self::WrongArity {
393 name,
394 expected,
395 got,
396 }
397 }
398
399 pub fn slots_in_condition_clause(slot: ast::Slot, clause_type: &'static str) -> Self {
401 parse_errors::SlotsInConditionClause { slot, clause_type }.into()
402 }
403
404 pub fn expected_static_policy(slot: ast::Slot) -> Self {
406 parse_errors::ExpectedStaticPolicy { slot }.into()
407 }
408
409 pub fn expected_template() -> Self {
411 parse_errors::ExpectedTemplate::new().into()
412 }
413
414 pub fn wrong_entity_argument_one_expected(
417 expected: parse_errors::Ref,
418 got: parse_errors::Ref,
419 ) -> Self {
420 parse_errors::WrongEntityArgument {
421 expected: Either::Left(expected),
422 got,
423 }
424 .into()
425 }
426
427 pub fn wrong_entity_argument_two_expected(
430 r1: parse_errors::Ref,
431 r2: parse_errors::Ref,
432 got: parse_errors::Ref,
433 ) -> Self {
434 let expected = Either::Right((r1, r2));
435 parse_errors::WrongEntityArgument { expected, got }.into()
436 }
437}
438
439pub mod parse_errors {
441
442 use std::sync::Arc;
443
444 use super::*;
445
446 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
448 #[error("expected a static policy, got a template containing the slot {}", slot.id)]
449 #[diagnostic(help("try removing the template slot(s) from this policy"))]
450 pub struct ExpectedStaticPolicy {
451 pub(crate) slot: ast::Slot,
453 }
454
455 impl From<ast::UnexpectedSlotError> for ExpectedStaticPolicy {
456 fn from(err: ast::UnexpectedSlotError) -> Self {
457 match err {
458 ast::UnexpectedSlotError::FoundSlot(slot) => Self { slot },
459 }
460 }
461 }
462
463 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
465 #[error("expected a template, got a static policy")]
466 #[diagnostic(help("a template should include slot(s) `?principal` or `?resource`"))]
467 pub struct ExpectedTemplate {
468 _dummy: (),
473 }
474
475 impl ExpectedTemplate {
476 pub(crate) fn new() -> Self {
477 Self { _dummy: () }
478 }
479 }
480
481 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
483 #[error("found template slot {} in a `{clause_type}` clause", slot.id)]
484 #[diagnostic(help("slots are currently unsupported in `{clause_type}` clauses"))]
485 pub struct SlotsInConditionClause {
486 pub(crate) slot: ast::Slot,
488 pub(crate) clause_type: &'static str,
490 }
491
492 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
494 #[diagnostic(help("action entities must have type `Action`, optionally in a namespace"))]
495 pub struct InvalidActionType {
496 pub(crate) euids: NonEmpty<Arc<ast::EntityUID>>,
497 }
498
499 impl std::fmt::Display for InvalidActionType {
500 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
501 let subject = if self.euids.len() > 1 {
502 "entity uids"
503 } else {
504 "an entity uid"
505 };
506 write!(f, "expected {subject} with type `Action` but got ")?;
507 join_with_conjunction(f, "and", self.euids.iter(), |f, e| write!(f, "`{e}`"))
508 }
509 }
510
511 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
513 #[error("expected {}, found {got}", match .expected { Either::Left(r) => r.to_string(), Either::Right((r1, r2)) => format!("{r1} or {r2}") })]
514 pub struct WrongEntityArgument {
515 pub(crate) expected: Either<Ref, (Ref, Ref)>,
518 pub(crate) got: Ref,
520 }
521
522 #[derive(Debug, Clone, PartialEq, Eq)]
524 pub enum Ref {
525 Single,
527 Set,
529 Template,
531 }
532
533 impl std::fmt::Display for Ref {
534 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
535 match self {
536 Ref::Single => write!(f, "single entity uid"),
537 Ref::Template => write!(f, "template slot"),
538 Ref::Set => write!(f, "set of entity uids"),
539 }
540 }
541 }
542}
543
544#[derive(Clone, Debug, Error, PartialEq, Eq)]
546pub struct ToCSTError {
547 err: OwnedRawParseError,
548}
549
550impl ToCSTError {
551 pub fn primary_source_span(&self) -> SourceSpan {
553 match &self.err {
554 OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
555 OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
556 OwnedRawParseError::UnrecognizedToken {
557 token: (token_start, _, token_end),
558 ..
559 } => SourceSpan::from(*token_start..*token_end),
560 OwnedRawParseError::ExtraToken {
561 token: (token_start, _, token_end),
562 } => SourceSpan::from(*token_start..*token_end),
563 OwnedRawParseError::User { error } => error.loc.span,
564 }
565 }
566
567 pub(crate) fn from_raw_parse_err(err: RawParseError<'_>) -> Self {
568 Self {
569 err: err.map_token(|token| token.to_string()),
570 }
571 }
572
573 pub(crate) fn from_raw_err_recovery(recovery: RawErrorRecovery<'_>) -> Self {
574 Self::from_raw_parse_err(recovery.error)
575 }
576}
577
578impl Display for ToCSTError {
579 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580 match &self.err {
581 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
582 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
583 OwnedRawParseError::UnrecognizedToken {
584 token: (_, token, _),
585 ..
586 } => write!(f, "unexpected token `{token}`"),
587 OwnedRawParseError::ExtraToken {
588 token: (_, token, _),
589 ..
590 } => write!(f, "extra token `{token}`"),
591 OwnedRawParseError::User { error } => write!(f, "{error}"),
592 }
593 }
594}
595
596impl Diagnostic for ToCSTError {
597 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
598 let primary_source_span = self.primary_source_span();
599 let labeled_span = match &self.err {
600 OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
601 OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
602 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
603 primary_source_span,
604 ),
605 OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
606 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
607 primary_source_span,
608 ),
609 OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
610 OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
611 };
612 Some(Box::new(iter::once(labeled_span)))
613 }
614}
615
616#[derive(Debug)]
619pub struct ExpectedTokenConfig {
620 pub friendly_token_names: HashMap<&'static str, &'static str>,
624
625 pub impossible_tokens: HashSet<&'static str>,
630
631 pub special_identifier_tokens: HashSet<&'static str>,
638
639 pub identifier_sentinel: &'static str,
642
643 pub first_set_identifier_tokens: HashSet<&'static str>,
648
649 pub first_set_sentinel: &'static str,
653}
654
655lazy_static! {
656 static ref POLICY_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
657 friendly_token_names: HashMap::from([
658 ("TRUE", "`true`"),
659 ("FALSE", "`false`"),
660 ("IF", "`if`"),
661 ("PERMIT", "`permit`"),
662 ("FORBID", "`forbid`"),
663 ("WHEN", "`when`"),
664 ("UNLESS", "`unless`"),
665 ("IN", "`in`"),
666 ("HAS", "`has`"),
667 ("LIKE", "`like`"),
668 ("IS", "`is`"),
669 ("THEN", "`then`"),
670 ("ELSE", "`else`"),
671 ("PRINCIPAL", "`principal`"),
672 ("ACTION", "`action`"),
673 ("RESOURCE", "`resource`"),
674 ("CONTEXT", "`context`"),
675 ("PRINCIPAL_SLOT", "`?principal`"),
676 ("RESOURCE_SLOT", "`?resource`"),
677 ("IDENTIFIER", "identifier"),
678 ("NUMBER", "number"),
679 ("STRINGLIT", "string literal"),
680 ]),
681 impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
682 special_identifier_tokens: HashSet::from([
683 "PERMIT",
684 "FORBID",
685 "WHEN",
686 "UNLESS",
687 "IN",
688 "HAS",
689 "LIKE",
690 "IS",
691 "THEN",
692 "ELSE",
693 "PRINCIPAL",
694 "ACTION",
695 "RESOURCE",
696 "CONTEXT",
697 ]),
698 identifier_sentinel: "IDENTIFIER",
699 first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
700 first_set_sentinel: "\"!\"",
701 };
702}
703
704pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
706 let mut expected = expected
707 .iter()
708 .filter(|e| !config.impossible_tokens.contains(e.as_str()))
709 .map(|e| e.as_str())
710 .collect::<BTreeSet<_>>();
711 if expected.contains(config.identifier_sentinel) {
712 for token in config.special_identifier_tokens.iter() {
713 expected.remove(*token);
714 }
715 if !expected.contains(config.first_set_sentinel) {
716 for token in config.first_set_identifier_tokens.iter() {
717 expected.remove(*token);
718 }
719 }
720 }
721 if expected.is_empty() {
722 return None;
723 }
724
725 let mut expected_string = "expected ".to_owned();
726 #[allow(clippy::expect_used)]
728 join_with_conjunction(
729 &mut expected_string,
730 "or",
731 expected,
732 |f, token| match config.friendly_token_names.get(token) {
733 Some(friendly_token_name) => write!(f, "{}", friendly_token_name),
734 None => write!(f, "{}", token.replace('"', "`")),
735 },
736 )
737 .expect("failed to format expected tokens");
738 Some(expected_string)
739}
740
741#[derive(Clone, Debug, PartialEq, Eq)]
744pub struct ParseErrors(NonEmpty<ParseError>);
745
746impl ParseErrors {
747 pub(crate) fn singleton(err: impl Into<ParseError>) -> Self {
749 Self(NonEmpty::singleton(err.into()))
750 }
751
752 pub(crate) fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
754 Self(NonEmpty {
755 head: first,
756 tail: rest.into_iter().collect::<Vec<_>>(),
757 })
758 }
759
760 pub(crate) fn new_from_nonempty(errs: NonEmpty<ParseError>) -> Self {
762 Self(errs)
763 }
764
765 pub(crate) fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
766 NonEmpty::collect(i).map(Self::new_from_nonempty)
767 }
768
769 pub(crate) fn flatten(v: Vec<ParseErrors>) -> Option<Self> {
772 let (first, rest) = v.split_first()?;
773 let mut first = first.clone();
774 rest.iter()
775 .for_each(|errs| first.extend(errs.iter().cloned()));
776 Some(first)
777 }
778
779 pub(crate) fn transpose<T>(
783 i: impl IntoIterator<Item = Result<T, ParseErrors>>,
784 ) -> Result<Vec<T>, Self> {
785 let mut errs = vec![];
786 let oks: Vec<_> = i
787 .into_iter()
788 .filter_map(|r| r.map_err(|e| errs.push(e)).ok())
789 .collect();
790 if let Some(combined_errs) = Self::flatten(errs) {
791 Err(combined_errs)
792 } else {
793 Ok(oks)
794 }
795 }
796}
797
798impl Display for ParseErrors {
799 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800 write!(f, "{}", self.first()) }
802}
803
804impl std::error::Error for ParseErrors {
805 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
806 self.first().source()
807 }
808
809 #[allow(deprecated)]
810 fn description(&self) -> &str {
811 self.first().description()
812 }
813
814 #[allow(deprecated)]
815 fn cause(&self) -> Option<&dyn std::error::Error> {
816 self.first().cause()
817 }
818}
819
820impl Diagnostic for ParseErrors {
825 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
826 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
828 errs.next().map(move |first_err| match first_err.related() {
829 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
830 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
831 })
832 }
833
834 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
835 self.first().code()
836 }
837
838 fn severity(&self) -> Option<miette::Severity> {
839 self.first().severity()
840 }
841
842 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
843 self.first().help()
844 }
845
846 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
847 self.first().url()
848 }
849
850 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
851 self.first().source_code()
852 }
853
854 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
855 self.first().labels()
856 }
857
858 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
859 self.first().diagnostic_source()
860 }
861}
862
863impl AsRef<NonEmpty<ParseError>> for ParseErrors {
864 fn as_ref(&self) -> &NonEmpty<ParseError> {
865 &self.0
866 }
867}
868
869impl AsMut<NonEmpty<ParseError>> for ParseErrors {
870 fn as_mut(&mut self) -> &mut NonEmpty<ParseError> {
871 &mut self.0
872 }
873}
874
875impl Deref for ParseErrors {
876 type Target = NonEmpty<ParseError>;
877
878 fn deref(&self) -> &Self::Target {
879 &self.0
880 }
881}
882
883impl DerefMut for ParseErrors {
884 fn deref_mut(&mut self) -> &mut Self::Target {
885 &mut self.0
886 }
887}
888
889impl<T: Into<ParseError>> From<T> for ParseErrors {
890 fn from(err: T) -> Self {
891 ParseErrors::singleton(err.into())
892 }
893}
894
895impl<T: Into<ParseError>> Extend<T> for ParseErrors {
896 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
897 self.0.extend(iter.into_iter().map(Into::into))
898 }
899}
900
901impl IntoIterator for ParseErrors {
902 type Item = ParseError;
903 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::vec::IntoIter<Self::Item>>;
904
905 fn into_iter(self) -> Self::IntoIter {
906 self.0.into_iter()
907 }
908}
909
910impl<'a> IntoIterator for &'a ParseErrors {
911 type Item = &'a ParseError;
912 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::slice::Iter<'a, ParseError>>;
913
914 fn into_iter(self) -> Self::IntoIter {
915 iter::once(&self.head).chain(self.tail.iter())
916 }
917}