libgraphql_core/operation/
fragment_registry_builder.rs1use crate::ast;
2use crate::file_reader;
3use crate::loc;
4use crate::operation::Fragment;
5use crate::operation::FragmentBuilder;
6use crate::operation::FragmentBuildError;
7use crate::operation::FragmentRegistry;
8use crate::operation::Selection;
9use crate::operation::SelectionSet;
10use crate::schema::Schema;
11use std::collections::HashMap;
12use std::collections::HashSet;
13use std::path::Path;
14use std::sync::Arc;
15use thiserror::Error;
16
17type Result<T> = std::result::Result<T, Vec<FragmentRegistryBuildError>>;
18
19#[derive(Debug)]
58pub struct FragmentRegistryBuilder<'schema> {
59 fragments: HashMap<String, Fragment<'schema>>,
60}
61
62impl<'schema> FragmentRegistryBuilder<'schema> {
63 pub fn new() -> Self {
65 Self {
66 fragments: HashMap::new(),
67 }
68 }
69
70 pub fn add_fragment(
74 &mut self,
75 fragment: Fragment<'schema>,
76 ) -> std::result::Result<(), FragmentRegistryBuildError> {
77 let name = fragment.name.clone();
78
79 if let Some(existing) = self.fragments.get(&name) {
80 return Err(FragmentRegistryBuildError::DuplicateFragmentDefinition {
81 fragment_name: name,
82 first_def_location: existing.def_location.clone(),
83 second_def_location: fragment.def_location.clone(),
84 });
85 }
86
87 self.fragments.insert(name, fragment);
88 Ok(())
89 }
90
91 pub fn add_from_document_ast(
98 &mut self,
99 schema: &'schema Schema,
100 ast: &ast::operation::Document,
101 file_path: Option<&Path>,
102 ) -> std::result::Result<(), Vec<FragmentBuildError>> {
103 let mut errors = vec![];
104
105 for def in &ast.definitions {
106 if let ast::operation::Definition::Fragment(frag_def) = def {
107 let temp_registry = FragmentRegistry::empty();
109
110 match FragmentBuilder::from_ast(schema, temp_registry, frag_def, file_path)
111 .and_then(|builder| builder.build())
112 {
113 Ok(fragment) => {
114 if let Err(FragmentRegistryBuildError::DuplicateFragmentDefinition {
116 fragment_name,
117 first_def_location,
118 second_def_location,
119 }) = self.add_fragment(fragment) {
120 errors.push(FragmentBuildError::DuplicateFragmentDefinition {
121 fragment_name,
122 first_def_location,
123 second_def_location,
124 });
125 }
126 }
127 Err(e) => errors.push(e),
128 }
129 }
130 }
131
132 if !errors.is_empty() {
133 return Err(errors);
134 }
135
136 Ok(())
137 }
138
139 pub fn add_from_document_file(
143 &mut self,
144 schema: &'schema Schema,
145 file_path: impl AsRef<Path>,
146 ) -> std::result::Result<(), Vec<FragmentBuildError>> {
147 let file_path = file_path.as_ref();
148 let file_content = file_reader::read_content(file_path)
149 .map_err(|e| vec![FragmentBuildError::FileReadError(Box::new(e))])?;
150
151 self.add_from_document_str(schema, file_content, Some(file_path))
152 }
153
154 pub fn add_from_document_str(
158 &mut self,
159 schema: &'schema Schema,
160 content: impl AsRef<str>,
161 file_path: Option<&Path>,
162 ) -> std::result::Result<(), Vec<FragmentBuildError>> {
163 let ast_doc = ast::operation::parse(content.as_ref())
164 .map_err(|e| vec![FragmentBuildError::ParseError(Arc::new(e))])?;
165
166 self.add_from_document_ast(schema, &ast_doc, file_path)
167 }
168
169 pub fn build(self) -> Result<FragmentRegistry<'schema>> {
179 let mut errors = Vec::new();
180
181 errors.extend(self.validate_no_cycles());
183
184 errors.extend(self.validate_fragment_references());
186
187 if !errors.is_empty() {
188 return Err(errors);
189 }
190
191 Ok(FragmentRegistry {
192 fragments: self.fragments,
193 })
194 }
195
196 fn validate_no_cycles(&self) -> Vec<FragmentRegistryBuildError> {
202 let mut all_cycles = Vec::new();
203 let mut seen_normalized_cycles = HashSet::new();
204
205 for fragment_name in self.fragments.keys() {
206 let mut path = Vec::new();
207 let mut visiting = HashSet::new();
208
209 self.check_fragment_cycles(
210 fragment_name,
211 &mut path,
212 &mut visiting,
213 &mut all_cycles,
214 &mut seen_normalized_cycles,
215 );
216 }
217
218 all_cycles
219 }
220
221 fn check_fragment_cycles(
222 &self,
223 fragment_name: &str,
224 path: &mut Vec<String>,
225 visiting: &mut HashSet<String>,
226 errors: &mut Vec<FragmentRegistryBuildError>,
227 seen_normalized: &mut HashSet<Vec<String>>,
228 ) {
229 if visiting.contains(fragment_name) {
231 path.push(fragment_name.to_string());
232
233 let normalized = Self::normalize_cycle(path);
235
236 if !seen_normalized.contains(&normalized) {
238 seen_normalized.insert(normalized);
239 errors.push(FragmentRegistryBuildError::FragmentCycleDetected {
240 cycle_path: path.clone(),
241 });
242 }
243
244 path.pop();
245 return;
246 }
247
248 let Some(fragment) = self.fragments.get(fragment_name) else {
250 return;
251 };
252
253 path.push(fragment_name.to_string());
254 visiting.insert(fragment_name.to_string());
255
256 self.check_selection_set_cycles(
258 &fragment.selection_set,
259 path,
260 visiting,
261 errors,
262 seen_normalized,
263 );
264
265 path.pop();
266 visiting.remove(fragment_name);
267 }
268
269 fn check_selection_set_cycles(
270 &self,
271 selection_set: &SelectionSet<'schema>,
272 path: &mut Vec<String>,
273 visiting: &mut HashSet<String>,
274 errors: &mut Vec<FragmentRegistryBuildError>,
275 seen_normalized: &mut HashSet<Vec<String>>,
276 ) {
277 for selection in &selection_set.selections {
278 match selection {
279 Selection::FragmentSpread(spread) => {
280 self.check_fragment_cycles(
281 spread.fragment_name(),
282 path,
283 visiting,
284 errors,
285 seen_normalized,
286 );
287 }
288 Selection::InlineFragment(inline) => {
289 self.check_selection_set_cycles(
290 inline.selection_set(),
291 path,
292 visiting,
293 errors,
294 seen_normalized,
295 );
296 }
297 Selection::Field(field) => {
298 if let Some(nested_set) = field.selection_set() {
299 self.check_selection_set_cycles(
300 nested_set,
301 path,
302 visiting,
303 errors,
304 seen_normalized,
305 );
306 }
307 }
308 }
309 }
310 }
311
312 fn validate_fragment_references(&self) -> Vec<FragmentRegistryBuildError> {
314 let mut errors = Vec::new();
315
316 for (fragment_name, fragment) in &self.fragments {
317 self.check_fragment_refs_in_selection_set(
318 fragment_name,
319 &fragment.selection_set,
320 &mut errors,
321 );
322 }
323
324 errors
325 }
326
327 fn check_fragment_refs_in_selection_set(
328 &self,
329 parent_fragment: &str,
330 selection_set: &SelectionSet<'schema>,
331 errors: &mut Vec<FragmentRegistryBuildError>,
332 ) {
333 for selection in &selection_set.selections {
334 match selection {
335 Selection::FragmentSpread(spread) => {
336 let ref_name = spread.fragment_name();
337 if !self.fragments.contains_key(ref_name) {
338 errors.push(FragmentRegistryBuildError::UndefinedFragmentReference {
339 fragment_name: parent_fragment.to_string(),
340 undefined_fragment: ref_name.to_string(),
341 reference_location: spread.def_location.clone(),
342 });
343 }
344 }
345 Selection::InlineFragment(inline) => {
346 self.check_fragment_refs_in_selection_set(
347 parent_fragment,
348 inline.selection_set(),
349 errors,
350 );
351 }
352 Selection::Field(field) => {
353 if let Some(nested_set) = field.selection_set() {
354 self.check_fragment_refs_in_selection_set(
355 parent_fragment,
356 nested_set,
357 errors,
358 );
359 }
360 }
361 }
362 }
363 }
364
365 fn normalize_cycle(cycle: &[String]) -> Vec<String> {
381 if cycle.is_empty() {
382 return Vec::new();
383 }
384
385 let cycle_without_repeat = &cycle[..cycle.len() - 1];
387
388 let min_idx = cycle_without_repeat
390 .iter()
391 .enumerate()
392 .min_by(|(_, a), (_, b)| a.cmp(b))
393 .map(|(idx, _)| idx)
394 .unwrap_or(0);
395
396 let mut normalized = Vec::new();
398 normalized.extend_from_slice(&cycle_without_repeat[min_idx..]);
399 normalized.extend_from_slice(&cycle_without_repeat[..min_idx]);
400
401 normalized.push(normalized[0].clone());
403
404 normalized
405 }
406}
407
408impl<'schema> Default for FragmentRegistryBuilder<'schema> {
409 fn default() -> Self {
410 Self::new()
411 }
412}
413
414#[derive(Clone, Debug, Error)]
415pub enum FragmentRegistryBuildError {
416 #[error("Duplicate fragment definition: '{fragment_name}'")]
417 DuplicateFragmentDefinition {
418 fragment_name: String,
419 first_def_location: loc::SourceLocation,
420 second_def_location: loc::SourceLocation,
421 },
422
423 #[error("Fragment cycle detected: {}", format_cycle_path(.cycle_path))]
424 FragmentCycleDetected { cycle_path: Vec<String> },
425
426 #[error("Fragment '{fragment_name}' references undefined fragment '{undefined_fragment}'")]
427 UndefinedFragmentReference {
428 fragment_name: String,
429 undefined_fragment: String,
430 reference_location: loc::SourceLocation,
431 },
432}
433
434fn format_cycle_path(cycle: &[String]) -> String {
435 cycle.join(" → ")
436}