libgraphql_parser/parse_result.rs
1//! Result type for parsing operations that may produce partial results.
2
3use crate::GraphQLParseError;
4
5/// The result of a parsing operation.
6///
7/// Unlike `Result<T, E>`, `ParseResult` can contain both a partial AST and
8/// errors. This enables error recovery: the parser can report multiple errors
9/// while still producing as much AST as possible.
10///
11/// # Design Rationale
12///
13/// Traditional `Result<T, E>` forces a binary choice: either success with a
14/// value, or failure with an error. But GraphQL tooling often benefits from
15/// having both:
16///
17/// - **IDE integration**: Show syntax errors while still providing completions
18/// based on the partially-parsed document
19/// - **Batch error reporting**: Report all syntax errors in one pass rather
20/// than stopping at the first error
21/// - **Graceful degradation**: Process as much of a document as possible even
22/// when parts are invalid
23///
24/// # Accessing the AST
25///
26/// Two methods are provided for accessing the AST, depending on your use case:
27///
28/// - [`valid_ast()`](Self::valid_ast) - Returns the AST only if parsing was
29/// completely successful (no errors). Use this when you need guaranteed-valid
30/// input.
31///
32/// - [`ast()`](Self::ast) - Returns the AST if present, regardless of errors.
33/// Use this for tools that want best-effort results (formatters, IDE
34/// features, linters).
35///
36/// # Example
37///
38/// ```
39/// # use libgraphql_parser::ast;
40/// # use libgraphql_parser::GraphQLParser;
41/// #
42/// # let source = "type Query { foo: String }";
43/// # let parser = GraphQLParser::new(source);
44/// #
45/// # fn analyze_schema(schema: &ast::schema::Document) { }
46/// # fn provide_ide_completions(schema: &ast::schema::Document) { }
47/// #
48/// let result = parser.parse_schema_document();
49///
50/// // Strict mode: only accept fully valid documents
51/// if let Some(doc) = result.valid_ast() {
52/// analyze_schema(doc);
53/// }
54///
55/// // Best-effort mode: work with whatever we got
56/// if let Some(doc) = result.ast() {
57/// provide_ide_completions(doc);
58/// }
59///
60/// // Report any errors
61/// if result.has_errors() {
62/// for error in &result.errors {
63/// eprintln!("{}", error.format_detailed(Some(source)));
64/// }
65/// }
66/// ```
67#[derive(Debug)]
68pub struct ParseResult<TAst> {
69 /// The parsed AST, if parsing produced any result.
70 ///
71 /// This may be `Some` even when `errors` is non-empty, representing a
72 /// recovered/partial parse result.
73 ast: Option<TAst>,
74
75 /// Errors encountered during parsing.
76 ///
77 /// Empty if parsing was completely successful.
78 pub errors: Vec<GraphQLParseError>,
79}
80
81impl<TAst> ParseResult<TAst> {
82 /// Creates a successful parse result with no errors.
83 pub(crate) fn ok(ast: TAst) -> Self {
84 Self {
85 ast: Some(ast),
86 errors: Vec::new(),
87 }
88 }
89
90 /// Creates a failed parse result with errors but no AST.
91 #[cfg(test)]
92 pub(crate) fn err(errors: Vec<GraphQLParseError>) -> Self {
93 Self { ast: None, errors }
94 }
95
96 /// Creates a recovered parse result with both AST and errors.
97 ///
98 /// The AST was produced via error recovery and may be incomplete or contain
99 /// placeholder values.
100 pub(crate) fn recovered(ast: TAst, errors: Vec<GraphQLParseError>) -> Self {
101 Self {
102 ast: Some(ast),
103 errors,
104 }
105 }
106
107 /// Returns the AST only if parsing was completely successful (no errors).
108 ///
109 /// Use this when you need guaranteed-valid input, such as when compiling
110 /// a schema or executing a query.
111 ///
112 /// Returns `None` if:
113 /// - Parsing failed entirely (no AST produced)
114 /// - Parsing succeeded but with errors (recovered AST)
115 pub fn valid_ast(&self) -> Option<&TAst> {
116 if self.errors.is_empty() {
117 self.ast.as_ref()
118 } else {
119 None
120 }
121 }
122
123 /// Returns the AST if present, regardless of whether errors occurred.
124 ///
125 /// Use this for tools that want best-effort results:
126 /// - IDE features (completions, hover info)
127 /// - Formatters (format what we can parse)
128 /// - Linters (report issues even in partially-valid documents)
129 ///
130 /// Check [`has_errors()`](Self::has_errors) to determine if the AST was
131 /// produced via error recovery.
132 pub fn ast(&self) -> Option<&TAst> {
133 self.ast.as_ref()
134 }
135
136 /// Takes ownership of the AST only if parsing was completely successful.
137 ///
138 /// This is the consuming version of [`valid_ast()`](Self::valid_ast).
139 pub fn into_valid_ast(self) -> Option<TAst> {
140 if self.errors.is_empty() {
141 self.ast
142 } else {
143 None
144 }
145 }
146
147 /// Takes ownership of the AST regardless of errors.
148 ///
149 /// This is the consuming version of [`ast()`](Self::ast).
150 pub fn into_ast(self) -> Option<TAst> {
151 self.ast
152 }
153
154 /// Returns `true` if parsing was completely successful (has AST, no
155 /// errors).
156 pub fn is_ok(&self) -> bool {
157 self.ast.is_some() && self.errors.is_empty()
158 }
159
160 /// Returns `true` if any errors were encountered during parsing.
161 pub fn has_errors(&self) -> bool {
162 !self.errors.is_empty()
163 }
164
165 /// Formats all errors as a single string for display.
166 ///
167 /// # Arguments
168 /// - `source`: Optional source text for snippet extraction in error
169 /// messages.
170 pub fn format_errors(&self, source: Option<&str>) -> String {
171 self.errors
172 .iter()
173 .map(|e| e.format_detailed(source))
174 .collect::<Vec<_>>()
175 .join("\n")
176 }
177}
178
179impl<TAst> From<ParseResult<TAst>> for Result<TAst, Vec<GraphQLParseError>> {
180 /// Converts to a standard `Result`, treating recovered ASTs as errors.
181 ///
182 /// Returns `Ok(ast)` only if there were no errors. Otherwise returns
183 /// `Err(errors)`, even if a recovered AST was available.
184 fn from(result: ParseResult<TAst>) -> Self {
185 if result.errors.is_empty() {
186 match result.ast {
187 Some(ast) => Ok(ast),
188 None => Err(Vec::new()),
189 }
190 } else {
191 Err(result.errors)
192 }
193 }
194}