Skip to main content

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 let Some(v) = requires.as_ref() {
100            if !v.is_empty() {
101                write_selections(state, v)?;
102                state.write(" =>")?;
103                state.new_line()?;
104            }
105        }
106        write_operation(state, operation_document)?;
107
108        state.dedent()?;
109        state.write("},")
110    }
111}
112
113impl SequenceNode {
114    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
115        let Self { nodes } = self;
116        state.write("Sequence {")?;
117
118        write_indented_lines(state, nodes, |state, node| node.write_indented(state))?;
119
120        state.write("},")
121    }
122}
123
124impl ParallelNode {
125    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
126        let Self { nodes } = self;
127        state.write("Parallel {")?;
128
129        write_indented_lines(state, nodes, |state, node| node.write_indented(state))?;
130
131        state.write("},")
132    }
133}
134
135impl FlattenNode {
136    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
137        let Self { path, node } = self;
138        state.write("Flatten(path: \"")?;
139        if let Some((first, rest)) = path.split_first() {
140            state.write(first)?;
141            for element in rest {
142                state.write(".")?;
143                state.write(element)?;
144            }
145        }
146        state.write("\") {")?;
147        state.indent()?;
148
149        node.write_indented(state)?;
150
151        state.dedent()?;
152        state.write("},")
153    }
154}
155
156impl ConditionNode {
157    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
158        let Self {
159            condition_variable,
160            if_clause,
161            else_clause,
162        } = self;
163        match (if_clause, else_clause) {
164            (Some(if_clause), Some(else_clause)) => {
165                state.write(format_args!("Condition(if: ${condition_variable}) {{"))?;
166                state.indent()?;
167
168                state.write("Then {")?;
169                state.indent()?;
170                if_clause.write_indented(state)?;
171                state.dedent()?;
172                state.write("}")?;
173
174                state.write(" Else {")?;
175                state.indent()?;
176                else_clause.write_indented(state)?;
177                state.dedent()?;
178                state.write("},")?;
179
180                state.dedent()?;
181                state.write("},")
182            }
183
184            (Some(if_clause), None) => {
185                state.write(format_args!("Include(if: ${condition_variable}) {{"))?;
186                state.indent()?;
187
188                if_clause.write_indented(state)?;
189
190                state.dedent()?;
191                state.write("},")
192            }
193
194            (None, Some(else_clause)) => {
195                state.write(format_args!("Skip(if: ${condition_variable}) {{"))?;
196                state.indent()?;
197
198                else_clause.write_indented(state)?;
199
200                state.dedent()?;
201                state.write("},")
202            }
203
204            // Shouldn’t happen?
205            (None, None) => state.write("Condition {}"),
206        }
207    }
208}
209
210impl DeferNode {
211    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
212        let Self { primary, deferred } = self;
213        state.write("Defer {")?;
214        state.indent()?;
215
216        primary.write_indented(state)?;
217        if !deferred.is_empty() {
218            state.write(" [")?;
219            write_indented_lines(state, deferred, |state, deferred| {
220                deferred.write_indented(state)
221            })?;
222            state.write("]")?;
223        }
224
225        state.dedent()?;
226        state.write("},")
227    }
228}
229
230impl PrimaryDeferBlock {
231    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
232        let Self {
233            sub_selection,
234            node,
235        } = self;
236        state.write("Primary {")?;
237        if sub_selection.is_some() || node.is_some() {
238            if let Some(sub_selection) = sub_selection {
239                // Manually indent and write the newline
240                // to prevent a duplicate indent from `.new_line()` and `.initial_indent_level()`.
241                state.indent_no_new_line();
242                state.write("\n")?;
243
244                state.write(
245                    sub_selection
246                        .serialize()
247                        .initial_indent_level(state.indent_level()),
248                )?;
249                if node.is_some() {
250                    state.write(":")?;
251                    state.new_line()?;
252                }
253            } else {
254                // Indent to match the Some() case
255                state.indent()?;
256            }
257
258            if let Some(node) = node {
259                node.write_indented(state)?;
260            }
261
262            state.dedent()?;
263        }
264        state.write("},")
265    }
266}
267
268impl DeferredDeferBlock {
269    fn write_indented(&self, state: &mut State<'_, '_>) -> fmt::Result {
270        let Self {
271            depends,
272            label,
273            query_path,
274            sub_selection,
275            node,
276        } = self;
277
278        state.write("Deferred(depends: [")?;
279        if let Some((DeferredDependency { id }, rest)) = depends.split_first() {
280            state.write(id)?;
281            for DeferredDependency { id } in rest {
282                state.write(", ")?;
283                state.write(id)?;
284            }
285        }
286        state.write("], path: \"")?;
287        if let Some((first, rest)) = query_path.split_first() {
288            state.write(first)?;
289            for element in rest {
290                state.write("/")?;
291                state.write(element)?;
292            }
293        }
294        state.write("\"")?;
295        if let Some(label) = label {
296            state.write_fmt(format_args!(r#", label: "{label}""#))?;
297        }
298        state.write(") {")?;
299
300        if sub_selection.is_some() || node.is_some() {
301            state.indent()?;
302
303            if let Some(sub_selection) = sub_selection {
304                write_selections(state, &sub_selection.selections)?;
305                state.write(":")?;
306            }
307            if sub_selection.is_some() && node.is_some() {
308                state.new_line()?;
309            }
310            if let Some(node) = node {
311                node.write_indented(state)?;
312            }
313
314            state.dedent()?;
315        }
316
317        state.write("},")
318    }
319}
320
321/// When we serialize a query plan, we want to serialize the operation
322/// but not show the root level `query` definition or the `_entities` call.
323/// This function flattens those nodes to only show their selection sets
324fn write_operation(
325    state: &mut State<'_, '_>,
326    operation_document: &ExecutableDocument,
327) -> fmt::Result {
328    let operation = operation_document
329        .operations
330        .get(None)
331        .expect("expected a single-operation document");
332    write_selections(state, &operation.selection_set.selections)?;
333    for fragment in operation_document.fragments.values() {
334        state.write("\n\n")?; // new line without indentation (since `fragment` adds indentation)
335        state.write(
336            fragment
337                .serialize()
338                .initial_indent_level(state.indent_level()),
339        )?
340    }
341    Ok(())
342}
343
344fn write_selections(
345    state: &mut State<'_, '_>,
346    mut selections: &[executable::Selection],
347) -> fmt::Result {
348    if let Some(executable::Selection::Field(field)) = selections.first() {
349        if field.name == "_entities" {
350            selections = &field.selection_set.selections
351        }
352    }
353    state.write("{")?;
354
355    // Manually indent and write the newline
356    // to prevent a duplicate indent from `.new_line()` and `.initial_indent_level()`.
357    state.indent_no_new_line();
358    for sel in selections {
359        state.write("\n")?;
360        state.write(sel.serialize().initial_indent_level(state.indent_level()))?;
361    }
362    state.dedent()?;
363
364    state.write("}")
365}
366
367/// PORT_NOTE: Corresponds to `GroupPath.updatedResponsePath` in `buildPlan.ts`
368impl fmt::Display for FetchDataPathElement {
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        match self {
371            Self::Key(name, conditions) => {
372                f.write_str(name)?;
373                write_conditions(conditions, f)
374            }
375            Self::AnyIndex(conditions) => {
376                f.write_str("@")?;
377                write_conditions(conditions, f)
378            }
379            Self::TypenameEquals(name) => write!(f, "... on {name}"),
380            Self::Parent => write!(f, ".."),
381        }
382    }
383}
384
385fn write_conditions(conditions: &Option<Vec<Name>>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386    if let Some(conditions) = conditions {
387        write!(f, "|[{}]", conditions.join(","))
388    } else {
389        Ok(())
390    }
391}
392
393impl fmt::Display for QueryPathElement {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        match self {
396            Self::Field(field) => f.write_str(field.response_key()),
397            Self::InlineFragment(inline) => {
398                if let Some(cond) = &inline.type_condition {
399                    write!(f, "... on {cond}")
400                } else {
401                    Ok(())
402                }
403            }
404        }
405    }
406}
407
408macro_rules! impl_display {
409    ($( $Ty: ty )+) => {
410        $(
411            impl fmt::Display for $Ty {
412                fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
413                    self.write_indented(&mut State::new(output))
414                }
415            }
416        )+
417    };
418}
419
420impl_display! {
421    QueryPlan
422    TopLevelPlanNode
423    PlanNode
424    SubscriptionNode
425    FetchNode
426    SequenceNode
427    ParallelNode
428    FlattenNode
429    ConditionNode
430    DeferNode
431    PrimaryDeferBlock
432    DeferredDeferBlock
433}