Skip to main content

graphql_query/visit/
folder.rs

1use crate::ast::*;
2use bumpalo::collections::Vec;
3
4pub use super::{folder_simple::SimpleFolder, Path, PathSegment, VisitInfo};
5pub use crate::error::{Error, Result};
6
7pub(crate) mod private {
8    use hashbrown::{hash_map::DefaultHashBuilder, HashMap};
9    use std::cell::RefCell;
10
11    use super::{ASTContext, Definition, Document, Folder, Result, Vec};
12    use crate::{ast::FragmentDefinitionWithIndex, visit::VisitInfo};
13
14    /// Private Folder context state that's kept to keep track of the current folding progress and
15    /// state. This contains the AST context and optional records on fragments and the new document's
16    /// definition if the `Folder` is being traversed by operation.
17    pub struct FolderContext<'a> {
18        pub(crate) ctx: &'a ASTContext,
19        pub(crate) definitions: RefCell<Vec<'a, Definition<'a>>>,
20        pub(crate) fragments: RefCell<
21            HashMap<
22                &'a str,
23                FragmentDefinitionWithIndex<'a>,
24                DefaultHashBuilder,
25                &'a bumpalo::Bump,
26            >,
27        >,
28        pub(crate) fragment_names:
29            RefCell<HashMap<&'a str, &'a str, DefaultHashBuilder, &'a bumpalo::Bump>>,
30        pub(crate) recurse: bool,
31    }
32
33    impl<'a> FolderContext<'a> {
34        pub(crate) fn empty(ctx: &'a ASTContext) -> Self {
35            FolderContext {
36                ctx,
37                fragments: RefCell::new(HashMap::new_in(&ctx.arena)),
38                fragment_names: RefCell::new(HashMap::new_in(&ctx.arena)),
39                definitions: RefCell::new(Vec::new_in(&ctx.arena)),
40                recurse: false,
41            }
42        }
43
44        pub(crate) fn new_vec<T>(&self) -> Vec<'_, T> {
45            Vec::new_in(&self.ctx.arena)
46        }
47
48        pub(crate) fn with_document(ctx: &'a ASTContext, document: &'a Document<'a>) -> Self {
49            FolderContext {
50                ctx,
51                fragments: RefCell::new(document.fragments_with_index(ctx)),
52                fragment_names: RefCell::new(HashMap::new_in(&ctx.arena)),
53                definitions: RefCell::new(Vec::new_in(&ctx.arena)),
54                recurse: true,
55            }
56        }
57    }
58
59    pub trait FoldNode<'a>: Sized {
60        fn fold_with_ctx<'b, F: Folder<'a>>(
61            &self,
62            info: &mut VisitInfo,
63            ctx: &'a FolderContext<'a>,
64            folder: &'b mut F,
65        ) -> Result<Self>;
66    }
67}
68
69/// Trait for a folder that carries methods that are called as callback while AST nodes
70/// implementing the folder pattern are traversed and edited.
71///
72/// A Folder is used to traverse an GraphQL AST top-to-bottom, depth-first and to replace the AST's
73/// nodes by calling the Folder's callbacks and replacing the AST nodes one by one. After an AST
74/// Node is folded it's an entirely new copy, separate from the input AST, which remains untouched.
75///
76/// All callbacks have a default no-op implementation that return the input AST Node and hence only
77/// create an unchanged copy of the AST.
78/// The callbacks receive a reference to the [`ASTContext`] and must return the altered (or
79/// unchanged) node that's placed into the new AST using a [Result]. This can be used to also
80/// return an error and stop the folding.
81///
82/// This pattern is applicable to any AST node that implements the [`FoldNode`] trait.
83pub trait Folder<'a> {
84    /// Folds an [`OperationDefinition`] into a new node as part of a new, transformed AST, before
85    /// the Operation is folded recursively
86    #[inline]
87    fn enter_operation(
88        &mut self,
89        _ctx: &'a ASTContext,
90        operation: OperationDefinition<'a>,
91        _info: &VisitInfo,
92    ) -> Result<OperationDefinition<'a>> {
93        Ok(operation)
94    }
95
96    /// Folds an [`OperationDefinition`] into a new node as part of a new, transformed AST, after
97    /// the Operation has been folded.
98    #[inline]
99    fn leave_operation(
100        &mut self,
101        _ctx: &'a ASTContext,
102        operation: OperationDefinition<'a>,
103        _info: &VisitInfo,
104    ) -> Result<OperationDefinition<'a>> {
105        Ok(operation)
106    }
107
108    /// Folds a [`FragmentDefinition`] into a new node as part of a new, transformed AST, before the
109    /// FragmentDefinition is folded recursively.
110    #[inline]
111    fn enter_fragment(
112        &mut self,
113        _ctx: &'a ASTContext,
114        fragment: FragmentDefinition<'a>,
115        _info: &VisitInfo,
116    ) -> Result<FragmentDefinition<'a>> {
117        Ok(fragment)
118    }
119
120    /// Folds a [`FragmentDefinition`] into a new node as part of a new, transformed AST, after the
121    /// FragmentDefinition has been folded.
122    #[inline]
123    fn leave_fragment(
124        &mut self,
125        _ctx: &'a ASTContext,
126        fragment: FragmentDefinition<'a>,
127        _info: &VisitInfo,
128    ) -> Result<FragmentDefinition<'a>> {
129        Ok(fragment)
130    }
131
132    /// Folds a [`VariableDefinitions`] node into a new node as part of a new, transformed AST.
133    #[inline]
134    fn variable_definitions(
135        &mut self,
136        _ctx: &'a ASTContext,
137        var_defs: VariableDefinitions<'a>,
138        _info: &VisitInfo,
139    ) -> Result<VariableDefinitions<'a>> {
140        Ok(var_defs)
141    }
142
143    /// Folds a [`VariableDefinition`] into a new node as part of a new, transformed AST.
144    #[inline]
145    fn variable_definition(
146        &mut self,
147        _ctx: &'a ASTContext,
148        var_def: VariableDefinition<'a>,
149        _info: &VisitInfo,
150    ) -> Result<VariableDefinition<'a>> {
151        Ok(var_def)
152    }
153
154    /// Folds a [`SelectionSet`] into a new node as part of a new, transformed AST.
155    #[inline]
156    fn selection_set(
157        &mut self,
158        _ctx: &'a ASTContext,
159        selection_set: SelectionSet<'a>,
160        _info: &VisitInfo,
161    ) -> Result<SelectionSet<'a>> {
162        Ok(selection_set)
163    }
164
165    /// Folds a [`FragmentSpread`] node into a new node as part of a new, transformed AST, before
166    /// the FragmentSpread is folded recursively.
167    #[inline]
168    fn enter_fragment_spread(
169        &mut self,
170        _ctx: &'a ASTContext,
171        fragment_spread: FragmentSpread<'a>,
172        _info: &VisitInfo,
173    ) -> Result<FragmentSpread<'a>> {
174        Ok(fragment_spread)
175    }
176
177    /// Folds a [`FragmentSpread`] node into a new node as part of a new, transformed AST, after
178    /// the FragmentSpread has been folded.
179    #[inline]
180    fn leave_fragment_spread(
181        &mut self,
182        _ctx: &'a ASTContext,
183        fragment_spread: FragmentSpread<'a>,
184        _info: &VisitInfo,
185    ) -> Result<FragmentSpread<'a>> {
186        Ok(fragment_spread)
187    }
188
189    /// Folds an [`InlineFragment`] into a new node as part of a new, transformed AST, before the
190    /// InlineFragment is folded recursively.
191    #[inline]
192    fn enter_inline_fragment(
193        &mut self,
194        _ctx: &'a ASTContext,
195        inline_fragment: InlineFragment<'a>,
196        _info: &VisitInfo,
197    ) -> Result<InlineFragment<'a>> {
198        Ok(inline_fragment)
199    }
200
201    /// Folds an [`InlineFragment`] into a new node as part of a new, transformed AST, after the
202    /// InlineFragment has been folded.
203    #[inline]
204    fn leave_inline_fragment(
205        &mut self,
206        _ctx: &'a ASTContext,
207        inline_fragment: InlineFragment<'a>,
208        _info: &VisitInfo,
209    ) -> Result<InlineFragment<'a>> {
210        Ok(inline_fragment)
211    }
212
213    /// Folds a [Field] into a new node as part of a new, transformed AST, before the Field is
214    /// folded recursively.
215    #[inline]
216    fn enter_field(
217        &mut self,
218        _ctx: &'a ASTContext,
219        field: Field<'a>,
220        _info: &VisitInfo,
221    ) -> Result<Field<'a>> {
222        Ok(field)
223    }
224
225    /// Folds a [Field] into a new node as part of a new, transformed AST, after the field has been
226    /// folded.
227    #[inline]
228    fn leave_field(
229        &mut self,
230        _ctx: &'a ASTContext,
231        field: Field<'a>,
232        _info: &VisitInfo,
233    ) -> Result<Field<'a>> {
234        Ok(field)
235    }
236
237    /// Folds a [Directives] node into a new node as part of a new, transformed AST.
238    #[inline]
239    fn directives(
240        &mut self,
241        _ctx: &'a ASTContext,
242        directives: Directives<'a>,
243        _info: &VisitInfo,
244    ) -> Result<Directives<'a>> {
245        Ok(directives)
246    }
247
248    /// Folds a [Directive] into a new node as part of a new, transformed AST, before the Directive
249    /// is folded recursively.
250    #[inline]
251    fn enter_directive(
252        &mut self,
253        _ctx: &'a ASTContext,
254        directive: Directive<'a>,
255        _info: &VisitInfo,
256    ) -> Result<Directive<'a>> {
257        Ok(directive)
258    }
259
260    /// Folds a [Directive] into a new node as part of a new, transformed AST, after the Directive
261    /// has been folded.
262    #[inline]
263    fn leave_directive(
264        &mut self,
265        _ctx: &'a ASTContext,
266        directive: Directive<'a>,
267        _info: &VisitInfo,
268    ) -> Result<Directive<'a>> {
269        Ok(directive)
270    }
271
272    /// Folds a [Arguments] node into a new node as part of a new, transformed AST.
273    #[inline]
274    fn arguments(
275        &mut self,
276        _ctx: &'a ASTContext,
277        arguments: Arguments<'a>,
278        _info: &VisitInfo,
279    ) -> Result<Arguments<'a>> {
280        Ok(arguments)
281    }
282
283    /// Folds an [Argument] into a new node as part of a new, transformed AST.
284    #[inline]
285    fn argument(
286        &mut self,
287        _ctx: &'a ASTContext,
288        argument: Argument<'a>,
289        _info: &VisitInfo,
290    ) -> Result<Argument<'a>> {
291        Ok(argument)
292    }
293
294    /// Folds a [Value] node into a new node as part of a new, transformed AST.
295    #[inline]
296    fn value(
297        &mut self,
298        _ctx: &'a ASTContext,
299        value: Value<'a>,
300        _info: &VisitInfo,
301    ) -> Result<Value<'a>> {
302        Ok(value)
303    }
304
305    /// Folds a [Type] node into a new node as part of a new, transformed AST.
306    #[inline]
307    fn of_type(
308        &mut self,
309        _ctx: &'a ASTContext,
310        of_type: Type<'a>,
311        _info: &VisitInfo,
312    ) -> Result<Type<'a>> {
313        Ok(of_type)
314    }
315
316    /// Folds a [Variable] node into a new node as part of a new, transformed AST.
317    #[inline]
318    fn variable(
319        &mut self,
320        _ctx: &'a ASTContext,
321        var: Variable<'a>,
322        _info: &VisitInfo,
323    ) -> Result<Variable<'a>> {
324        Ok(var)
325    }
326
327    /// Folds a [`NamedType`] node into a new node as part of a new, transformed AST.
328    #[inline]
329    fn named_type(
330        &mut self,
331        _ctx: &'a ASTContext,
332        name: NamedType<'a>,
333        _info: &VisitInfo,
334    ) -> Result<NamedType<'a>> {
335        Ok(name)
336    }
337}
338
339/// Trait for folding AST Nodes of a GraphQL language document in depth-first order using a
340/// custom folder. This transforms the AST while creating a new copy of it.
341///
342/// The folder must implement the [Folder] trait.
343pub trait FoldNode<'a>: private::FoldNode<'a> {
344    /// Visit the GraphQL AST node tree recursively in depth-first order and create a transformed
345    /// copy of it using the given folder. The folder must implement the [Folder] trait.
346    ///
347    /// This will return a [Result] containing a reference to the new copied AST Node allocated on
348    /// the current AST Context's arena or an error.
349    fn fold<'b, F: Folder<'a>>(
350        &'a self,
351        ctx: &'a ASTContext,
352        folder: &'b mut F,
353    ) -> Result<&'a Self> {
354        let mut info = VisitInfo::default();
355        let folder_ctx = ctx.alloc(private::FolderContext::empty(ctx));
356        Ok(ctx.alloc(self.fold_with_ctx(&mut info, folder_ctx, folder)?))
357    }
358}
359
360/// Trait for folding a GraphQL Document AST Node by traversing an operation instead of the entire
361/// AST tree. This method alters the traversal of the folder and traverses starting from an operation
362/// instead; folding the fragment definitions only as they're used and refered to using
363/// `FragmentSpread` nodes in the operation.
364///
365/// If a Document should instead be transformed and copied in its entirety then `Document::fold` is
366/// a better choice.
367pub trait FoldDocument<'a>: private::FoldNode<'a> {
368    /// Folds a GraphQL document by a given operation instead. Instead of transforming the given
369    /// document in its entirety `fold_operation` will start at the defined operation instead,
370    /// transforming fragments only as they're referred to via `FragmentSpread` nodes. This will
371    /// create a new document that only refers to and contains the specified `operation`.
372    fn fold_operation<'b, F: Folder<'a>>(
373        &'a self,
374        ctx: &'a ASTContext,
375        operation: Option<&'a str>,
376        folder: &'b mut F,
377    ) -> Result<&'a Self>;
378}
379
380impl<'a, T: private::FoldNode<'a>> FoldNode<'a> for T {}
381
382impl<'a> private::FoldNode<'a> for Type<'a> {
383    #[inline]
384    fn fold_with_ctx<'b, F: Folder<'a>>(
385        &self,
386        info: &mut VisitInfo,
387        ctx: &'b private::FolderContext<'a>,
388        folder: &'b mut F,
389    ) -> Result<Self> {
390        folder.of_type(ctx.ctx, *self, info)
391    }
392}
393
394impl<'a> private::FoldNode<'a> for Value<'a> {
395    #[inline]
396    fn fold_with_ctx<'b, F: Folder<'a>>(
397        &self,
398        info: &mut VisitInfo,
399        ctx: &'a private::FolderContext<'a>,
400        folder: &'b mut F,
401    ) -> Result<Self> {
402        folder.value(ctx.ctx, self.clone(), info)
403    }
404}
405
406impl<'a> private::FoldNode<'a> for NamedType<'a> {
407    #[inline]
408    fn fold_with_ctx<'b, F: Folder<'a>>(
409        &self,
410        info: &mut VisitInfo,
411        ctx: &'a private::FolderContext<'a>,
412        folder: &'b mut F,
413    ) -> Result<Self> {
414        folder.named_type(ctx.ctx, *self, info)
415    }
416}
417
418impl<'a> private::FoldNode<'a> for Variable<'a> {
419    #[inline]
420    fn fold_with_ctx<'b, F: Folder<'a>>(
421        &self,
422        info: &mut VisitInfo,
423        ctx: &'a private::FolderContext<'a>,
424        folder: &'b mut F,
425    ) -> Result<Self> {
426        folder.variable(ctx.ctx, *self, info)
427    }
428}
429
430impl<'a> private::FoldNode<'a> for Argument<'a> {
431    #[inline]
432    fn fold_with_ctx<'b, F: Folder<'a>>(
433        &self,
434        info: &mut VisitInfo,
435        ctx: &'a private::FolderContext<'a>,
436        folder: &'b mut F,
437    ) -> Result<Self> {
438        let argument = folder.argument(ctx.ctx, self.clone(), info)?;
439
440        info.path.push(PathSegment::Value);
441        let value = argument.value.fold_with_ctx(info, ctx, folder)?;
442        info.path.pop();
443
444        Ok(Argument {
445            name: argument.name,
446            value,
447        })
448    }
449}
450
451impl<'a> private::FoldNode<'a> for Arguments<'a> {
452    #[inline]
453    fn fold_with_ctx<'b, F: Folder<'a>>(
454        &self,
455        info: &mut VisitInfo,
456        ctx: &'a private::FolderContext<'a>,
457        folder: &'b mut F,
458    ) -> Result<Self> {
459        let new_arguments_iter = {
460            folder
461                .arguments(ctx.ctx, self.clone(), info)?
462                .into_iter()
463                .enumerate()
464                .map(|(index, argument)| {
465                    info.path.push(PathSegment::Index(index));
466                    let folded = argument.fold_with_ctx(info, ctx, folder);
467                    info.path.pop();
468                    folded
469                })
470        };
471
472        let new_arguments = {
473            let mut new_arguments = ctx.new_vec();
474            for item in new_arguments_iter {
475                new_arguments.push(item?);
476            }
477
478            new_arguments
479        };
480
481        Ok(Arguments {
482            children: new_arguments,
483        })
484    }
485}
486
487impl<'a> private::FoldNode<'a> for Directive<'a> {
488    #[inline]
489    fn fold_with_ctx<'b, F: Folder<'a>>(
490        &self,
491        info: &mut VisitInfo,
492        ctx: &'a private::FolderContext<'a>,
493        folder: &'b mut F,
494    ) -> Result<Self> {
495        let directive = &folder.enter_directive(ctx.ctx, self.clone(), info)?;
496
497        info.path.push(PathSegment::Arguments);
498        let arguments = directive.arguments.fold_with_ctx(info, ctx, folder)?;
499        info.path.pop();
500
501        let directive = Directive {
502            name: directive.name,
503            arguments,
504        };
505        folder.leave_directive(ctx.ctx, directive, info)
506    }
507}
508
509impl<'a> private::FoldNode<'a> for Directives<'a> {
510    #[inline]
511    fn fold_with_ctx<'b, F: Folder<'a>>(
512        &self,
513        info: &mut VisitInfo,
514        ctx: &'a private::FolderContext<'a>,
515        folder: &'b mut F,
516    ) -> Result<Self> {
517        // TODO: how to make this Result dissapear
518        let new_directives_iter = folder
519            .directives(ctx.ctx, self.clone(), info)?
520            .into_iter()
521            .enumerate()
522            .map(|(index, directive)| {
523                info.path.push(PathSegment::Index(index));
524                let folded = directive.fold_with_ctx(info, ctx, folder);
525                info.path.pop();
526                folded
527            });
528
529        let mut new_directives = Vec::new_in(&ctx.ctx.arena);
530        for item in new_directives_iter {
531            new_directives.push(item?);
532        }
533        Ok(Directives {
534            children: new_directives,
535        })
536    }
537}
538
539impl<'a> private::FoldNode<'a> for VariableDefinition<'a> {
540    #[inline]
541    fn fold_with_ctx<'b, F: Folder<'a>>(
542        &self,
543        info: &mut VisitInfo,
544        ctx: &'a private::FolderContext<'a>,
545        folder: &'b mut F,
546    ) -> Result<Self> {
547        let var_def = folder.variable_definition(ctx.ctx, self.clone(), info)?;
548
549        info.path.push(PathSegment::Variable);
550        let variable = var_def.variable.fold_with_ctx(info, ctx, folder)?;
551        info.path.pop();
552
553        info.path.push(PathSegment::Type);
554        let of_type = var_def.of_type.fold_with_ctx(info, ctx, folder)?;
555        info.path.pop();
556
557        info.path.push(PathSegment::Value);
558        let default_value = var_def.default_value.fold_with_ctx(info, ctx, folder)?;
559        info.path.pop();
560
561        info.path.push(PathSegment::Directives);
562        let directives = var_def.directives.fold_with_ctx(info, ctx, folder)?;
563        info.path.pop();
564
565        Ok(VariableDefinition {
566            variable,
567            of_type,
568            default_value,
569            directives,
570        })
571    }
572}
573
574impl<'a> private::FoldNode<'a> for VariableDefinitions<'a> {
575    #[inline]
576    fn fold_with_ctx<'b, F: Folder<'a>>(
577        &self,
578        info: &mut VisitInfo,
579        ctx: &'a private::FolderContext<'a>,
580        folder: &'b mut F,
581    ) -> Result<Self> {
582        let new_variables_iter = folder
583            .variable_definitions(ctx.ctx, self.clone(), info)?
584            .into_iter()
585            .enumerate()
586            .map(|(index, var_def)| {
587                info.path.push(PathSegment::Index(index));
588                let folded = var_def.fold_with_ctx(info, ctx, folder);
589                info.path.pop();
590                folded
591            });
592
593        let mut new_variables = Vec::new_in(&ctx.ctx.arena);
594        for item in new_variables_iter {
595            new_variables.push(item?);
596        }
597        Ok(VariableDefinitions {
598            children: new_variables,
599        })
600    }
601}
602
603impl<'a> private::FoldNode<'a> for Field<'a> {
604    #[inline]
605    fn fold_with_ctx<'b, F: Folder<'a>>(
606        &self,
607        info: &mut VisitInfo,
608        ctx: &'a private::FolderContext<'a>,
609        folder: &'b mut F,
610    ) -> Result<Self> {
611        let field = &folder.enter_field(ctx.ctx, self.clone(), info)?;
612
613        info.path.push(PathSegment::Arguments);
614        let arguments = field.arguments.fold_with_ctx(info, ctx, folder)?;
615        info.path.pop();
616
617        info.path.push(PathSegment::Directives);
618        let directives = field.directives.fold_with_ctx(info, ctx, folder)?;
619        info.path.pop();
620
621        info.path.push(PathSegment::SelectionSet);
622        let selection_set = field.selection_set.fold_with_ctx(info, ctx, folder)?;
623        info.path.pop();
624
625        let field = Field {
626            alias: field.alias,
627            name: field.name,
628            arguments,
629            directives,
630            selection_set,
631        };
632        folder.leave_field(ctx.ctx, field, info)
633    }
634}
635
636impl<'a> private::FoldNode<'a> for FragmentSpread<'a> {
637    #[inline]
638    fn fold_with_ctx<'b, F: Folder<'a>>(
639        &self,
640        info: &mut VisitInfo,
641        ctx: &'a private::FolderContext<'a>,
642        folder: &'b mut F,
643    ) -> Result<Self> {
644        let spread = &folder.enter_fragment_spread(ctx.ctx, self.clone(), info)?;
645        let spread = if ctx.recurse {
646            let fragment_name = spread.name.name;
647
648            // Check if fragment was already visited (short borrow, released before recursion)
649            let already_visited = {
650                let borrowed = ctx.fragment_names.borrow();
651                borrowed.get(fragment_name).copied()
652            };
653
654            let fragment_name = if let Some(fragment_name) = already_visited {
655                fragment_name
656            } else {
657                // Get the fragment definition (short borrow of fragments)
658                let fragment_and_index = ctx
659                    .fragments
660                    .borrow()
661                    .get(spread.name.name)
662                    .map(|f| (f.fragment.clone(), f.index));
663
664                if let Some((fragment, index)) = fragment_and_index {
665                    let path = info.path.clone();
666                    info.path = Path::default();
667                    info.path.push(PathSegment::Index(index.to_owned()));
668                    let fragment = fragment.fold_with_ctx(info, ctx, folder)?;
669                    info.path = path;
670
671                    let fragment_name = fragment.name.name;
672                    // TODO: was prepend before
673                    ctx.definitions
674                        .borrow_mut()
675                        .push(Definition::Fragment(fragment));
676                    ctx.fragment_names
677                        .borrow_mut()
678                        .insert(fragment_name, fragment_name);
679                    fragment_name
680                } else {
681                    return Err(Error::new(
682                        format!("The fragment '{}' does not exist", fragment_name),
683                        None,
684                    ));
685                }
686            };
687
688            info.path.push(PathSegment::Directives);
689            let directives = spread.directives.fold_with_ctx(info, ctx, folder)?;
690            info.path.pop();
691
692            FragmentSpread {
693                name: fragment_name.into(),
694                directives,
695            }
696        } else {
697            info.path.push(PathSegment::Name);
698            let name = spread.name.fold_with_ctx(info, ctx, folder)?;
699            info.path.pop();
700
701            info.path.push(PathSegment::Directives);
702            let directives = spread.directives.fold_with_ctx(info, ctx, folder)?;
703            info.path.pop();
704
705            FragmentSpread { name, directives }
706        };
707        folder.leave_fragment_spread(ctx.ctx, spread, info)
708    }
709}
710
711impl<'a> private::FoldNode<'a> for InlineFragment<'a> {
712    #[inline]
713    fn fold_with_ctx<'b, F: Folder<'a>>(
714        &self,
715        info: &mut VisitInfo,
716        ctx: &'a private::FolderContext<'a>,
717        folder: &'b mut F,
718    ) -> Result<Self> {
719        let fragment = folder.enter_inline_fragment(ctx.ctx, self.clone(), info)?;
720
721        let type_condition = match fragment.type_condition {
722            Some(condition) => {
723                info.path.push(PathSegment::Name);
724                let folded = condition.fold_with_ctx(info, ctx, folder)?;
725                info.path.pop();
726                Some(folded)
727            }
728            None => None,
729        };
730
731        info.path.push(PathSegment::Directives);
732        let directives = fragment.directives.fold_with_ctx(info, ctx, folder)?;
733        info.path.pop();
734
735        info.path.push(PathSegment::SelectionSet);
736        let selection_set = fragment.selection_set.fold_with_ctx(info, ctx, folder)?;
737        info.path.pop();
738
739        let fragment = InlineFragment {
740            type_condition,
741            directives,
742            selection_set,
743        };
744        folder.leave_inline_fragment(ctx.ctx, fragment, info)
745    }
746}
747
748impl<'a> private::FoldNode<'a> for SelectionSet<'a> {
749    #[inline]
750    fn fold_with_ctx<'b, F: Folder<'a>>(
751        &self,
752        info: &mut VisitInfo,
753        ctx: &'a private::FolderContext<'a>,
754        folder: &'b mut F,
755    ) -> Result<Self> {
756        let new_selections_iter = folder
757            .selection_set(ctx.ctx, self.clone(), info)?
758            .into_iter()
759            .enumerate()
760            .map(|(index, selection)| -> Result<Selection> {
761                info.path.push(PathSegment::Index(index));
762                let folded = match selection {
763                    Selection::Field(field) => Ok(field.fold_with_ctx(info, ctx, folder)?.into()),
764                    Selection::FragmentSpread(spread) => {
765                        Ok(spread.fold_with_ctx(info, ctx, folder)?.into())
766                    }
767                    Selection::InlineFragment(fragment) => {
768                        Ok(fragment.fold_with_ctx(info, ctx, folder)?.into())
769                    }
770                };
771                info.path.pop();
772                folded
773            });
774
775        let mut new_selections = Vec::new_in(&ctx.ctx.arena);
776        for item in new_selections_iter {
777            new_selections.push(item?);
778        }
779        Ok(SelectionSet {
780            selections: new_selections,
781        })
782    }
783}
784
785impl<'a> private::FoldNode<'a> for FragmentDefinition<'a> {
786    #[inline]
787    fn fold_with_ctx<'b, F: Folder<'a>>(
788        &self,
789        info: &mut VisitInfo,
790        ctx: &'a private::FolderContext<'a>,
791        folder: &'b mut F,
792    ) -> Result<Self> {
793        let fragment = &folder.enter_fragment(ctx.ctx, self.clone(), info)?;
794
795        info.path.push(PathSegment::Name);
796        let name = fragment.name.fold_with_ctx(info, ctx, folder)?;
797        info.path.pop();
798
799        info.path.push(PathSegment::Type);
800        let type_condition = fragment.type_condition.fold_with_ctx(info, ctx, folder)?;
801        info.path.pop();
802
803        info.path.push(PathSegment::Directives);
804        let directives = fragment.directives.fold_with_ctx(info, ctx, folder)?;
805        info.path.pop();
806
807        info.path.push(PathSegment::SelectionSet);
808        let selection_set = fragment.selection_set.fold_with_ctx(info, ctx, folder)?;
809        info.path.pop();
810
811        let fragment = FragmentDefinition {
812            name,
813            type_condition,
814            directives,
815            selection_set,
816        };
817        folder.leave_fragment(ctx.ctx, fragment, info)
818    }
819}
820
821impl<'a> private::FoldNode<'a> for OperationDefinition<'a> {
822    #[inline]
823    fn fold_with_ctx<'b, F: Folder<'a>>(
824        &self,
825        info: &mut VisitInfo,
826        ctx: &'a private::FolderContext<'a>,
827        folder: &'b mut F,
828    ) -> Result<Self> {
829        let operation = &folder.enter_operation(ctx.ctx, self.clone(), info)?;
830
831        info.path.push(PathSegment::VariableDefinitions);
832        let variable_definitions = operation
833            .variable_definitions
834            .fold_with_ctx(info, ctx, folder)?;
835        info.path.pop();
836
837        info.path.push(PathSegment::Directives);
838        let directives = operation.directives.fold_with_ctx(info, ctx, folder)?;
839        info.path.pop();
840
841        info.path.push(PathSegment::SelectionSet);
842        let selection_set = operation.selection_set.fold_with_ctx(info, ctx, folder)?;
843        info.path.pop();
844
845        let operation = OperationDefinition {
846            operation: operation.operation,
847            name: operation.name,
848            variable_definitions,
849            directives,
850            selection_set,
851        };
852        folder.leave_operation(ctx.ctx, operation, info)
853    }
854}
855
856impl<'a> private::FoldNode<'a> for Document<'a> {
857    #[inline]
858    fn fold_with_ctx<'b, F: Folder<'a>>(
859        &self,
860        info: &mut VisitInfo,
861        ctx: &'a private::FolderContext<'a>,
862        folder: &'b mut F,
863    ) -> Result<Self> {
864        let new_definitions_iter =
865            self.definitions
866                .iter()
867                .enumerate()
868                .map(|(index, selection)| -> Result<Definition> {
869                    info.path.push(PathSegment::Index(index));
870                    let folded = match selection {
871                        Definition::Operation(operation) => {
872                            Ok(operation.fold_with_ctx(info, ctx, folder)?.into())
873                        }
874                        Definition::Fragment(fragment) => {
875                            Ok(fragment.fold_with_ctx(info, ctx, folder)?.into())
876                        }
877                    };
878                    info.path.pop();
879                    folded
880                });
881        let mut new_definitions = Vec::new_in(&ctx.ctx.arena);
882        for item in new_definitions_iter.rev() {
883            new_definitions.push(item?);
884        }
885        Ok(Document {
886            definitions: new_definitions,
887            size_hint: self.size_hint,
888        })
889    }
890}
891
892impl<'a> FoldDocument<'a> for Document<'a> {
893    /// Folds a GraphQL document by a given operation instead. Instead of transforming the given
894    /// document in its entirety `fold_operation` will start at the defined operation instead,
895    /// transforming fragments only as they're referred to via `FragmentSpread` nodes. This will
896    /// create a new document that only refers to and contains the specified `operation`.
897    fn fold_operation<'b, F: Folder<'a>>(
898        &'a self,
899        ctx: &'a ASTContext,
900        operation_name: Option<&'a str>,
901        folder: &'b mut F,
902    ) -> Result<&'a Self> {
903        let (operation, index) = self.operation_with_index(operation_name)?;
904        let folder_ctx = ctx.alloc(private::FolderContext::with_document(ctx, self));
905        let mut info = VisitInfo::default();
906        info.path.push(PathSegment::Index(index));
907        let operation = private::FoldNode::fold_with_ctx(operation, &mut info, folder_ctx, folder)?;
908
909        let mut borrowed_definitions = folder_ctx.definitions.clone().into_inner();
910
911        borrowed_definitions.push(Definition::Operation(operation));
912        Ok(ctx.alloc(Document {
913            definitions: Vec::from_iter_in(borrowed_definitions.into_iter().rev(), &ctx.arena),
914            size_hint: self.size_hint,
915        }))
916    }
917}
918
919#[cfg(test)]
920mod tests {
921    use bumpalo::collections::CollectIn;
922
923    use super::{super::*, FoldNode, Folder, Result};
924    use crate::{
925        ast::*,
926        visit::{folder::FoldDocument, folder_simple::SimpleFolder},
927    };
928
929    #[test]
930    fn kitchen_sink() {
931        #[derive(Default)]
932        struct FoldNoop {}
933        impl<'a> SimpleFolder<'a> for FoldNoop {}
934
935        let ctx = ASTContext::new();
936        let query = include_str!("../../fixture/kitchen_sink.graphql");
937        let ast = Document::parse(&ctx, query).unwrap();
938
939        let output = ast
940            .fold_operation(&ctx, Some("queryName"), &mut FoldNoop::default())
941            .unwrap();
942
943        let actual = output.print();
944        let expected = indoc::indoc! {r#"
945            query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
946              whoever123is: node(id: [123, 456]) {
947                id
948                ... on User @onInlineFragment {
949                  field2 {
950                    id
951                    alias: field1(first: 10, after: $foo) @include(if: $foo) {
952                      id
953                      ...frag @onFragmentSpread
954                    }
955                  }
956                }
957                ... @skip(unless: $foo) {
958                  id
959                }
960                ... {
961                  id
962                }
963              }
964            }
965
966            fragment frag on Friend @onFragmentDefinition {
967              foo(size: $site, bar: 12, obj: {key: "value", block: """
968              block string uses \"""
969              """})
970            }"#};
971
972        assert_eq!(actual, expected);
973    }
974
975    struct InfoFolder {}
976
977    impl<'a> Folder<'a> for InfoFolder {
978        fn enter_fragment_spread(
979            &mut self,
980            _ctx: &'a ASTContext,
981            fragment_spread: FragmentSpread<'a>,
982            info: &VisitInfo,
983        ) -> Result<FragmentSpread<'a>> {
984            // We run this folder on the kitchen sink query which contains
985            // exactly one fragment spread at the following location
986            let expected_path = Path::try_from(
987                "0.selectionSet.0.selectionSet.1.selectionSet.0.selectionSet.1.selectionSet.1",
988            )
989            .unwrap();
990            assert_eq!(info.path, expected_path);
991            Ok(fragment_spread)
992        }
993
994        fn value(
995            &mut self,
996            _ctx: &'a ASTContext,
997            value: Value<'a>,
998            info: &VisitInfo,
999        ) -> Result<Value<'a>> {
1000            if let Value::Object(_) = value {
1001                // We run this folder on the kitchen sink query which contains
1002                // exactly one object value at the following location
1003                let expected_path = Path::try_from("3.selectionSet.0.arguments.2.value").unwrap();
1004                assert_eq!(info.path, expected_path);
1005            }
1006
1007            Ok(value)
1008        }
1009    }
1010
1011    #[test]
1012    fn fold_info_path() {
1013        let ctx = ASTContext::new();
1014        let query = include_str!("../../fixture/kitchen_sink.graphql");
1015        let ast = Document::parse(&ctx, query).unwrap();
1016
1017        let mut folder = InfoFolder {};
1018        let _ = ast.fold(&ctx, &mut folder).unwrap();
1019    }
1020
1021    #[test]
1022    fn fold_operation_info_path() {
1023        let ctx = ASTContext::new();
1024        let query = include_str!("../../fixture/kitchen_sink.graphql");
1025        let ast = Document::parse(&ctx, query).unwrap();
1026
1027        let mut folder = InfoFolder {};
1028        let _ = ast
1029            .fold_operation(&ctx, Some("queryName"), &mut folder)
1030            .unwrap();
1031    }
1032
1033    struct AddTypenames {}
1034
1035    use std::iter;
1036
1037    impl<'arena> Folder<'arena> for AddTypenames {
1038        fn selection_set(
1039            &mut self,
1040            ctx: &'arena ASTContext,
1041            selection_set: SelectionSet<'arena>,
1042            _info: &VisitInfo,
1043        ) -> Result<SelectionSet<'arena>> {
1044            if selection_set.is_empty() {
1045                return Ok(selection_set);
1046            }
1047
1048            let has_typename = selection_set.selections.iter().any(|selection| {
1049                selection
1050                    .field()
1051                    .map(|field| field.name == "__typename" && field.alias.is_none())
1052                    .unwrap_or(false)
1053            });
1054
1055            if !has_typename {
1056                let typename_field = Selection::Field(Field {
1057                    alias: None,
1058                    name: "__typename",
1059                    arguments: Arguments::default_in(&ctx.arena),
1060                    directives: Directives::default_in(&ctx.arena),
1061                    selection_set: SelectionSet::default_in(&ctx.arena),
1062                });
1063
1064                let new_selections =
1065                    selection_set
1066                        .into_iter()
1067                        .chain(iter::once(typename_field))
1068                        .collect_in::<bumpalo::collections::Vec<Selection>>(&ctx.arena);
1069
1070                Ok(SelectionSet {
1071                    selections: new_selections,
1072                })
1073            } else {
1074                Ok(selection_set)
1075            }
1076        }
1077    }
1078
1079    #[test]
1080    fn fold_typename() {
1081        let ctx = ASTContext::new();
1082        let query = "query { todo { id author { id } } }";
1083        let ast = Document::parse(&ctx, query).unwrap();
1084
1085        let mut folder = AddTypenames {};
1086        let new_ast = ast.fold_operation(&ctx, None, &mut folder);
1087        assert_eq!(
1088            new_ast.unwrap().print(),
1089            "{\n  todo {\n    id\n    author {\n      id\n      __typename\n    }\n    __typename\n  }\n  __typename\n}"
1090        );
1091    }
1092}