graphql_toolkit_parser/
lib.rs

1//! A GraphQL document parser
2#![warn(missing_docs)]
3#![allow(clippy::unnecessary_wraps)]
4#![allow(clippy::upper_case_acronyms)]
5#![allow(clippy::needless_question_mark)]
6#![allow(clippy::uninlined_format_args)]
7#![forbid(unsafe_code)]
8
9use std::fmt::{self, Display, Formatter};
10
11pub use graphql_toolkit_ast::*;
12pub use parse::{parse_query, parse_schema};
13use pest::{error::LineColLocation, RuleType};
14use serde::{Serialize, Serializer};
15
16mod parse;
17mod pos;
18
19/// Parser error.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum Error {
23    /// A syntax error occurred.
24    Syntax {
25        /// The message of the error, nicely formatted with newlines.
26        message: String,
27        /// The start position of the error.
28        start: Pos,
29        /// The end position of the error, if present.
30        end: Option<Pos>,
31    },
32    /// The schema contained multiple query, mutation or subscription roots.
33    MultipleRoots {
34        /// The type of root that was duplicated.
35        root: OperationType,
36        /// The position of the schema.
37        schema: Pos,
38        /// The position of the second root.
39        pos: Pos,
40    },
41    /// The schema contained no query root.
42    MissingQueryRoot {
43        /// The position of the schema.
44        pos: Pos,
45    },
46    /// Multiple operations were found in a document with an anonymous one.
47    MultipleOperations {
48        /// The position of the anonymous operation.
49        anonymous: Pos,
50        /// The position of the other operation.
51        operation: Pos,
52    },
53    /// An operation is defined multiple times in a document.
54    OperationDuplicated {
55        /// The name of the operation.
56        operation: Name,
57        /// The position of the first definition.
58        first: Pos,
59        /// The position of the second definition.
60        second: Pos,
61    },
62    /// A fragment is defined multiple times in a document.
63    FragmentDuplicated {
64        /// The name of the fragment.
65        fragment: Name,
66        /// The position of the first definition.
67        first: Pos,
68        /// The position of the second definition.
69        second: Pos,
70    },
71    /// The document does not contain any operation.
72    MissingOperation,
73    /// Recursion limit exceeded.
74    RecursionLimitExceeded,
75}
76
77impl Error {
78    /// Get an iterator over the positions of the error.
79    ///
80    /// The iterator is ordered from most important to least important position.
81    #[must_use]
82    pub fn positions(&self) -> ErrorPositions {
83        match self {
84            Self::Syntax {
85                start,
86                end: Some(end),
87                ..
88            } => ErrorPositions::new_2(*start, *end),
89            Self::Syntax { start, .. } => ErrorPositions::new_1(*start),
90            Self::MultipleRoots { schema, pos, .. } => ErrorPositions::new_2(*pos, *schema),
91            Self::MissingQueryRoot { pos } => ErrorPositions::new_1(*pos),
92            Self::MultipleOperations {
93                anonymous,
94                operation,
95            } => ErrorPositions::new_2(*anonymous, *operation),
96            Self::OperationDuplicated { first, second, .. } => {
97                ErrorPositions::new_2(*second, *first)
98            }
99            Self::FragmentDuplicated { first, second, .. } => {
100                ErrorPositions::new_2(*second, *first)
101            }
102            Self::MissingOperation => ErrorPositions::new_0(),
103            Self::RecursionLimitExceeded => ErrorPositions::new_0(),
104        }
105    }
106}
107
108impl Display for Error {
109    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
110        match self {
111            Self::Syntax { message, .. } => f.write_str(message),
112            Self::MissingQueryRoot { .. } => f.write_str("schema definition is missing query root"),
113            Self::MultipleRoots { root, .. } => {
114                write!(f, "multiple {} roots in schema definition", root)
115            }
116            Self::MultipleOperations { .. } => f.write_str("document contains multiple operations"),
117            Self::OperationDuplicated { operation, .. } => {
118                write!(f, "operation {} is defined twice", operation)
119            }
120            Self::FragmentDuplicated { fragment, .. } => {
121                write!(f, "fragment {} is defined twice", fragment)
122            }
123            Self::MissingOperation => f.write_str("document does not contain an operation"),
124            Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded."),
125        }
126    }
127}
128
129impl std::error::Error for Error {}
130
131impl<R: RuleType> From<pest::error::Error<R>> for Error {
132    fn from(err: pest::error::Error<R>) -> Self {
133        let (start, end) = match err.line_col {
134            LineColLocation::Pos(at) => (at, None),
135            LineColLocation::Span(start, end) => (start, Some(end)),
136        };
137
138        Error::Syntax {
139            message: err.to_string(),
140            start: Pos::from(start),
141            end: end.map(Pos::from),
142        }
143    }
144}
145
146/// An alias for `Result<T, Error>`.
147pub type Result<T> = std::result::Result<T, Error>;
148
149/// An iterator over the positions inside an error.
150///
151/// Constructed from the `Error::postions` function.
152#[derive(Debug, Clone)]
153pub struct ErrorPositions(ErrorPositionsInner);
154
155impl ErrorPositions {
156    fn new_0() -> Self {
157        Self(ErrorPositionsInner::None)
158    }
159    fn new_1(a: Pos) -> Self {
160        Self(ErrorPositionsInner::One(a))
161    }
162    fn new_2(a: Pos, b: Pos) -> Self {
163        Self(ErrorPositionsInner::Two(a, b))
164    }
165}
166
167impl Iterator for ErrorPositions {
168    type Item = Pos;
169
170    fn next(&mut self) -> Option<Self::Item> {
171        match self.0 {
172            ErrorPositionsInner::Two(a, b) => {
173                self.0 = ErrorPositionsInner::One(b);
174                Some(a)
175            }
176            ErrorPositionsInner::One(a) => {
177                self.0 = ErrorPositionsInner::None;
178                Some(a)
179            }
180            ErrorPositionsInner::None => None,
181        }
182    }
183
184    fn size_hint(&self) -> (usize, Option<usize>) {
185        let len = self.len();
186        (len, Some(len))
187    }
188}
189
190impl DoubleEndedIterator for ErrorPositions {
191    fn next_back(&mut self) -> Option<Self::Item> {
192        match self.0 {
193            ErrorPositionsInner::Two(a, b) => {
194                self.0 = ErrorPositionsInner::One(a);
195                Some(b)
196            }
197            ErrorPositionsInner::One(a) => {
198                self.0 = ErrorPositionsInner::None;
199                Some(a)
200            }
201            ErrorPositionsInner::None => None,
202        }
203    }
204}
205
206impl std::iter::FusedIterator for ErrorPositions {}
207
208impl ExactSizeIterator for ErrorPositions {
209    fn len(&self) -> usize {
210        match self.0 {
211            ErrorPositionsInner::Two(_, _) => 2,
212            ErrorPositionsInner::One(_) => 1,
213            ErrorPositionsInner::None => 0,
214        }
215    }
216}
217
218impl Serialize for ErrorPositions {
219    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
220        serializer.collect_seq(self.clone())
221    }
222}
223
224#[derive(Debug, Clone, Copy)]
225enum ErrorPositionsInner {
226    Two(Pos, Pos),
227    One(Pos),
228    None,
229}