1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::fmt;

/// An `Error` type for operations performed in the lexer and the parser.
///
/// Errors get returned alongside the resulting CST if either the lexer or the
/// parser encouter lexical or syntactical errors respectively.
///
/// We encourage you to check for the CST's errors before proceeding to iterate
/// over the CST's nodes:
///
/// ## Example
/// ```rust
/// use apollo_parser::Parser;
///
/// let input = "union SearchResult = Photo | Person | Cat | Dog";
/// let parser = Parser::new(input);
/// let cst = parser.parse();
///
/// assert_eq!(0, cst.errors().len());
///
/// let doc = cst.document();
/// ```
///
/// ### Diagnostics
///
/// Using something like [miette crate] along with apollo-parser lets you have
/// more visual diagnostics. [miette] and [annotate_snippets] examples guide you
/// through integrating them with apollo-parser. These are useful if you are
/// displaying Errors in a terminal-like environment.
///
/// <img src="https://raw.githubusercontent.com/apollographql/apollo-rs/main/crates/apollo-parser/screenshots/apollo_parser_error.png" alt="A screenshot of an error example produced by using apollo-parser and miette. The ascii display shows a graphql code snippet with line numbers to the left. Under the code sample there is a line pointing to where a value is missing in graphql code">
///
/// [miette]: https://github.com/apollographql/apollo-rs/blob/a7f616454a53dcb8496725ceac6c63eacddefb2c/crates/apollo-parser/examples/miette.rs
/// [annotate_snippets]: https://github.com/apollographql/apollo-rs/blob/a7f616454a53dcb8496725ceac6c63eacddefb2c/crates/apollo-parser/examples/annotate_snippet.rs
/// [miette crate]: https://docs.rs/miette/3.2.0/miette/index.html

#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub(crate) enum ErrorData {
    Eof,
    LimitExceeded,
    Text(String),
}

impl ErrorData {
    pub fn len(&self) -> usize {
        match self {
            Self::Eof | Self::LimitExceeded => 0,
            Self::Text(text) => text.len(),
        }
    }
}

impl fmt::Display for ErrorData {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Eof => write!(f, "EOF"),
            Self::LimitExceeded => Ok(()),
            Self::Text(text) => write!(f, "{text}"),
        }
    }
}

#[derive(PartialEq, Eq, Clone, Hash, thiserror::Error)]
#[error("ERROR@{index}:{} {message:?} {data}", .index + .data.len())]
pub struct Error {
    pub(crate) message: String,
    pub(crate) data: ErrorData,
    pub(crate) index: usize,
}

impl Error {
    /// Create a new instance of `Error`.
    pub fn new<S: Into<String>>(message: S, data: String) -> Self {
        Self {
            message: message.into(),
            data: ErrorData::Text(data),
            index: 0,
        }
    }

    /// Create a new instance of `Error` with a `Location`.
    pub fn with_loc<S: Into<String>>(message: S, data: String, index: usize) -> Self {
        Self {
            message: message.into(),
            data: ErrorData::Text(data),
            index,
        }
    }

    pub fn limit<S: Into<String>>(message: S, index: usize) -> Self {
        Self {
            message: message.into(),
            data: ErrorData::LimitExceeded,
            index,
        }
    }

    pub fn eof<S: Into<String>>(message: S, index: usize) -> Self {
        Self {
            message: message.into(),
            data: ErrorData::Eof,
            index,
        }
    }

    /// Get a reference to the error's data. This is usually the token that
    /// `apollo-parser` has found to be lexically or syntactically incorrect.
    pub fn data(&self) -> &str {
        match &self.data {
            ErrorData::Text(text) => text,
            _ => "",
        }
    }

    pub fn is_limit(&self) -> bool {
        matches!(&self.data, ErrorData::LimitExceeded)
    }

    pub fn is_eof(&self) -> bool {
        matches!(&self.data, ErrorData::Eof)
    }

    pub(crate) fn set_data(&mut self, data: String) {
        self.data = ErrorData::Text(data);
    }

    /// Get a reference to the error's index. This is where the error begins in
    /// a given input.
    pub fn index(&self) -> usize {
        self.index
    }

    /// Get a reference to the error's message.
    pub fn message(&self) -> &str {
        self.message.as_ref()
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let start = self.index;
        let end = self.index + self.data.len();

        write!(
            f,
            "ERROR@{}:{} {:?} {}",
            start, end, self.message, self.data
        )
    }
}