libgraphql_core/operation/
executable_document_builder.rs1use crate::ast;
2use crate::file_reader;
3use crate::operation::ExecutableDocument;
4use crate::operation::FragmentBuilder;
5use crate::operation::FragmentBuildError;
6use crate::operation::FragmentRegistry;
7use crate::operation::Operation;
8use crate::operation::OperationBuilder;
9use crate::operation::OperationBuildError;
10use crate::schema::Schema;
11use std::path::Path;
12use std::sync::Arc;
13use thiserror::Error;
14
15type Result<T> = std::result::Result<T, Vec<ExecutableDocumentBuildError>>;
16
17pub struct ExecutableDocumentBuilder<'schema: 'fragreg, 'fragreg> {
18 fragment_registry: &'fragreg FragmentRegistry<'schema>,
19 operations: Vec<Operation<'schema, 'fragreg>>,
20 schema: &'schema Schema,
21}
22
23impl<'schema, 'fragreg> ExecutableDocumentBuilder<'schema, 'fragreg> {
24 pub fn build(self) -> Result<ExecutableDocument<'schema, 'fragreg>> {
25 Ok(ExecutableDocument {
26 fragment_registry: self.fragment_registry,
27 operations: self.operations,
28 schema: self.schema,
29 })
30 }
31
32 pub fn new(
33 schema: &'schema Schema,
34 fragment_registry: &'fragreg FragmentRegistry<'schema>,
35 ) -> Self {
36 Self {
37 fragment_registry,
38 operations: vec![],
39 schema,
40 }
41 }
42
43 pub fn from_ast(
44 schema: &'schema Schema,
45 fragment_registry: &'fragreg FragmentRegistry<'schema>,
46 ast: &ast::operation::Document,
47 file_path: Option<&Path>,
48 ) -> Result<Self> {
49 let mut frag_errors = vec![];
50 let mut op_build_errors = vec![];
51 let mut errors = vec![];
52 let mut operations = vec![];
53 for def in &ast.definitions {
54 use ast::operation::Definition as Def;
55 match def {
56 Def::Fragment(frag_def) => {
57 let fragment_name = frag_def.name.as_str();
59
60 let fragment = FragmentBuilder::from_ast(
62 schema,
63 fragment_registry,
64 frag_def,
65 file_path,
66 ).and_then(|builder| builder.build());
67 let fragment = match fragment {
68 Ok(fragment) => fragment,
69 Err(err) => {
70 frag_errors.push(err);
71 continue;
72 }
73 };
74
75 let registry_frag =
77 fragment_registry
78 .fragments()
79 .get(fragment_name);
80
81 if let Some(registry_frag) = registry_frag
82 && &fragment != registry_frag {
83 errors.push(
87 ExecutableDocumentBuildError::FragmentDefinitionMismatch {
88 fragment_name: fragment_name.to_string(),
89 document_location: fragment.def_location.clone(),
90 registry_location: registry_frag.def_location.clone(),
91 }
92 );
93 } else if registry_frag.is_none() {
94 errors.push(
95 ExecutableDocumentBuildError::FragmentNotInRegistry {
96 fragment_name: fragment_name.to_string(),
97 document_location: fragment.def_location.clone(),
98 }
99 );
100 }
101 },
102
103 Def::Operation(op_def) => {
104 let mut maybe_op = OperationBuilder::from_ast(
116 schema,
117 fragment_registry,
118 op_def,
119 file_path,
120 ).and_then(|op_builder| op_builder.build());
121
122 if let Err(errs) = &mut maybe_op {
123 op_build_errors.append(errs);
124 continue;
125 }
126 operations.push(maybe_op.unwrap())
127 },
128 }
129 }
130
131 if !frag_errors.is_empty() {
132 errors.push(
133 ExecutableDocumentBuildError::FragmentValidationErrors(
134 frag_errors
135 )
136 )
137 }
138
139 if !op_build_errors.is_empty() {
140 errors.push(
141 ExecutableDocumentBuildError::OperationBuildErrors(
142 op_build_errors
143 )
144 );
145 }
146
147 if !errors.is_empty() {
148 return Err(errors);
149 }
150
151 Ok(Self {
152 fragment_registry,
153 schema,
154 operations,
155 })
156 }
157
158 pub fn from_file(
159 schema: &'schema Schema,
160 fragment_registry: &'fragreg FragmentRegistry<'schema>,
161 file_path: impl AsRef<Path>,
162 ) -> Result<Self> {
163 let file_path = file_path.as_ref();
164 let file_content = file_reader::read_content(file_path)
165 .map_err(|e| ExecutableDocumentBuildError::ExecutableDocumentFileReadError(
166 Box::new(e),
167 ))?;
168 Self::from_str(schema, fragment_registry, file_content, Some(file_path))
169 }
170
171 pub fn from_str(
172 schema: &'schema Schema,
173 fragment_registry: &'fragreg FragmentRegistry<'schema>,
174 content: impl AsRef<str>,
175 file_path: Option<&Path>,
176 ) -> Result<Self> {
177 let ast_doc =
178 ast::operation::parse(content.as_ref())
179 .map_err(|e| vec![e.into()])?;
180 Self::from_ast(schema, fragment_registry, &ast_doc, file_path)
181 }
182}
183
184#[derive(Clone, Debug, Error)]
185pub enum ExecutableDocumentBuildError {
186 #[error(
187 "Failure while trying to read an executable document file from disk: $0"
188 )]
189 ExecutableDocumentFileReadError(Box<file_reader::ReadContentError>),
190
191 #[error(
192 "Encountered errors while building operations within this \
193 executable document: {0:?}",
194 )]
195 OperationBuildErrors(Vec<OperationBuildError>),
196
197 #[error("Error parsing executable document: $0")]
198 ParseError(Arc<ast::operation::ParseError>),
199
200 #[error(
201 "Fragment '{fragment_name}' is defined in the document but does not match \
202 the fragment in the registry"
203 )]
204 FragmentDefinitionMismatch {
205 fragment_name: String,
206 document_location: crate::loc::SourceLocation,
207 registry_location: crate::loc::SourceLocation,
208 },
209
210 #[error("Some fragments have validation errors: {0:?}")]
211 FragmentValidationErrors(Vec<FragmentBuildError>),
212
213 #[error(
214 "Fragment '{fragment_name}' is defined in the document but does not exist \
215 in the provided FragmentRegistry"
216 )]
217 FragmentNotInRegistry {
218 fragment_name: String,
219 document_location: crate::loc::SourceLocation,
220 },
221}
222impl std::convert::From<ast::operation::ParseError> for ExecutableDocumentBuildError {
223 fn from(value: ast::operation::ParseError) -> Self {
224 Self::ParseError(Arc::new(value))
225 }
226}
227impl std::convert::From<ExecutableDocumentBuildError> for Vec<ExecutableDocumentBuildError> {
228 fn from(value: ExecutableDocumentBuildError) -> Self {
229 vec![value]
230 }
231}