apollo_federation/query_plan/
display.rs

1use std::fmt;
2
3use apollo_compiler::executable;
4
5use super::*;
6use crate::display_helpers::State;
7use crate::display_helpers::write_indented_lines;
8
9impl QueryPlan {
10    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
11        let Self {
12            node,
13            statistics: _,
14        } = self;
15        state.write("QueryPlan {")?;
16        if let Some(node) = node {
17            state.indent()?;
18            node.write_indented(state)?;
19            state.dedent()?;
20        }
21        state.write("}")
22    }
23}
24
25impl TopLevelPlanNode {
26    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
27        match self {
28            Self::Subscription(node) => node.write_indented(state),
29            Self::Fetch(node) => node.write_indented(state),
30            Self::Sequence(node) => node.write_indented(state),
31            Self::Parallel(node) => node.write_indented(state),
32            Self::Flatten(node) => node.write_indented(state),
33            Self::Defer(node) => node.write_indented(state),
34            Self::Condition(node) => node.write_indented(state),
35        }
36    }
37}
38
39impl PlanNode {
40    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
41        match self {
42            Self::Fetch(node) => node.write_indented(state),
43            Self::Sequence(node) => node.write_indented(state),
44            Self::Parallel(node) => node.write_indented(state),
45            Self::Flatten(node) => node.write_indented(state),
46            Self::Defer(node) => node.write_indented(state),
47            Self::Condition(node) => node.write_indented(state),
48        }
49    }
50}
51
52impl SubscriptionNode {
53    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
54        let Self { primary, rest } = self;
55        state.write("Subscription {")?;
56        state.indent()?;
57
58        state.write("Primary: {")?;
59        state.indent()?;
60        primary.write_indented(state)?;
61        state.dedent()?;
62        state.write("},")?;
63
64        if let Some(rest) = rest {
65            state.new_line()?;
66            state.write("Rest: {")?;
67            state.indent()?;
68            rest.write_indented(state)?;
69            state.dedent()?;
70            state.write("},")?;
71        }
72
73        state.dedent()?;
74        state.write("},")
75    }
76}
77
78impl FetchNode {
79    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
80        let Self {
81            subgraph_name,
82            id,
83            variable_usages: _,
84            requires,
85            operation_document,
86            operation_name: _,
87            operation_kind: _,
88            input_rewrites: _,
89            output_rewrites: _,
90            context_rewrites: _,
91        } = self;
92        state.write(format_args!("Fetch(service: {subgraph_name:?}"))?;
93        if let Some(id) = id {
94            state.write(format_args!(", id: {id:?}"))?;
95        }
96        state.write(") {")?;
97        state.indent()?;
98
99        if !requires.is_empty() {
100            write_requires_selections(state, requires)?;
101            state.write(" =>")?;
102            state.new_line()?;
103        }
104        write_operation(
105            state,
106            operation_document.as_parsed().map_err(|_| fmt::Error)?,
107        )?;
108
109        state.dedent()?;
110        state.write("},")
111    }
112}
113
114impl SequenceNode {
115    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
116        let Self { nodes } = self;
117        state.write("Sequence {")?;
118
119        write_indented_lines(state, nodes, |state, node| node.write_indented(state))?;
120
121        state.write("},")
122    }
123}
124
125impl ParallelNode {
126    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
127        let Self { nodes } = self;
128        state.write("Parallel {")?;
129
130        write_indented_lines(state, nodes, |state, node| node.write_indented(state))?;
131
132        state.write("},")
133    }
134}
135
136impl FlattenNode {
137    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
138        let Self { path, node } = self;
139        state.write("Flatten(path: \"")?;
140        if let Some((first, rest)) = path.split_first() {
141            state.write(first)?;
142            for element in rest {
143                state.write(".")?;
144                state.write(element)?;
145            }
146        }
147        state.write("\") {")?;
148        state.indent()?;
149
150        node.write_indented(state)?;
151
152        state.dedent()?;
153        state.write("},")
154    }
155}
156
157impl ConditionNode {
158    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
159        let Self {
160            condition_variable,
161            if_clause,
162            else_clause,
163        } = self;
164        match (if_clause, else_clause) {
165            (Some(if_clause), Some(else_clause)) => {
166                state.write(format_args!("Condition(if: ${condition_variable}) {{"))?;
167                state.indent()?;
168
169                state.write("Then {")?;
170                state.indent()?;
171                if_clause.write_indented(state)?;
172                state.dedent()?;
173                state.write("}")?;
174
175                state.write(" Else {")?;
176                state.indent()?;
177                else_clause.write_indented(state)?;
178                state.dedent()?;
179                state.write("},")?;
180
181                state.dedent()?;
182                state.write("},")
183            }
184
185            (Some(if_clause), None) => {
186                state.write(format_args!("Include(if: ${condition_variable}) {{"))?;
187                state.indent()?;
188
189                if_clause.write_indented(state)?;
190
191                state.dedent()?;
192                state.write("},")
193            }
194
195            (None, Some(else_clause)) => {
196                state.write(format_args!("Skip(if: ${condition_variable}) {{"))?;
197                state.indent()?;
198
199                else_clause.write_indented(state)?;
200
201                state.dedent()?;
202                state.write("},")
203            }
204
205            // Shouldn’t happen?
206            (None, None) => state.write("Condition {}"),
207        }
208    }
209}
210
211impl DeferNode {
212    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
213        let Self { primary, deferred } = self;
214        state.write("Defer {")?;
215        state.indent()?;
216
217        primary.write_indented(state)?;
218        if !deferred.is_empty() {
219            state.write(" [")?;
220            write_indented_lines(state, deferred, |state, deferred| {
221                deferred.write_indented(state)
222            })?;
223            state.write("]")?;
224        }
225
226        state.dedent()?;
227        state.write("},")
228    }
229}
230
231impl PrimaryDeferBlock {
232    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
233        let Self {
234            sub_selection,
235            node,
236        } = self;
237        state.write("Primary {")?;
238        if sub_selection.is_some() || node.is_some() {
239            if let Some(sub_selection) = sub_selection {
240                state.indent()?;
241                state.write(sub_selection)?;
242                if node.is_some() {
243                    state.write(":")?;
244                    state.new_line()?;
245                }
246            } else {
247                // Indent to match the Some() case
248                state.indent()?;
249            }
250
251            if let Some(node) = node {
252                node.write_indented(state)?;
253            }
254
255            state.dedent()?;
256        }
257        state.write("},")
258    }
259}
260
261impl DeferredDeferBlock {
262    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
263        let Self {
264            depends,
265            label,
266            query_path,
267            sub_selection,
268            node,
269        } = self;
270
271        state.write("Deferred(depends: [")?;
272        if let Some((DeferredDependency { id }, rest)) = depends.split_first() {
273            state.write(id)?;
274            for DeferredDependency { id } in rest {
275                state.write(", ")?;
276                state.write(id)?;
277            }
278        }
279        state.write("], path: \"")?;
280        if let Some((first, rest)) = query_path.split_first() {
281            state.write(first)?;
282            for element in rest {
283                state.write("/")?;
284                state.write(element)?;
285            }
286        }
287        state.write("\"")?;
288        if let Some(label) = label {
289            state.write_fmt(format_args!(r#", label: "{label}""#))?;
290        }
291        state.write(") {")?;
292
293        if sub_selection.is_some() || node.is_some() {
294            state.indent()?;
295
296            if let Some(sub_selection) = sub_selection {
297                state.write(sub_selection)?;
298                state.write(":")?;
299            }
300            if sub_selection.is_some() && node.is_some() {
301                state.new_line()?;
302            }
303            if let Some(node) = node {
304                node.write_indented(state)?;
305            }
306
307            state.dedent()?;
308        }
309
310        state.write("},")
311    }
312}
313
314/// When we serialize a query plan, we want to serialize the operation
315/// but not show the root level `query` definition or the `_entities` call.
316/// This function flattens those nodes to only show their selection sets
317fn write_operation(
318    state: &mut State<'_, '_>,
319    operation_document: &ExecutableDocument,
320) -> fmt::Result {
321    let operation = operation_document
322        .operations
323        .get(None)
324        .expect("expected a single-operation document");
325    write_selections(state, &operation.selection_set.selections)?;
326    for fragment in operation_document.fragments.values() {
327        state.write("\n\n")?; // new line without indentation (since `fragment` adds indentation)
328        state.write(
329            fragment
330                .serialize()
331                .initial_indent_level(state.indent_level()),
332        )?
333    }
334    Ok(())
335}
336
337fn write_selections(
338    state: &mut State<'_, '_>,
339    mut selections: &[executable::Selection],
340) -> fmt::Result {
341    if let Some(executable::Selection::Field(field)) = selections.first() {
342        if field.name == "_entities" {
343            selections = &field.selection_set.selections
344        }
345    }
346    state.write("{")?;
347
348    // Manually indent and write the newline
349    // to prevent a duplicate indent from `.new_line()` and `.initial_indent_level()`.
350    state.indent_no_new_line();
351    for sel in selections {
352        state.write("\n")?;
353        state.write(sel.serialize().initial_indent_level(state.indent_level()))?;
354    }
355    state.dedent()?;
356
357    state.write("}")
358}
359
360fn write_requires_selections(
361    state: &mut State<'_, '_>,
362    selections: &[requires_selection::Selection],
363) -> fmt::Result {
364    state.write("{")?;
365
366    // Manually indent and write the newline
367    // to prevent a duplicate indent from `.new_line()` and `.initial_indent_level()`.
368    state.indent()?;
369    if let Some((first, rest)) = selections.split_first() {
370        write_requires_selection(state, first)?;
371        for sel in rest {
372            state.new_line()?;
373            write_requires_selection(state, sel)?;
374        }
375    }
376    state.dedent()?;
377
378    state.write("}")
379}
380
381fn write_requires_selection(
382    state: &mut State<'_, '_>,
383    selection: &requires_selection::Selection,
384) -> fmt::Result {
385    match selection {
386        requires_selection::Selection::Field(requires_selection::Field {
387            alias,
388            name,
389            selections,
390        }) => {
391            if let Some(alias) = alias {
392                state.write(alias)?;
393                state.write(": ")?;
394            }
395            state.write(name)?;
396            if !selections.is_empty() {
397                state.write(" ")?;
398                write_requires_selections(state, selections)?;
399            }
400        }
401        requires_selection::Selection::InlineFragment(requires_selection::InlineFragment {
402            type_condition,
403            selections,
404        }) => {
405            if let Some(type_name) = type_condition {
406                state.write("... on ")?;
407                state.write(type_name)?;
408                state.write(" ")?;
409            } else {
410                state.write("... ")?;
411            }
412            write_requires_selections(state, selections)?;
413        }
414    }
415    Ok(())
416}
417
418impl fmt::Display for requires_selection::Selection {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        write_requires_selection(&mut State::new(f), self)
421    }
422}
423
424/// PORT_NOTE: Corresponds to `GroupPath.updatedResponsePath` in `buildPlan.ts`
425impl fmt::Display for FetchDataPathElement {
426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427        match self {
428            Self::Key(name, conditions) => {
429                f.write_str(name)?;
430                write_conditions(conditions, f)
431            }
432            Self::AnyIndex(conditions) => {
433                f.write_str("@")?;
434                write_conditions(conditions, f)
435            }
436            Self::TypenameEquals(name) => write!(f, "... on {name}"),
437            Self::Parent => write!(f, ".."),
438        }
439    }
440}
441
442fn write_conditions(conditions: &Option<Vec<Name>>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443    if let Some(conditions) = conditions {
444        write!(f, "|[{}]", conditions.join(","))
445    } else {
446        Ok(())
447    }
448}
449
450impl fmt::Display for QueryPathElement {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        match self {
453            Self::Field { response_key } => f.write_str(response_key),
454            Self::InlineFragment { type_condition } => write!(f, "... on {type_condition}"),
455        }
456    }
457}
458
459macro_rules! impl_display {
460    ($( $Ty: ty )+) => {
461        $(
462            impl fmt::Display for $Ty {
463                fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
464                    self.write_indented(&mut State::new(output))
465                }
466            }
467        )+
468    };
469}
470
471impl_display! {
472    QueryPlan
473    TopLevelPlanNode
474    PlanNode
475    SubscriptionNode
476    FetchNode
477    SequenceNode
478    ParallelNode
479    FlattenNode
480    ConditionNode
481    DeferNode
482    PrimaryDeferBlock
483    DeferredDeferBlock
484}