Skip to main content

hive_router_plan_executor/projection/
plan.rs

1use ahash::HashSet;
2use indexmap::IndexMap;
3use std::fmt::{Display, Formatter as FmtFormatter, Result as FmtResult};
4use std::sync::Arc;
5use tracing::warn;
6
7use hive_router_query_planner::{
8    ast::{
9        operation::OperationDefinition,
10        selection_item::SelectionItem,
11        selection_set::{FieldSelection, InlineFragmentSelection, SelectionSet},
12    },
13    state::supergraph_state::OperationKind,
14    utils::pretty_display::{get_indent, PrettyDisplay},
15};
16
17use crate::projection::error::ProjectionError;
18use crate::{introspection::schema::SchemaMetadata, utils::consts::TYPENAME_FIELD_NAME};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum TypeCondition {
22    Exact(String),
23    OneOf(HashSet<String>),
24}
25
26impl TypeCondition {
27    pub fn matches(&self, type_name: &str) -> bool {
28        match self {
29            TypeCondition::Exact(expected) => type_name == expected,
30            TypeCondition::OneOf(possible) => possible.contains(type_name),
31        }
32    }
33
34    pub fn union(self, other: TypeCondition) -> TypeCondition {
35        use TypeCondition::*;
36        match (self, other) {
37            (Exact(left), Exact(right)) => {
38                if left == right {
39                    // Or(Exact(A), Exact(A)) -> Exact(A)
40                    Exact(left)
41                } else {
42                    // Or(Exact(A), Exact(B)) -> OneOf(A, B)
43                    OneOf(HashSet::from_iter(vec![left, right]))
44                }
45            }
46            (OneOf(mut types), Exact(exact)) | (Exact(exact), OneOf(mut types)) => {
47                // Or(OneOf(A, B), Exact(C)) -> OneOf(A, B, C)
48                // Or(Exact(A), OneOf(A, B)) -> OneOf(A, B)
49                types.insert(exact);
50                OneOf(types)
51            }
52            (OneOf(mut left), OneOf(right)) => {
53                // Or(OneOf(A, B), OneOf(C, D)) -> OneOf(A, B, C, D)
54                left.extend(right);
55                OneOf(left)
56            }
57        }
58    }
59
60    pub fn intersect(self, other: TypeCondition) -> TypeCondition {
61        use TypeCondition::*;
62        match (self, other) {
63            (Exact(left), Exact(right)) => {
64                if left == right {
65                    // And(Exact(A), Exact(A)) -> Exact(A)
66                    Exact(left)
67                } else {
68                    // And(Exact(A), Exact(B)) is impossible,
69                    // so we return an empty OneOf
70                    // to represent a condition that can never be true.
71                    OneOf(HashSet::default())
72                }
73            }
74            (OneOf(types), Exact(exact)) | (Exact(exact), OneOf(types)) => {
75                if types.contains(&exact) {
76                    // And(OneOf(A, B), Exact(A)) -> Exact(A)
77                    Exact(exact)
78                } else {
79                    // And(Exact(A), OneOf(B, C)) is impossible,
80                    // so we return an empty OneOf
81                    // to represent a condition that can never be true.
82                    OneOf(HashSet::default())
83                }
84            }
85            (OneOf(mut left), OneOf(right)) => {
86                left.retain(|t| right.contains(t));
87                if left.len() == 1 {
88                    // And(OneOf(A, B), OneOf(A, C)) -> Exact(A)
89                    Exact(left.into_iter().next().expect("Set has one element"))
90                } else {
91                    // And(OneOf(A, B, C), OneOf(B, C)) -> OneOf(B, C)
92                    // And(OneOf(A, B), OneOf(C, D)) -> OneOf()
93                    OneOf(left)
94                }
95            }
96        }
97    }
98}
99
100#[derive(Debug, Clone)]
101pub enum ProjectionValueSource {
102    /// Represents the entire response data from subgraphs.
103    ResponseData {
104        selections: Option<Arc<Vec<FieldProjectionPlan>>>,
105    },
106    /// Represents a null value.
107    Null,
108}
109
110#[derive(Debug, Clone)]
111pub struct FieldProjectionPlan {
112    pub field_name: String,
113    pub response_key: String,
114    pub is_typename: bool,
115    /// A condition that checks the name of the parent object.
116    /// This is used to ensure that fields inside a fragment (e.g., `... on User`)
117    /// are only applied when the parent object's type matches the fragment's type condition.
118    /// If `None`, the plan applies to any parent type.
119    pub parent_type_guard: Option<TypeCondition>,
120    pub conditions: Option<FieldProjectionCondition>,
121    pub value: ProjectionValueSource,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum FieldProjectionCondition {
126    IncludeIfVariable(String),
127    SkipIfVariable(String),
128    ParentTypeCondition(TypeCondition),
129    FieldTypeCondition(TypeCondition),
130    EnumValuesCondition(HashSet<String>),
131    Or(Box<FieldProjectionCondition>, Box<FieldProjectionCondition>),
132    And(Box<FieldProjectionCondition>, Box<FieldProjectionCondition>),
133}
134
135pub enum FieldProjectionConditionError {
136    InvalidParentType,
137    InvalidFieldType,
138    Skip,
139    InvalidEnumValue,
140    Fatal(ProjectionError),
141}
142
143impl From<ProjectionError> for FieldProjectionConditionError {
144    fn from(err: ProjectionError) -> Self {
145        FieldProjectionConditionError::Fatal(err)
146    }
147}
148
149impl FieldProjectionCondition {
150    /// Combines two conditions with AND logic, reducing them to their minimum form
151    pub fn and(&self, right: FieldProjectionCondition) -> FieldProjectionCondition {
152        use FieldProjectionCondition::*;
153
154        match (self, right) {
155            (ParentTypeCondition(left), ParentTypeCondition(right)) => {
156                ParentTypeCondition(left.clone().intersect(right))
157            }
158            (FieldTypeCondition(left), FieldTypeCondition(right)) => {
159                FieldTypeCondition(left.clone().intersect(right))
160            }
161            (EnumValuesCondition(left), EnumValuesCondition(right)) => {
162                let mut left = left.clone();
163                left.retain(|v| right.contains(v));
164                EnumValuesCondition(left)
165            }
166            (left, right) => And(Box::new(left.clone()), Box::new(right)),
167        }
168    }
169
170    /// Combines two conditions with OR logic, reducing them to their minimum form.
171    ///
172    /// This method automatically deduplicates identical conditions to avoid creating
173    /// redundant expressions like `X OR X`.
174    pub fn or(&self, right: FieldProjectionCondition) -> FieldProjectionCondition {
175        use FieldProjectionCondition::*;
176
177        // Avoid creating duplicate OR expressions
178        if self == &right {
179            return self.clone();
180        }
181
182        match (self, right) {
183            (ParentTypeCondition(left), ParentTypeCondition(right)) => {
184                ParentTypeCondition(left.clone().union(right))
185            }
186            (FieldTypeCondition(left), FieldTypeCondition(right)) => {
187                FieldTypeCondition(left.clone().union(right))
188            }
189            (EnumValuesCondition(left), EnumValuesCondition(right)) => {
190                let mut result = left.clone();
191                result.extend(right);
192                EnumValuesCondition(result)
193            }
194            (left, right) => Or(Box::new(left.clone()), Box::new(right)),
195        }
196    }
197}
198
199impl FieldProjectionPlan {
200    pub fn from_operation(
201        operation: &OperationDefinition,
202        schema_metadata: &SchemaMetadata,
203    ) -> (&'static str, Vec<FieldProjectionPlan>) {
204        let root_type_name = match operation.operation_kind {
205            Some(OperationKind::Query) => "Query",
206            Some(OperationKind::Mutation) => "Mutation",
207            Some(OperationKind::Subscription) => "Subscription",
208            None => "Query",
209        };
210
211        let mut plans = Self::from_selection_set(
212            &operation.selection_set,
213            schema_metadata,
214            root_type_name,
215            &None,
216        )
217        .unwrap_or_default();
218
219        for plan in &mut plans {
220            Self::remove_redundant_child_guards(plan, schema_metadata);
221        }
222
223        (root_type_name, plans)
224    }
225
226    fn from_selection_set(
227        selection_set: &SelectionSet,
228        schema_metadata: &SchemaMetadata,
229        parent_type_name: &str,
230        parent_condition: &Option<FieldProjectionCondition>,
231    ) -> Option<Vec<FieldProjectionPlan>> {
232        let mut field_selections: IndexMap<String, FieldProjectionPlan> = IndexMap::new();
233
234        for selection_item in &selection_set.items {
235            match selection_item {
236                SelectionItem::Field(field) => {
237                    Self::process_field(
238                        field,
239                        &mut field_selections,
240                        schema_metadata,
241                        parent_type_name,
242                        parent_condition,
243                    );
244                }
245                SelectionItem::InlineFragment(inline_fragment) => {
246                    Self::process_inline_fragment(
247                        inline_fragment,
248                        &mut field_selections,
249                        schema_metadata,
250                        parent_condition,
251                    );
252                }
253                SelectionItem::FragmentSpread(_) => {
254                    // Fragment spreads should have been inlined by this stage.
255                    unreachable!(
256                        "Fragment spreads should not exist in the final response projection."
257                    );
258                }
259            }
260        }
261
262        if field_selections.is_empty() {
263            None
264        } else {
265            Some(field_selections.into_values().collect())
266        }
267    }
268
269    /// Extracts type names from a TypeCondition for easier comparison
270    fn type_names_from(condition: &TypeCondition) -> Vec<&str> {
271        match condition {
272            TypeCondition::Exact(ty) => vec![ty.as_str()],
273            TypeCondition::OneOf(types) => types.iter().map(|s| s.as_str()).collect(),
274        }
275    }
276
277    /// Recursively removes redundant type guards from child selections.
278    ///
279    /// A type guard is redundant when it covers exactly all possible types that can appear in that position.
280    /// For example, if a field `children` can only return types A or B (based on the parent's type guard),
281    /// and a child selection has guard `OneOf(A, B)`, then that guard is redundant since it will always match.
282    ///
283    /// Every guard has a cost during execution (computes field's output type or name of its parent),
284    /// so removing redundant guards helps optimize the response projection.
285    ///
286    /// This optimization must run after all fragment merging is complete to correctly
287    /// determine the full set of possible types.
288    fn remove_redundant_child_guards(
289        parent_field: &mut FieldProjectionPlan,
290        schema_metadata: &SchemaMetadata,
291    ) {
292        let ProjectionValueSource::ResponseData { selections } = &mut parent_field.value else {
293            return;
294        };
295
296        let Some(selections_arc) = selections else {
297            return;
298        };
299
300        let selections_mut = Arc::make_mut(selections_arc);
301
302        // Compute possible child types only if parent has a type guard
303        let possible_child_types = parent_field.parent_type_guard.as_ref().map(|parent_guard| {
304            let parent_types = Self::type_names_from(parent_guard);
305
306            // For each parent type, lookup what type this field returns
307            HashSet::from_iter(parent_types.iter().filter_map(|parent_type| {
308                schema_metadata
309                    .type_fields
310                    .get(*parent_type)
311                    .and_then(|fields| fields.get(&parent_field.field_name))
312                    .map(|field_info| field_info.output_type_name.as_str())
313            }))
314        });
315
316        for child in selections_mut {
317            // Remove redundant guard if this child's guard covers all possible types
318            if let (Some(child_guard), Some(possible_types)) =
319                (&child.parent_type_guard, &possible_child_types)
320            {
321                // Check if guard covers exactly all possible types by comparing as sets
322                let is_redundant = match child_guard {
323                    TypeCondition::Exact(ty) => {
324                        possible_types.len() == 1 && possible_types.contains(ty.as_str())
325                    }
326                    TypeCondition::OneOf(guard_types) => {
327                        guard_types.len() == possible_types.len()
328                            && guard_types
329                                .iter()
330                                .all(|t| possible_types.contains(t.as_str()))
331                    }
332                };
333
334                if is_redundant {
335                    child.parent_type_guard = None;
336                }
337            }
338
339            Self::remove_redundant_child_guards(child, schema_metadata);
340        }
341    }
342
343    fn apply_directive_conditions(
344        condition: Option<FieldProjectionCondition>,
345        include_if: &Option<String>,
346        skip_if: &Option<String>,
347    ) -> Option<FieldProjectionCondition> {
348        let mut condition = condition;
349        if let Some(include_if_var) = include_if {
350            condition = Self::and_optional(
351                condition,
352                Some(FieldProjectionCondition::IncludeIfVariable(
353                    include_if_var.clone(),
354                )),
355            );
356        }
357        if let Some(skip_if_var) = skip_if {
358            condition = Self::and_optional(
359                condition,
360                Some(FieldProjectionCondition::SkipIfVariable(
361                    skip_if_var.clone(),
362                )),
363            );
364        }
365        condition
366    }
367
368    fn combine_optional<T, F>(left: Option<T>, right: Option<T>, combiner: F) -> Option<T>
369    where
370        F: FnOnce(T, T) -> T,
371    {
372        match (left, right) {
373            (None, None) => None,
374            (Some(c), None) | (None, Some(c)) => Some(c),
375            (Some(l), Some(r)) => Some(combiner(l, r)),
376        }
377    }
378
379    /// Combines two optional conditions with OR logic
380    fn or_optional(
381        left: Option<FieldProjectionCondition>,
382        right: Option<FieldProjectionCondition>,
383    ) -> Option<FieldProjectionCondition> {
384        Self::combine_optional(left, right, |l, r| l.or(r))
385    }
386
387    /// Combines two optional conditions with AND logic
388    fn and_optional(
389        left: Option<FieldProjectionCondition>,
390        right: Option<FieldProjectionCondition>,
391    ) -> Option<FieldProjectionCondition> {
392        Self::combine_optional(left, right, |l, r| l.and(r))
393    }
394
395    /// When merging selections from different type fragments (e.g., Book and Magazine),
396    /// each condition must remain associated with its original type guard to ensure
397    /// correct runtime evaluation.
398    fn merge_conditions(
399        left_guard: &Option<TypeCondition>,
400        left_condition: Option<FieldProjectionCondition>,
401        right_guard: &Option<TypeCondition>,
402        right_condition: Option<FieldProjectionCondition>,
403    ) -> Option<FieldProjectionCondition> {
404        match (left_guard, right_guard, left_condition, right_condition) {
405            // No conditions to merge - return None
406            (_, _, None, None) => None,
407
408            // Both have guards AND guards differ - must preserve guard associations
409            // This happens when merging fragments on different types (e.g., Book + Magazine)
410            (Some(lg), Some(rg), lc, rc) if lg != rg => {
411                Some(Self::condition_with_guard(lg, lc).or(Self::condition_with_guard(rg, rc)))
412            }
413
414            // Catch-all for remaining cases - simple OR merge
415            // These cases are safe to merge without guard wrapping because:
416            // - Both guards are identical (l == r), so conditions apply to same type
417            // - One or both guards are None, meaning selections apply to all types
418            //   (merge_plan treats None as "all types", so if either
419            //   is None, the merged guard is None, making simple OR safe)
420            (_, _, lc, rc) => Self::or_optional(lc, rc),
421        }
422    }
423
424    /// Wraps a condition with a ParentType guard, or returns just the guard if no condition.
425    /// This creates
426    ///   `ParentType(guard) AND condition`,
427    /// or just
428    ///   `ParentType(guard)`
429    /// if condition is None.
430    fn condition_with_guard(
431        guard: &TypeCondition,
432        condition: Option<FieldProjectionCondition>,
433    ) -> FieldProjectionCondition {
434        let parent_check = FieldProjectionCondition::ParentTypeCondition(guard.clone());
435        match condition {
436            Some(cond) => parent_check.and(cond),
437            None => parent_check,
438        }
439    }
440
441    /// When the same field appears in multiple fragments,
442    /// this function combines them into a single plan by:
443    /// - Unioning type guards (e.g., Book + Magazine = OneOf(Book, Magazine))
444    /// - OR-ing conditions while preserving guard associations
445    /// - Recursively merging child selections
446    fn merge_plan(
447        field_selections: &mut IndexMap<String, FieldProjectionPlan>,
448        plan_to_merge: FieldProjectionPlan,
449    ) {
450        let Some(existing_plan) = field_selections.get_mut(&plan_to_merge.response_key) else {
451            // First time seeing this field - just insert it
452            field_selections.insert(plan_to_merge.response_key.clone(), plan_to_merge);
453            return;
454        };
455
456        // Capture guards before merging, needed for condition association
457        let existing_guard = existing_plan.parent_type_guard.clone();
458        let new_guard = plan_to_merge.parent_type_guard.clone();
459
460        // Merge type guards using OR semantics (union of when to include the field)
461        // - None means "applies to all types" (no type restriction)
462        // - Some(guard) means "applies only to specific types"
463        existing_plan.parent_type_guard = match (
464            existing_plan.parent_type_guard.take(),
465            plan_to_merge.parent_type_guard,
466        ) {
467            (None, _) | (_, None) => None, // None (all types) subsumes any specific guard
468            (Some(left), Some(right)) => Some(left.union(right)),
469        };
470
471        existing_plan.conditions = Self::merge_conditions(
472            &existing_guard,
473            existing_plan.conditions.take(),
474            &new_guard,
475            plan_to_merge.conditions,
476        );
477
478        match (&mut existing_plan.value, plan_to_merge.value) {
479            (
480                ProjectionValueSource::ResponseData {
481                    selections: existing_selections,
482                },
483                ProjectionValueSource::ResponseData {
484                    selections: new_selections,
485                },
486            ) => {
487                if let Some(new_selections) = new_selections {
488                    match existing_selections {
489                        Some(selections) => {
490                            let selections_mut = Arc::make_mut(selections);
491                            let new_selections_vec = Arc::try_unwrap(new_selections)
492                                .unwrap_or_else(|arc| (*arc).clone());
493
494                            // Convert Vec to Map for efficient merging by response_key
495                            let mut selections_map: IndexMap<String, FieldProjectionPlan> =
496                                selections_mut
497                                    .drain(..)
498                                    .map(|plan| (plan.response_key.clone(), plan))
499                                    .collect();
500
501                            // Recursively merge each child selection
502                            for new_plan in new_selections_vec {
503                                Self::merge_plan(&mut selections_map, new_plan);
504                            }
505
506                            // Convert back to Vec for efficient iteration during projection
507                            selections_mut.extend(selections_map.into_values());
508                        }
509                        None => *existing_selections = Some(new_selections),
510                    }
511                }
512            }
513            (ProjectionValueSource::Null, ProjectionValueSource::Null) => {
514                // Both plans have `Null` value source, so nothing to merge
515            }
516            _ => {
517                // This case should not be reached during initial plan construction,
518                // as `Null` is only introduced during the authorization step.
519                // If we merge a plan, it's always to combine selections.
520                warn!("Merging plans with `Null` value source is not supported during initial plan construction.");
521                existing_plan.value = ProjectionValueSource::Null;
522            }
523        }
524    }
525
526    fn simplify_condition(
527        condition: FieldProjectionCondition,
528        parent_type_guard: &Option<TypeCondition>,
529    ) -> Option<FieldProjectionCondition> {
530        let Some(TypeCondition::Exact(guard_type)) = parent_type_guard else {
531            return Some(condition);
532        };
533
534        match condition {
535            // Remove redundant ParentTypeCondition that matches the guard
536            FieldProjectionCondition::ParentTypeCondition(TypeCondition::Exact(cond_type))
537                if &cond_type == guard_type =>
538            {
539                None
540            }
541
542            // OneOf with single type matching guard is redundant
543            FieldProjectionCondition::ParentTypeCondition(TypeCondition::OneOf(types))
544                if types.len() == 1
545                    && types.iter().next().map(|t| t.as_str()) == Some(guard_type) =>
546            {
547                None
548            }
549
550            // Recursively simplify AND expressions
551            FieldProjectionCondition::And(left, right) => {
552                let left_simplified = Self::simplify_condition(*left, parent_type_guard);
553                let right_simplified = Self::simplify_condition(*right, parent_type_guard);
554
555                match (left_simplified, right_simplified) {
556                    (None, None) => None,
557                    (Some(cond), None) | (None, Some(cond)) => Some(cond),
558                    (Some(l), Some(r)) => Some(l.and(r)),
559                }
560            }
561
562            // Recursively simplify OR expressions
563            FieldProjectionCondition::Or(left, right) => {
564                let left_simplified = Self::simplify_condition(*left, parent_type_guard);
565                let right_simplified = Self::simplify_condition(*right, parent_type_guard);
566
567                match (left_simplified, right_simplified) {
568                    (None, None) => None,
569                    (Some(cond), None) | (None, Some(cond)) => Some(cond),
570                    (Some(l), Some(r)) => Some(l.or(r)),
571                }
572            }
573
574            // Keep other conditions as-is
575            other => Some(other),
576        }
577    }
578
579    fn process_field(
580        field: &FieldSelection,
581        field_selections: &mut IndexMap<String, FieldProjectionPlan>,
582        schema_metadata: &SchemaMetadata,
583        parent_type_name: &str,
584        parent_condition: &Option<FieldProjectionCondition>,
585    ) {
586        let field_name = &field.name;
587        let response_key = field.alias.as_ref().unwrap_or(field_name).clone();
588
589        let field_type = if field_name == TYPENAME_FIELD_NAME {
590            "String".to_string()
591        } else {
592            let field_map = match schema_metadata.type_fields.get(parent_type_name) {
593                Some(fields) => fields,
594                None => {
595                    warn!(
596                        "No fields found for type `{}` in schema metadata.",
597                        parent_type_name
598                    );
599                    return;
600                }
601            };
602            match field_map.get(field_name) {
603                Some(f) => f.output_type_name.clone(),
604                None => {
605                    warn!(
606                        "Field `{}` not found in type `{}` in schema metadata.",
607                        field_name, parent_type_name
608                    );
609                    return;
610                }
611            }
612        };
613
614        let type_condition = if schema_metadata.is_object_type(&field_type)
615            || schema_metadata.is_scalar_type(&field_type)
616        {
617            TypeCondition::Exact(field_type.to_string())
618        } else {
619            TypeCondition::OneOf(
620                schema_metadata
621                    .possible_types
622                    .get_possible_types(&field_type),
623            )
624        };
625
626        let parent_type_guard = parent_condition.as_ref().and_then(Self::get_type_guard);
627        let conditions_for_selections = Self::apply_directive_conditions(
628            parent_type_guard
629                .as_ref()
630                .map(|_| FieldProjectionCondition::ParentTypeCondition(type_condition.clone())),
631            &field.include_if,
632            &field.skip_if,
633        );
634
635        let mut condition_for_field = if schema_metadata.is_union_type(&field_type)
636            || schema_metadata.is_interface_type(&field_type)
637        {
638            Self::and_optional(
639                parent_condition.clone(),
640                Some(FieldProjectionCondition::FieldTypeCondition(type_condition)),
641            )
642        } else {
643            // It makes no sense to have a field type condition for concrete types
644            // as they'd always evaluate to true.
645            parent_condition.clone()
646        };
647        condition_for_field = Self::apply_directive_conditions(
648            condition_for_field,
649            &field.include_if,
650            &field.skip_if,
651        );
652
653        if let Some(enum_values) = schema_metadata.enum_values.get(&field_type) {
654            condition_for_field = Self::and_optional(
655                condition_for_field,
656                Some(FieldProjectionCondition::EnumValuesCondition(
657                    enum_values.clone(),
658                )),
659            );
660        }
661
662        let final_conditions =
663            condition_for_field.and_then(|cond| Self::simplify_condition(cond, &parent_type_guard));
664
665        let new_plan = FieldProjectionPlan {
666            field_name: field_name.to_string(),
667            response_key,
668            parent_type_guard,
669            is_typename: field_name == TYPENAME_FIELD_NAME,
670            conditions: final_conditions,
671            value: ProjectionValueSource::ResponseData {
672                selections: Self::from_selection_set(
673                    &field.selections,
674                    schema_metadata,
675                    &field_type,
676                    &conditions_for_selections,
677                )
678                .map(Arc::new),
679            },
680        };
681
682        Self::merge_plan(field_selections, new_plan);
683    }
684
685    fn process_inline_fragment(
686        inline_fragment: &InlineFragmentSelection,
687        field_selections: &mut IndexMap<String, FieldProjectionPlan>,
688        schema_metadata: &SchemaMetadata,
689        parent_condition: &Option<FieldProjectionCondition>,
690    ) {
691        let inline_fragment_type = &inline_fragment.type_condition;
692        let type_condition = if schema_metadata.is_object_type(inline_fragment_type) {
693            TypeCondition::Exact(inline_fragment_type.to_string())
694        } else {
695            TypeCondition::OneOf(
696                schema_metadata
697                    .possible_types
698                    .get_possible_types(inline_fragment_type),
699            )
700        };
701
702        let mut condition_for_fragment = Self::and_optional(
703            parent_condition.clone(),
704            Some(FieldProjectionCondition::ParentTypeCondition(
705                type_condition.clone(),
706            )),
707        );
708
709        condition_for_fragment = Self::apply_directive_conditions(
710            condition_for_fragment,
711            &inline_fragment.include_if,
712            &inline_fragment.skip_if,
713        );
714
715        if let Some(mut inline_fragment_selections) = Self::from_selection_set(
716            &inline_fragment.selections,
717            schema_metadata,
718            inline_fragment_type,
719            &condition_for_fragment,
720        ) {
721            // Update the type guard for all selections from this fragment
722            for selection in &mut inline_fragment_selections {
723                selection.parent_type_guard = Some(type_condition.clone());
724            }
725
726            for selection in inline_fragment_selections {
727                Self::merge_plan(field_selections, selection);
728            }
729        }
730    }
731
732    pub fn with_new_value(&self, new_value: ProjectionValueSource) -> FieldProjectionPlan {
733        FieldProjectionPlan {
734            field_name: self.field_name.clone(),
735            response_key: self.response_key.clone(),
736            parent_type_guard: self.parent_type_guard.clone(),
737            conditions: self.conditions.clone(),
738            is_typename: self.is_typename,
739            value: new_value,
740        }
741    }
742
743    fn get_type_guard(condition: &FieldProjectionCondition) -> Option<TypeCondition> {
744        match condition {
745            FieldProjectionCondition::ParentTypeCondition(tc) => Some(tc.clone()),
746            FieldProjectionCondition::And(a, b) => Self::combine_optional(
747                Self::get_type_guard(a),
748                Self::get_type_guard(b),
749                |ga, gb| ga.intersect(gb),
750            ),
751            FieldProjectionCondition::Or(a, b) => Self::combine_optional(
752                Self::get_type_guard(a),
753                Self::get_type_guard(b),
754                |ga, gb| ga.union(gb),
755            ),
756            _ => None,
757        }
758    }
759}
760
761impl Display for TypeCondition {
762    fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
763        match self {
764            TypeCondition::Exact(type_name) => write!(f, "Exact({})", type_name),
765            TypeCondition::OneOf(types) => {
766                write!(f, "OneOf(")?;
767                let types_vec: Vec<_> = types.iter().collect();
768                for (i, type_name) in types_vec.iter().enumerate() {
769                    if i > 0 {
770                        write!(f, ", ")?;
771                    }
772                    write!(f, "{}", type_name)?;
773                }
774                write!(f, ")")
775            }
776        }
777    }
778}
779
780impl Display for FieldProjectionCondition {
781    fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
782        match self {
783            FieldProjectionCondition::IncludeIfVariable(var) => {
784                write!(f, "Include(if: ${})", var)
785            }
786            FieldProjectionCondition::SkipIfVariable(var) => {
787                write!(f, "Skip(if: ${})", var)
788            }
789            FieldProjectionCondition::ParentTypeCondition(tc) => {
790                write!(f, "ParentType({})", tc)
791            }
792            FieldProjectionCondition::FieldTypeCondition(tc) => {
793                write!(f, "FieldType({})", tc)
794            }
795            FieldProjectionCondition::EnumValuesCondition(values) => {
796                write!(f, "EnumValues(")?;
797                let values_vec: Vec<_> = values.iter().collect();
798                for (i, value) in values_vec.iter().enumerate() {
799                    if i > 0 {
800                        write!(f, ", ")?;
801                    }
802                    write!(f, "{}", value)?;
803                }
804                write!(f, ")")
805            }
806            FieldProjectionCondition::Or(left, right) => {
807                write!(f, "({} OR {})", left, right)
808            }
809            FieldProjectionCondition::And(left, right) => {
810                write!(f, "({} AND {})", left, right)
811            }
812        }
813    }
814}
815
816impl Display for FieldProjectionPlan {
817    fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
818        self.pretty_fmt(f, 0)
819    }
820}
821
822impl PrettyDisplay for FieldProjectionPlan {
823    fn pretty_fmt(&self, f: &mut FmtFormatter<'_>, depth: usize) -> FmtResult {
824        let indent = get_indent(depth);
825
826        if self.response_key == self.field_name {
827            writeln!(f, "{}{}: {{", indent, self.response_key)?;
828        } else {
829            writeln!(
830                f,
831                "{}{} (alias for {}) {{",
832                indent, self.response_key, self.field_name
833            )?;
834        }
835
836        if let Some(parent_type_guard) = self.parent_type_guard.as_ref() {
837            writeln!(f, "{}  type guard: {}", indent, parent_type_guard)?;
838        }
839
840        if let Some(conditions) = self.conditions.as_ref() {
841            writeln!(f, "{}  conditions: {}", indent, conditions)?;
842        }
843
844        match &self.value {
845            ProjectionValueSource::ResponseData { selections } => {
846                if let Some(selections) = selections {
847                    writeln!(f, "{}  selections:", indent)?;
848                    for selection in selections.iter() {
849                        selection.pretty_fmt(f, depth + 2)?;
850                    }
851                }
852            }
853            ProjectionValueSource::Null => {
854                writeln!(f, "{}  value: Null", indent)?;
855            }
856        }
857
858        writeln!(f, "{}}}", indent)?;
859        Ok(())
860    }
861}