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)]
129#[non_exhaustive]
130pub enum ToASTErrorKind {
131 #[error("a template with id `{0}` already exists in the policy set")]
133 DuplicateTemplateId(ast::PolicyID),
134 #[error("a policy with id `{0}` already exists in the policy set")]
136 DuplicatePolicyId(ast::PolicyID),
137 #[error(transparent)]
139 #[diagnostic(transparent)]
140 ExpectedStaticPolicy(#[from] parse_errors::ExpectedStaticPolicy),
141 #[error(transparent)]
143 #[diagnostic(transparent)]
144 ExpectedTemplate(#[from] parse_errors::ExpectedTemplate),
145 #[error("duplicate annotation: @{0}")]
148 DuplicateAnnotation(ast::AnyId),
149 #[error(transparent)]
152 #[diagnostic(transparent)]
153 SlotsInConditionClause(#[from] parse_errors::SlotsInConditionClause),
154 #[error("this policy is missing the `{0}` variable in the scope")]
157 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
158 MissingScopeVariable(ast::Var),
159 #[error("this policy has an extra element in the scope: {0}")]
161 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
162 ExtraScopeElement(cst::VariableDef),
163 #[error("this identifier is reserved and cannot be used: {0}")]
165 ReservedIdentifier(cst::Ident),
166 #[error("invalid identifier: {0}")]
170 InvalidIdentifier(String),
171 #[error("'=' is not a valid operator in Cedar")]
174 #[diagnostic(help("try using '==' instead"))]
175 InvalidSingleEq,
176 #[error("invalid policy effect: {0}")]
178 #[diagnostic(help("effect must be either `permit` or `forbid`"))]
179 InvalidEffect(cst::Ident),
180 #[error("invalid policy condition: {0}")]
182 #[diagnostic(help("condition must be either `when` or `unless`"))]
183 InvalidCondition(cst::Ident),
184 #[error("found an invalid variable in the policy scope: {0}")]
187 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
188 InvalidScopeVariable(cst::Ident),
189 #[error("found the variable `{got}` where the variable `{expected}` must be used")]
192 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
193 IncorrectVariable {
194 expected: ast::Var,
196 got: ast::Var,
198 },
199 #[error("invalid operator in the policy scope: {0}")]
201 #[diagnostic(help("policy scope clauses can only use `==`, `in`, `is`, or `_ is _ in _`"))]
202 InvalidScopeOperator(cst::RelOp),
203 #[error("invalid operator in the action scope: {0}")]
206 #[diagnostic(help("action scope clauses can only use `==` or `in`"))]
207 InvalidActionScopeOperator(cst::RelOp),
208 #[error("`is` cannot appear in the action scope")]
210 #[diagnostic(help("try moving `action is ..` into a `when` condition"))]
211 IsInActionScope,
212 #[error("`is` cannot be used together with `==`")]
214 #[diagnostic(help("try using `_ is _ in _`"))]
215 IsWithEq,
216 #[error(transparent)]
218 #[diagnostic(transparent)]
219 InvalidActionType(#[from] parse_errors::InvalidActionType),
220 #[error("{}condition clause cannot be empty", match .0 { Some(ident) => format!("`{}` ", ident), None => "".to_string() })]
222 EmptyClause(Option<cst::Ident>),
223 #[error("internal invariant violated. Membership chain did not resolve to an expression")]
226 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
227 MembershipInvariantViolation,
228 #[error("invalid string literal: {0}")]
230 InvalidString(String),
231 #[error("invalid variable: {0}")]
234 #[diagnostic(help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `{0}` in quotes to make a string?"))]
235 ArbitraryVariable(SmolStr),
236 #[error("invalid attribute name: {0}")]
238 #[diagnostic(help("attribute names can either be identifiers or string literals"))]
239 InvalidAttribute(SmolStr),
240 #[error("`{0}` cannot be used as an attribute as it contains a namespace")]
242 PathAsAttribute(String),
243 #[error("`{0}` is a method, not a function")]
245 #[diagnostic(help("use a method-style call `e.{0}(..)`"))]
246 FunctionCallOnMethod(ast::UnreservedId),
247 #[error("`{0}` is a function, not a method")]
249 #[diagnostic(help("use a function-style call `{0}(..)`"))]
250 MethodCallOnFunction(ast::UnreservedId),
251 #[error("right hand side of a `like` expression must be a pattern literal, but got `{0}`")]
253 InvalidPattern(String),
254 #[error("right hand side of an `is` expression must be an entity type name, but got `{0}`")]
256 #[diagnostic(help("try using `==` to test for equality"))]
257 InvalidIsType(String),
258 #[error("expected {expected}, found {got}")]
260 WrongNode {
261 expected: &'static str,
263 got: String,
265 #[help]
267 suggestion: Option<String>,
268 },
269 #[error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")]
272 AmbiguousOperators,
273 #[error("division is not supported")]
275 UnsupportedDivision,
276 #[error("remainder/modulo is not supported")]
278 UnsupportedModulo,
279 #[error(transparent)]
281 #[diagnostic(transparent)]
282 ExpressionConstructionError(#[from] ast::ExpressionConstructionError),
283 #[error("integer literal `{0}` is too large")]
285 #[diagnostic(help("maximum allowed integer literal is `{}`", ast::InputInteger::MAX))]
286 IntegerLiteralTooLarge(u64),
287 #[error("too many occurrences of `{0}`")]
289 #[diagnostic(help("cannot chain more the 4 applications of a unary operator"))]
290 UnaryOpLimit(ast::UnaryOp),
291 #[error("`{0}(...)` is not a valid function call")]
294 #[diagnostic(help("variables cannot be called as functions"))]
295 VariableCall(ast::Var),
296 #[error("attempted to call `{0}.{1}(...)`, but `{0}` does not have any methods")]
298 NoMethods(ast::Name, ast::UnreservedId),
299 #[error("`{id}` is not a valid method")]
301 UnknownMethod {
302 id: ast::UnreservedId,
304 #[help]
306 hint: Option<String>,
307 },
308 #[error("`{id}` is not a valid function")]
310 UnknownFunction {
311 id: ast::Name,
313 #[help]
315 hint: Option<String>,
316 },
317 #[error("invalid entity literal: {0}")]
319 #[diagnostic(help("entity literals should have a form like `Namespace::User::\"alice\"`"))]
320 InvalidEntityLiteral(String),
321 #[error("function calls must be of the form `<name>(arg1, arg2, ...)`")]
324 ExpressionCall,
325 #[error("invalid member access `{0}.{1}`, `{0}` has no fields or methods")]
327 InvalidAccess(ast::Name, SmolStr),
328 #[error("invalid indexing expression `{0}[\"{}\"]`, `{0}` has no fields", .1.escape_debug())]
330 InvalidIndex(ast::Name, SmolStr),
331 #[error("the contents of an index expression must be a string literal")]
333 NonStringIndex,
334 #[error("type constraints using `:` are not supported")]
338 #[diagnostic(help("try using `is` instead"))]
339 TypeConstraints,
340 #[error("`{kind}` needs to be normalized (e.g., whitespace removed): {src}")]
342 #[diagnostic(help("the normalized form is `{normalized_src}`"))]
343 NonNormalizedString {
344 kind: &'static str,
346 src: String,
348 normalized_src: String,
350 },
351 #[error("internal invariant violated. Parsed data node should not be empty")]
356 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
357 EmptyNodeInvariantViolation,
358 #[error("call to `{name}` requires exactly {expected} argument{}, but got {got} argument{}", if .expected == &1 { "" } else { "s" }, if .got == &1 { "" } else { "s" })]
360 WrongArity {
361 name: &'static str,
363 expected: usize,
365 got: usize,
367 },
368 #[error(transparent)]
370 #[diagnostic(transparent)]
371 Unescape(#[from] UnescapeError),
372 #[error(transparent)]
374 #[diagnostic(transparent)]
375 WrongEntityArgument(#[from] parse_errors::WrongEntityArgument),
376 #[error("`{0}` is not a valid template slot")]
378 #[diagnostic(help("a template slot may only be `?principal` or `?resource`"))]
379 InvalidSlot(SmolStr),
380 #[error(transparent)]
382 #[diagnostic(transparent)]
383 ReservedNamespace(#[from] ReservedNameError),
384 #[error("when `is` and `in` are used together, `is` must come first")]
386 #[diagnostic(help("try `_ is _ in _`"))]
387 InvertedIsIn,
388}
389
390impl ToASTErrorKind {
391 pub fn wrong_node(
393 expected: &'static str,
394 got: impl Into<String>,
395 suggestion: Option<impl Into<String>>,
396 ) -> Self {
397 Self::WrongNode {
398 expected,
399 got: got.into(),
400 suggestion: suggestion.map(Into::into),
401 }
402 }
403
404 pub fn wrong_arity(name: &'static str, expected: usize, got: usize) -> Self {
406 Self::WrongArity {
407 name,
408 expected,
409 got,
410 }
411 }
412
413 pub fn slots_in_condition_clause(slot: ast::Slot, clause_type: &'static str) -> Self {
415 parse_errors::SlotsInConditionClause { slot, clause_type }.into()
416 }
417
418 pub fn expected_static_policy(slot: ast::Slot) -> Self {
420 parse_errors::ExpectedStaticPolicy { slot }.into()
421 }
422
423 pub fn expected_template() -> Self {
425 parse_errors::ExpectedTemplate::new().into()
426 }
427
428 pub fn wrong_entity_argument_one_expected(
431 expected: parse_errors::Ref,
432 got: parse_errors::Ref,
433 ) -> Self {
434 parse_errors::WrongEntityArgument {
435 expected: Either::Left(expected),
436 got,
437 }
438 .into()
439 }
440
441 pub fn wrong_entity_argument_two_expected(
444 r1: parse_errors::Ref,
445 r2: parse_errors::Ref,
446 got: parse_errors::Ref,
447 ) -> Self {
448 let expected = Either::Right((r1, r2));
449 parse_errors::WrongEntityArgument { expected, got }.into()
450 }
451}
452
453pub mod parse_errors {
455
456 use std::sync::Arc;
457
458 use super::*;
459
460 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
462 #[error("expected a static policy, got a template containing the slot {}", slot.id)]
463 #[diagnostic(help("try removing the template slot(s) from this policy"))]
464 pub struct ExpectedStaticPolicy {
465 pub(crate) slot: ast::Slot,
467 }
468
469 impl From<ast::UnexpectedSlotError> for ExpectedStaticPolicy {
470 fn from(err: ast::UnexpectedSlotError) -> Self {
471 match err {
472 ast::UnexpectedSlotError::FoundSlot(slot) => Self { slot },
473 }
474 }
475 }
476
477 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
479 #[error("expected a template, got a static policy")]
480 #[diagnostic(help("a template should include slot(s) `?principal` or `?resource`"))]
481 pub struct ExpectedTemplate {
482 _dummy: (),
487 }
488
489 impl ExpectedTemplate {
490 pub(crate) fn new() -> Self {
491 Self { _dummy: () }
492 }
493 }
494
495 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
497 #[error("found template slot {} in a `{clause_type}` clause", slot.id)]
498 #[diagnostic(help("slots are currently unsupported in `{clause_type}` clauses"))]
499 pub struct SlotsInConditionClause {
500 pub(crate) slot: ast::Slot,
502 pub(crate) clause_type: &'static str,
504 }
505
506 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
508 #[diagnostic(help("action entities must have type `Action`, optionally in a namespace"))]
509 pub struct InvalidActionType {
510 pub(crate) euids: NonEmpty<Arc<ast::EntityUID>>,
511 }
512
513 impl std::fmt::Display for InvalidActionType {
514 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515 let subject = if self.euids.len() > 1 {
516 "entity uids"
517 } else {
518 "an entity uid"
519 };
520 write!(f, "expected {subject} with type `Action` but got ")?;
521 join_with_conjunction(f, "and", self.euids.iter(), |f, e| write!(f, "`{e}`"))
522 }
523 }
524
525 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
527 #[error("expected {}, found {got}", match .expected { Either::Left(r) => r.to_string(), Either::Right((r1, r2)) => format!("{r1} or {r2}") })]
528 pub struct WrongEntityArgument {
529 pub(crate) expected: Either<Ref, (Ref, Ref)>,
532 pub(crate) got: Ref,
534 }
535
536 #[derive(Debug, Clone, PartialEq, Eq)]
538 pub enum Ref {
539 Single,
541 Set,
543 Template,
545 }
546
547 impl std::fmt::Display for Ref {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 match self {
550 Ref::Single => write!(f, "single entity uid"),
551 Ref::Template => write!(f, "template slot"),
552 Ref::Set => write!(f, "set of entity uids"),
553 }
554 }
555 }
556}
557
558#[derive(Clone, Debug, Error, PartialEq, Eq)]
560pub struct ToCSTError {
561 err: OwnedRawParseError,
562}
563
564impl ToCSTError {
565 pub fn primary_source_span(&self) -> SourceSpan {
567 match &self.err {
568 OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
569 OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
570 OwnedRawParseError::UnrecognizedToken {
571 token: (token_start, _, token_end),
572 ..
573 } => SourceSpan::from(*token_start..*token_end),
574 OwnedRawParseError::ExtraToken {
575 token: (token_start, _, token_end),
576 } => SourceSpan::from(*token_start..*token_end),
577 OwnedRawParseError::User { error } => error.loc.span,
578 }
579 }
580
581 pub(crate) fn from_raw_parse_err(err: RawParseError<'_>) -> Self {
582 Self {
583 err: err.map_token(|token| token.to_string()),
584 }
585 }
586
587 pub(crate) fn from_raw_err_recovery(recovery: RawErrorRecovery<'_>) -> Self {
588 Self::from_raw_parse_err(recovery.error)
589 }
590}
591
592impl Display for ToCSTError {
593 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
594 match &self.err {
595 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
596 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
597 OwnedRawParseError::UnrecognizedToken {
598 token: (_, token, _),
599 ..
600 } => write!(f, "unexpected token `{token}`"),
601 OwnedRawParseError::ExtraToken {
602 token: (_, token, _),
603 ..
604 } => write!(f, "extra token `{token}`"),
605 OwnedRawParseError::User { error } => write!(f, "{error}"),
606 }
607 }
608}
609
610impl Diagnostic for ToCSTError {
611 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
612 let primary_source_span = self.primary_source_span();
613 let labeled_span = match &self.err {
614 OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
615 OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
616 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
617 primary_source_span,
618 ),
619 OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
620 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
621 primary_source_span,
622 ),
623 OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
624 OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
625 };
626 Some(Box::new(iter::once(labeled_span)))
627 }
628}
629
630#[derive(Debug)]
633pub struct ExpectedTokenConfig {
634 pub friendly_token_names: HashMap<&'static str, &'static str>,
638
639 pub impossible_tokens: HashSet<&'static str>,
644
645 pub special_identifier_tokens: HashSet<&'static str>,
652
653 pub identifier_sentinel: &'static str,
656
657 pub first_set_identifier_tokens: HashSet<&'static str>,
662
663 pub first_set_sentinel: &'static str,
667}
668
669lazy_static! {
670 static ref POLICY_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
671 friendly_token_names: HashMap::from([
672 ("TRUE", "`true`"),
673 ("FALSE", "`false`"),
674 ("IF", "`if`"),
675 ("PERMIT", "`permit`"),
676 ("FORBID", "`forbid`"),
677 ("WHEN", "`when`"),
678 ("UNLESS", "`unless`"),
679 ("IN", "`in`"),
680 ("HAS", "`has`"),
681 ("LIKE", "`like`"),
682 ("IS", "`is`"),
683 ("THEN", "`then`"),
684 ("ELSE", "`else`"),
685 ("PRINCIPAL", "`principal`"),
686 ("ACTION", "`action`"),
687 ("RESOURCE", "`resource`"),
688 ("CONTEXT", "`context`"),
689 ("PRINCIPAL_SLOT", "`?principal`"),
690 ("RESOURCE_SLOT", "`?resource`"),
691 ("IDENTIFIER", "identifier"),
692 ("NUMBER", "number"),
693 ("STRINGLIT", "string literal"),
694 ]),
695 impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
696 special_identifier_tokens: HashSet::from([
697 "PERMIT",
698 "FORBID",
699 "WHEN",
700 "UNLESS",
701 "IN",
702 "HAS",
703 "LIKE",
704 "IS",
705 "THEN",
706 "ELSE",
707 "PRINCIPAL",
708 "ACTION",
709 "RESOURCE",
710 "CONTEXT",
711 ]),
712 identifier_sentinel: "IDENTIFIER",
713 first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
714 first_set_sentinel: "\"!\"",
715 };
716}
717
718pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
720 let mut expected = expected
721 .iter()
722 .filter(|e| !config.impossible_tokens.contains(e.as_str()))
723 .map(|e| e.as_str())
724 .collect::<BTreeSet<_>>();
725 if expected.contains(config.identifier_sentinel) {
726 for token in config.special_identifier_tokens.iter() {
727 expected.remove(*token);
728 }
729 if !expected.contains(config.first_set_sentinel) {
730 for token in config.first_set_identifier_tokens.iter() {
731 expected.remove(*token);
732 }
733 }
734 }
735 if expected.is_empty() {
736 return None;
737 }
738
739 let mut expected_string = "expected ".to_owned();
740 #[allow(clippy::expect_used)]
742 join_with_conjunction(
743 &mut expected_string,
744 "or",
745 expected,
746 |f, token| match config.friendly_token_names.get(token) {
747 Some(friendly_token_name) => write!(f, "{}", friendly_token_name),
748 None => write!(f, "{}", token.replace('"', "`")),
749 },
750 )
751 .expect("failed to format expected tokens");
752 Some(expected_string)
753}
754
755#[derive(Clone, Debug, PartialEq, Eq)]
758pub struct ParseErrors(NonEmpty<ParseError>);
759
760impl ParseErrors {
761 pub(crate) fn singleton(err: impl Into<ParseError>) -> Self {
763 Self(NonEmpty::singleton(err.into()))
764 }
765
766 pub(crate) fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
768 Self(NonEmpty {
769 head: first,
770 tail: rest.into_iter().collect::<Vec<_>>(),
771 })
772 }
773
774 pub(crate) fn new_from_nonempty(errs: NonEmpty<ParseError>) -> Self {
776 Self(errs)
777 }
778
779 pub(crate) fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
780 NonEmpty::collect(i).map(Self::new_from_nonempty)
781 }
782
783 pub(crate) fn flatten(v: Vec<ParseErrors>) -> Option<Self> {
786 let (first, rest) = v.split_first()?;
787 let mut first = first.clone();
788 rest.iter()
789 .for_each(|errs| first.extend(errs.iter().cloned()));
790 Some(first)
791 }
792
793 pub(crate) fn transpose<T>(
797 i: impl IntoIterator<Item = Result<T, ParseErrors>>,
798 ) -> Result<Vec<T>, Self> {
799 let mut errs = vec![];
800 let oks: Vec<_> = i
801 .into_iter()
802 .filter_map(|r| r.map_err(|e| errs.push(e)).ok())
803 .collect();
804 if let Some(combined_errs) = Self::flatten(errs) {
805 Err(combined_errs)
806 } else {
807 Ok(oks)
808 }
809 }
810}
811
812impl Display for ParseErrors {
813 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
814 write!(f, "{}", self.first()) }
816}
817
818impl std::error::Error for ParseErrors {
819 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
820 self.first().source()
821 }
822
823 #[allow(deprecated)]
824 fn description(&self) -> &str {
825 self.first().description()
826 }
827
828 #[allow(deprecated)]
829 fn cause(&self) -> Option<&dyn std::error::Error> {
830 self.first().cause()
831 }
832}
833
834impl Diagnostic for ParseErrors {
839 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
840 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
842 errs.next().map(move |first_err| match first_err.related() {
843 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
844 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
845 })
846 }
847
848 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
849 self.first().code()
850 }
851
852 fn severity(&self) -> Option<miette::Severity> {
853 self.first().severity()
854 }
855
856 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
857 self.first().help()
858 }
859
860 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
861 self.first().url()
862 }
863
864 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
865 self.first().source_code()
866 }
867
868 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
869 self.first().labels()
870 }
871
872 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
873 self.first().diagnostic_source()
874 }
875}
876
877impl AsRef<NonEmpty<ParseError>> for ParseErrors {
878 fn as_ref(&self) -> &NonEmpty<ParseError> {
879 &self.0
880 }
881}
882
883impl AsMut<NonEmpty<ParseError>> for ParseErrors {
884 fn as_mut(&mut self) -> &mut NonEmpty<ParseError> {
885 &mut self.0
886 }
887}
888
889impl Deref for ParseErrors {
890 type Target = NonEmpty<ParseError>;
891
892 fn deref(&self) -> &Self::Target {
893 &self.0
894 }
895}
896
897impl DerefMut for ParseErrors {
898 fn deref_mut(&mut self) -> &mut Self::Target {
899 &mut self.0
900 }
901}
902
903impl<T: Into<ParseError>> From<T> for ParseErrors {
904 fn from(err: T) -> Self {
905 ParseErrors::singleton(err.into())
906 }
907}
908
909impl<T: Into<ParseError>> Extend<T> for ParseErrors {
910 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
911 self.0.extend(iter.into_iter().map(Into::into))
912 }
913}
914
915impl IntoIterator for ParseErrors {
916 type Item = ParseError;
917 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::vec::IntoIter<Self::Item>>;
918
919 fn into_iter(self) -> Self::IntoIter {
920 self.0.into_iter()
921 }
922}
923
924impl<'a> IntoIterator for &'a ParseErrors {
925 type Item = &'a ParseError;
926 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::slice::Iter<'a, ParseError>>;
927
928 fn into_iter(self) -> Self::IntoIter {
929 iter::once(&self.head).chain(self.tail.iter())
930 }
931}