async_graphql_parser/
lib.rs

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