Skip to main content

partiql_parser/
error.rs

1// Copyright Amazon.com, Inc. or its affiliates.
2
3//! [`Error`] and [`Result`] types for parsing `PartiQL`.
4
5use std::borrow::Cow;
6use std::fmt::{Debug, Display};
7
8use partiql_common::syntax::location::Located;
9use thiserror::Error;
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14/// Errors in the lexical structure of a `PartiQL` query.
15///
16/// ### Notes
17/// This is marked `#[non_exhaustive]`, to reserve the right to add more variants in the future.
18#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
19#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20#[non_exhaustive]
21pub enum LexError<'input> {
22    /// Generic invalid input; likely an unrecognizable token.
23    #[error("Lexing error: invalid input `{}`", .0)]
24    InvalidInput(Cow<'input, str>),
25    /// Embedded Ion value is not properly terminated.
26    #[error("Lexing error: unterminated embedded document literal")]
27    UnterminatedDocLiteral,
28    /// Comment is not properly terminated.
29    #[error("Lexing error: unterminated comment")]
30    UnterminatedComment,
31    /// Any other lexing error.
32    #[error("Lexing error: unknown error")]
33    Unknown,
34}
35
36/// Errors in the syntactic structure of a `PartiQL` query.
37///
38/// ### Notes
39/// This is marked `#[non_exhaustive]`, to reserve the right to add more variants in the future.
40#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42#[non_exhaustive]
43pub enum ParseError<'input, Loc>
44where
45    Loc: Display,
46{
47    /// Indicates that there was a problem with syntax.
48    #[error("Syntax Error: {} at `{}`", _0.inner, _0.location)]
49    SyntaxError(Located<String, Loc>),
50
51    /// There were not enough tokens to complete a parse
52    #[error("Unexpected end of input")]
53    UnexpectedEndOfInput(Loc),
54
55    /// An otherwise un-categorized error occurred
56    #[error("Unknown parse error at `{}`", _0)]
57    Unknown(Loc),
58
59    /// There was a token that was not expected
60    #[error("Unexpected token `{}` at `{}`", _0.inner.token, _0.location)]
61    UnexpectedToken(UnexpectedToken<'input, Loc>),
62
63    /// There was an error lexing the input
64    #[error("{} at `{}`", _0.inner, _0.location)]
65    LexicalError(Located<LexError<'input>, Loc>),
66
67    /// Indicates that there is an internal error that was not due to user input or API violation.
68    #[error("Illegal State: {0}")]
69    IllegalState(String),
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
74pub struct UnexpectedTokenData<'input> {
75    /// The unexpected token
76    pub token: Cow<'input, str>,
77    // TODO expected: ...,
78}
79pub type UnexpectedToken<'input, L> = Located<UnexpectedTokenData<'input>, L>;
80
81impl<'input, Loc: Debug> ParseError<'input, Loc>
82where
83    Loc: Display,
84{
85    /// Maps an `ParserError<Loc>` to `ParserError<Loc2>` by applying a function to each variant
86    pub fn map_loc<F, Loc2>(self, mut tx: F) -> ParseError<'input, Loc2>
87    where
88        Loc2: Display,
89        F: FnMut(Loc) -> Loc2,
90    {
91        match self {
92            ParseError::SyntaxError(l) => ParseError::SyntaxError(l.map_loc(tx)),
93            ParseError::UnexpectedEndOfInput(l) => ParseError::UnexpectedEndOfInput((tx)(l)),
94            ParseError::UnexpectedToken(l) => ParseError::UnexpectedToken(l.map_loc(tx)),
95            ParseError::LexicalError(l) => ParseError::LexicalError(l.map_loc(tx)),
96            ParseError::IllegalState(s) => ParseError::IllegalState(s),
97            _ => ParseError::IllegalState("Unhandled internal error".to_string()),
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use partiql_common::syntax::location::{ByteOffset, BytePosition, LineAndColumn, ToLocated};
106    use std::num::NonZeroUsize;
107
108    #[test]
109    fn syntax_error() {
110        let e1 = ParseError::SyntaxError("oops".to_string().to_located(
111            BytePosition::from(ByteOffset::from(255))..BytePosition::from(ByteOffset::from(512)),
112        ));
113
114        let e2 = e1.map_loc(|BytePosition(x)| BytePosition(x - 2));
115        assert_eq!(e2.to_string(), "Syntax Error: oops at `(b253..b510)`");
116    }
117
118    #[test]
119    fn unexpected_token() {
120        let e1 = ParseError::UnexpectedToken(
121            UnexpectedTokenData { token: "/".into() }
122                .to_located(BytePosition(0.into())..ByteOffset::from(1).into()),
123        );
124
125        let e2 = e1.map_loc(|x| BytePosition(x.0 + 1));
126        assert_eq!(e2.to_string(), "Unexpected token `/` at `(b1..b2)`");
127    }
128
129    #[test]
130    fn lexical_error() {
131        let lex = LexError::InvalidInput("🤷".into())
132            .to_located(LineAndColumn::new(1, 1).unwrap()..LineAndColumn::new(5, 5).unwrap());
133        let e1 = ParseError::LexicalError(lex);
134
135        let e2 = e1.map_loc(|LineAndColumn { line, column }| LineAndColumn {
136            line,
137            column: NonZeroUsize::new(column.get() + 10).unwrap(),
138        });
139        assert_eq!(
140            e2.to_string(),
141            "Lexing error: invalid input `🤷` at `(1:11..5:15)`"
142        );
143    }
144
145    #[test]
146    fn illegal_state() {
147        let e1: ParseError<'_, BytePosition> = ParseError::IllegalState("uh oh".to_string());
148
149        let e2 = e1.map_loc(|x| x);
150        assert_eq!(e2.to_string(), "Illegal State: uh oh");
151    }
152}