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            let mut borrowed_fragment_names = ctx.fragment_names.borrow_mut();
648            let fragment_name =
649                if let Some(fragment_name) = borrowed_fragment_names.get(fragment_name) {
650                    fragment_name
651                } else if let Some(FragmentDefinitionWithIndex { fragment, index }) =
652                    ctx.fragments.borrow().get(spread.name.name)
653                {
654                    let path = info.path.clone();
655                    info.path = Path::default();
656                    info.path.push(PathSegment::Index(index.to_owned()));
657                    let fragment = fragment.fold_with_ctx(info, ctx, folder)?;
658                    info.path = path;
659
660                    let fragment_name = fragment.name.name;
661                    // TODO: was prepend before
662                    ctx.definitions
663                        .borrow_mut()
664                        .push(Definition::Fragment(fragment));
665                    borrowed_fragment_names.insert(fragment_name, fragment_name);
666                    fragment_name
667                } else {
668                    return Err(Error::new(
669                        format!("The fragment '{}' does not exist", fragment_name),
670                        None,
671                    ));
672                };
673
674            info.path.push(PathSegment::Directives);
675            let directives = spread.directives.fold_with_ctx(info, ctx, folder)?;
676            info.path.pop();
677
678            FragmentSpread {
679                name: fragment_name.into(),
680                directives,
681            }
682        } else {
683            info.path.push(PathSegment::Name);
684            let name = spread.name.fold_with_ctx(info, ctx, folder)?;
685            info.path.pop();
686
687            info.path.push(PathSegment::Directives);
688            let directives = spread.directives.fold_with_ctx(info, ctx, folder)?;
689            info.path.pop();
690
691            FragmentSpread { name, directives }
692        };
693        folder.leave_fragment_spread(ctx.ctx, spread, info)
694    }
695}
696
697impl<'a> private::FoldNode<'a> for InlineFragment<'a> {
698    #[inline]
699    fn fold_with_ctx<'b, F: Folder<'a>>(
700        &self,
701        info: &mut VisitInfo,
702        ctx: &'a private::FolderContext<'a>,
703        folder: &'b mut F,
704    ) -> Result<Self> {
705        let fragment = folder.enter_inline_fragment(ctx.ctx, self.clone(), info)?;
706
707        let type_condition = match fragment.type_condition {
708            Some(condition) => {
709                info.path.push(PathSegment::Name);
710                let folded = condition.fold_with_ctx(info, ctx, folder)?;
711                info.path.pop();
712                Some(folded)
713            }
714            None => None,
715        };
716
717        info.path.push(PathSegment::Directives);
718        let directives = fragment.directives.fold_with_ctx(info, ctx, folder)?;
719        info.path.pop();
720
721        info.path.push(PathSegment::SelectionSet);
722        let selection_set = fragment.selection_set.fold_with_ctx(info, ctx, folder)?;
723        info.path.pop();
724
725        let fragment = InlineFragment {
726            type_condition,
727            directives,
728            selection_set,
729        };
730        folder.leave_inline_fragment(ctx.ctx, fragment, info)
731    }
732}
733
734impl<'a> private::FoldNode<'a> for SelectionSet<'a> {
735    #[inline]
736    fn fold_with_ctx<'b, F: Folder<'a>>(
737        &self,
738        info: &mut VisitInfo,
739        ctx: &'a private::FolderContext<'a>,
740        folder: &'b mut F,
741    ) -> Result<Self> {
742        let new_selections_iter = folder
743            .selection_set(ctx.ctx, self.clone(), info)?
744            .into_iter()
745            .enumerate()
746            .map(|(index, selection)| -> Result<Selection> {
747                info.path.push(PathSegment::Index(index));
748                let folded = match selection {
749                    Selection::Field(field) => Ok(field.fold_with_ctx(info, ctx, folder)?.into()),
750                    Selection::FragmentSpread(spread) => {
751                        Ok(spread.fold_with_ctx(info, ctx, folder)?.into())
752                    }
753                    Selection::InlineFragment(fragment) => {
754                        Ok(fragment.fold_with_ctx(info, ctx, folder)?.into())
755                    }
756                };
757                info.path.pop();
758                folded
759            });
760
761        let mut new_selections = Vec::new_in(&ctx.ctx.arena);
762        for item in new_selections_iter {
763            new_selections.push(item?);
764        }
765        Ok(SelectionSet {
766            selections: new_selections,
767        })
768    }
769}
770
771impl<'a> private::FoldNode<'a> for FragmentDefinition<'a> {
772    #[inline]
773    fn fold_with_ctx<'b, F: Folder<'a>>(
774        &self,
775        info: &mut VisitInfo,
776        ctx: &'a private::FolderContext<'a>,
777        folder: &'b mut F,
778    ) -> Result<Self> {
779        let fragment = &folder.enter_fragment(ctx.ctx, self.clone(), info)?;
780
781        info.path.push(PathSegment::Name);
782        let name = fragment.name.fold_with_ctx(info, ctx, folder)?;
783        info.path.pop();
784
785        info.path.push(PathSegment::Type);
786        let type_condition = fragment.type_condition.fold_with_ctx(info, ctx, folder)?;
787        info.path.pop();
788
789        info.path.push(PathSegment::Directives);
790        let directives = fragment.directives.fold_with_ctx(info, ctx, folder)?;
791        info.path.pop();
792
793        info.path.push(PathSegment::SelectionSet);
794        let selection_set = fragment.selection_set.fold_with_ctx(info, ctx, folder)?;
795        info.path.pop();
796
797        let fragment = FragmentDefinition {
798            name,
799            type_condition,
800            directives,
801            selection_set,
802        };
803        folder.leave_fragment(ctx.ctx, fragment, info)
804    }
805}
806
807impl<'a> private::FoldNode<'a> for OperationDefinition<'a> {
808    #[inline]
809    fn fold_with_ctx<'b, F: Folder<'a>>(
810        &self,
811        info: &mut VisitInfo,
812        ctx: &'a private::FolderContext<'a>,
813        folder: &'b mut F,
814    ) -> Result<Self> {
815        let operation = &folder.enter_operation(ctx.ctx, self.clone(), info)?;
816
817        info.path.push(PathSegment::VariableDefinitions);
818        let variable_definitions = operation
819            .variable_definitions
820            .fold_with_ctx(info, ctx, folder)?;
821        info.path.pop();
822
823        info.path.push(PathSegment::Directives);
824        let directives = operation.directives.fold_with_ctx(info, ctx, folder)?;
825        info.path.pop();
826
827        info.path.push(PathSegment::SelectionSet);
828        let selection_set = operation.selection_set.fold_with_ctx(info, ctx, folder)?;
829        info.path.pop();
830
831        let operation = OperationDefinition {
832            operation: operation.operation,
833            name: operation.name,
834            variable_definitions,
835            directives,
836            selection_set,
837        };
838        folder.leave_operation(ctx.ctx, operation, info)
839    }
840}
841
842impl<'a> private::FoldNode<'a> for Document<'a> {
843    #[inline]
844    fn fold_with_ctx<'b, F: Folder<'a>>(
845        &self,
846        info: &mut VisitInfo,
847        ctx: &'a private::FolderContext<'a>,
848        folder: &'b mut F,
849    ) -> Result<Self> {
850        let new_definitions_iter =
851            self.definitions
852                .iter()
853                .enumerate()
854                .map(|(index, selection)| -> Result<Definition> {
855                    info.path.push(PathSegment::Index(index));
856                    let folded = match selection {
857                        Definition::Operation(operation) => {
858                            Ok(operation.fold_with_ctx(info, ctx, folder)?.into())
859                        }
860                        Definition::Fragment(fragment) => {
861                            Ok(fragment.fold_with_ctx(info, ctx, folder)?.into())
862                        }
863                    };
864                    info.path.pop();
865                    folded
866                });
867        let mut new_definitions = Vec::new_in(&ctx.ctx.arena);
868        for item in new_definitions_iter.rev() {
869            new_definitions.push(item?);
870        }
871        Ok(Document {
872            definitions: new_definitions,
873            size_hint: self.size_hint,
874        })
875    }
876}
877
878impl<'a> FoldDocument<'a> for Document<'a> {
879    /// Folds a GraphQL document by a given operation instead. Instead of transforming the given
880    /// document in its entirety `fold_operation` will start at the defined operation instead,
881    /// transforming fragments only as they're referred to via `FragmentSpread` nodes. This will
882    /// create a new document that only refers to and contains the specified `operation`.
883    fn fold_operation<'b, F: Folder<'a>>(
884        &'a self,
885        ctx: &'a ASTContext,
886        operation_name: Option<&'a str>,
887        folder: &'b mut F,
888    ) -> Result<&'a Self> {
889        let (operation, index) = self.operation_with_index(operation_name)?;
890        let folder_ctx = ctx.alloc(private::FolderContext::with_document(ctx, self));
891        let mut info = VisitInfo::default();
892        info.path.push(PathSegment::Index(index));
893        let operation = private::FoldNode::fold_with_ctx(operation, &mut info, folder_ctx, folder)?;
894
895        let mut borrowed_definitions = folder_ctx.definitions.clone().into_inner();
896
897        borrowed_definitions.push(Definition::Operation(operation));
898        Ok(ctx.alloc(Document {
899            definitions: Vec::from_iter_in(borrowed_definitions.into_iter().rev(), &ctx.arena),
900            size_hint: self.size_hint,
901        }))
902    }
903}
904
905#[cfg(test)]
906mod tests {
907    use bumpalo::collections::CollectIn;
908
909    use super::{super::*, FoldNode, Folder, Result};
910    use crate::{
911        ast::*,
912        visit::{folder::FoldDocument, folder_simple::SimpleFolder},
913    };
914
915    #[test]
916    fn kitchen_sink() {
917        #[derive(Default)]
918        struct FoldNoop {}
919        impl<'a> SimpleFolder<'a> for FoldNoop {}
920
921        let ctx = ASTContext::new();
922        let query = include_str!("../../fixture/kitchen_sink.graphql");
923        let ast = Document::parse(&ctx, query).unwrap();
924
925        let output = ast
926            .fold_operation(&ctx, Some("queryName"), &mut FoldNoop::default())
927            .unwrap();
928
929        let actual = output.print();
930        let expected = indoc::indoc! {r#"
931            query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
932              whoever123is: node(id: [123, 456]) {
933                id
934                ... on User @onInlineFragment {
935                  field2 {
936                    id
937                    alias: field1(first: 10, after: $foo) @include(if: $foo) {
938                      id
939                      ...frag @onFragmentSpread
940                    }
941                  }
942                }
943                ... @skip(unless: $foo) {
944                  id
945                }
946                ... {
947                  id
948                }
949              }
950            }
951
952            fragment frag on Friend @onFragmentDefinition {
953              foo(size: $site, bar: 12, obj: {key: "value", block: """
954              block string uses \"""
955              """})
956            }"#};
957
958        assert_eq!(actual, expected);
959    }
960
961    struct InfoFolder {}
962
963    impl<'a> Folder<'a> for InfoFolder {
964        fn enter_fragment_spread(
965            &mut self,
966            _ctx: &'a ASTContext,
967            fragment_spread: FragmentSpread<'a>,
968            info: &VisitInfo,
969        ) -> Result<FragmentSpread<'a>> {
970            // We run this folder on the kitchen sink query which contains
971            // exactly one fragment spread at the following location
972            let expected_path = Path::try_from(
973                "0.selectionSet.0.selectionSet.1.selectionSet.0.selectionSet.1.selectionSet.1",
974            )
975            .unwrap();
976            assert_eq!(info.path, expected_path);
977            Ok(fragment_spread)
978        }
979
980        fn value(
981            &mut self,
982            _ctx: &'a ASTContext,
983            value: Value<'a>,
984            info: &VisitInfo,
985        ) -> Result<Value<'a>> {
986            if let Value::Object(_) = value {
987                // We run this folder on the kitchen sink query which contains
988                // exactly one object value at the following location
989                let expected_path = Path::try_from("3.selectionSet.0.arguments.2.value").unwrap();
990                assert_eq!(info.path, expected_path);
991            }
992
993            Ok(value)
994        }
995    }
996
997    #[test]
998    fn fold_info_path() {
999        let ctx = ASTContext::new();
1000        let query = include_str!("../../fixture/kitchen_sink.graphql");
1001        let ast = Document::parse(&ctx, query).unwrap();
1002
1003        let mut folder = InfoFolder {};
1004        let _ = ast.fold(&ctx, &mut folder).unwrap();
1005    }
1006
1007    #[test]
1008    fn fold_operation_info_path() {
1009        let ctx = ASTContext::new();
1010        let query = include_str!("../../fixture/kitchen_sink.graphql");
1011        let ast = Document::parse(&ctx, query).unwrap();
1012
1013        let mut folder = InfoFolder {};
1014        let _ = ast
1015            .fold_operation(&ctx, Some("queryName"), &mut folder)
1016            .unwrap();
1017    }
1018
1019    struct AddTypenames {}
1020
1021    use std::iter;
1022
1023    impl<'arena> Folder<'arena> for AddTypenames {
1024        fn selection_set(
1025            &mut self,
1026            ctx: &'arena ASTContext,
1027            selection_set: SelectionSet<'arena>,
1028            _info: &VisitInfo,
1029        ) -> Result<SelectionSet<'arena>> {
1030            if selection_set.is_empty() {
1031                return Ok(selection_set);
1032            }
1033    
1034            let has_typename = selection_set.selections.iter().any(|selection| {
1035                selection
1036                    .field()
1037                    .map(|field| field.name == "__typename" && field.alias.is_none())
1038                    .unwrap_or(false)
1039            });
1040    
1041            if !has_typename {
1042                let typename_field = Selection::Field(Field {
1043                    alias: None,
1044                    name: "__typename",
1045                    arguments: Arguments::default_in(&ctx.arena),
1046                    directives: Directives::default_in(&ctx.arena),
1047                    selection_set: SelectionSet::default_in(&ctx.arena),
1048                });
1049            
1050              let new_selections = selection_set
1051                .into_iter()
1052                .chain(iter::once(typename_field))
1053                .collect_in::<bumpalo::collections::Vec<Selection>>(&ctx.arena);
1054    
1055                Ok(SelectionSet { selections: new_selections })
1056            } else {
1057                Ok(selection_set)
1058            }
1059        }
1060    }
1061
1062    #[test]
1063    fn fold_typename() {
1064        let ctx = ASTContext::new();
1065        let query = "query { todo { id author { id } } }";
1066        let ast = Document::parse(&ctx, query).unwrap();
1067
1068        let mut folder = AddTypenames {};
1069        let new_ast = ast
1070            .fold_operation(&ctx, None, &mut folder);
1071        assert_eq!(
1072            new_ast.unwrap().print(),
1073            "{\n  todo {\n    id\n    author {\n      id\n      __typename\n    }\n    __typename\n  }\n  __typename\n}"
1074        );
1075    }
1076}