Skip to main content

bluejay_validator/executable/document/rules/
field_selection_merging.rs

1use crate::executable::{
2    document::{Error, Rule, Visitor},
3    Cache,
4};
5use bluejay_core::definition::{
6    FieldDefinition, FieldsDefinition, ObjectTypeDefinition, OutputType, OutputTypeReference,
7    SchemaDefinition, TypeDefinitionReference,
8};
9use bluejay_core::executable::{
10    ExecutableDocument, Field, FragmentDefinition, FragmentSpread, InlineFragment, Selection,
11    SelectionReference,
12};
13use bluejay_core::{Arguments, AsIter, Indexed};
14use std::collections::{BTreeMap, HashMap};
15use std::ops::Not;
16
17pub struct FieldSelectionMerging<'a, E: ExecutableDocument, S: SchemaDefinition> {
18    cache: &'a Cache<'a, E, S>,
19    schema_definition: &'a S,
20    cached_errors: BTreeMap<Indexed<'a, E::SelectionSet>, Vec<Error<'a, E, S>>>,
21}
22
23impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition> Visitor<'a, E, S>
24    for FieldSelectionMerging<'a, E, S>
25{
26    fn new(_: &'a E, schema_definition: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
27        Self {
28            cache,
29            schema_definition,
30            cached_errors: BTreeMap::new(),
31        }
32    }
33
34    fn visit_selection_set(
35        &mut self,
36        selection_set: &'a E::SelectionSet,
37        r#type: TypeDefinitionReference<'a, S::TypeDefinition>,
38    ) {
39        self.selection_set_valid(selection_set, r#type);
40    }
41}
42
43impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> FieldSelectionMerging<'a, E, S> {
44    fn selection_set_valid(
45        &mut self,
46        selection_set: &'a E::SelectionSet,
47        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
48    ) -> bool {
49        if let Some(errors) = self.cached_errors.get(&Indexed(selection_set)) {
50            errors.is_empty()
51        } else {
52            self.cached_errors
53                .insert(Indexed(selection_set), Vec::new());
54
55            let grouped_fields = self.selection_set_contained_fields(selection_set, parent_type);
56
57            let errors = self.fields_in_set_can_merge(grouped_fields, selection_set);
58
59            let is_valid = errors.is_empty();
60
61            self.cached_errors.insert(Indexed(selection_set), errors);
62
63            is_valid
64        }
65    }
66
67    fn fields_in_set_can_merge(
68        &mut self,
69        grouped_fields: HashMap<&'a str, Vec<FieldContext<'a, E, S>>>,
70        selection_set: &'a E::SelectionSet,
71    ) -> Vec<Error<'a, E, S>> {
72        let mut errors = Vec::new();
73
74        grouped_fields.values().for_each(|fields_for_name| {
75            self.same_response_shape(fields_for_name, selection_set, &mut errors);
76            self.same_for_common_parents_by_name(
77                fields_for_name.as_slice(),
78                selection_set,
79                &mut errors,
80            );
81        });
82
83        errors
84    }
85
86    fn same_response_shape(
87        &mut self,
88        fields_for_name: &[FieldContext<'a, E, S>],
89        selection_set: &'a E::SelectionSet,
90        errors: &mut Vec<Error<'a, E, S>>,
91    ) {
92        if fields_for_name.len() <= 1 {
93            return;
94        }
95
96        let (first, rest) = fields_for_name.split_first().unwrap();
97        let prev_len = errors.len();
98        errors.extend(rest.iter().filter_map(|other| {
99            Self::same_output_type_shape(
100                self.schema_definition,
101                first.field_definition.r#type(),
102                other.field_definition.r#type(),
103            )
104            .not()
105            .then_some(Error::FieldSelectionsDoNotMergeIncompatibleTypes {
106                selection_set,
107                field_a: first.field,
108                field_definition_a: first.field_definition,
109                field_b: other.field,
110                field_definition_b: other.field_definition,
111            })
112        }));
113
114        if errors.len() == prev_len {
115            let nested_grouped_fields =
116                self.field_contexts_contained_fields(fields_for_name.iter());
117
118            for nested_fields_for_name in nested_grouped_fields.values() {
119                self.same_response_shape(nested_fields_for_name, selection_set, errors);
120            }
121        }
122    }
123
124    fn same_for_common_parents_by_name(
125        &mut self,
126        fields_for_name: &[FieldContext<'a, E, S>],
127        selection_set: &'a E::SelectionSet,
128        errors: &mut Vec<Error<'a, E, S>>,
129    ) {
130        if fields_for_name.len() <= 1 {
131            return;
132        }
133
134        // Fast path: check if all fields share the same parent type (common case)
135        let all_same_parent =
136            fields_for_name
137                .windows(2)
138                .all(|w| match (&w[0].parent_type, &w[1].parent_type) {
139                    (TypeDefinitionReference::Object(a), TypeDefinitionReference::Object(b)) => {
140                        a.name() == b.name()
141                    }
142                    // Interface fields are from the abstract type itself, not a specific
143                    // concrete type, so all interface parents are treated as the same group.
144                    (
145                        TypeDefinitionReference::Interface(_),
146                        TypeDefinitionReference::Interface(_),
147                    ) => true,
148                    _ => false,
149                });
150
151        if all_same_parent {
152            // All fields are from the same parent — treat as a single group
153            let refs: Vec<_> = fields_for_name.iter().collect();
154            self.check_common_parent_group(&refs, selection_set, errors);
155            return;
156        }
157
158        type Group<'a, 'b, E, S> = Vec<&'b FieldContext<'a, E, S>>;
159        type ConcreteGroups<'a, 'b, E, S> = HashMap<&'a str, Group<'a, 'b, E, S>>;
160
161        let (abstract_group, concrete_groups): (Group<'a, '_, E, S>, ConcreteGroups<'a, '_, E, S>) =
162            fields_for_name.iter().fold(
163                (Vec::new(), HashMap::new()),
164                |(mut abstract_group, mut concrete_groups), field_context| {
165                    match field_context.parent_type {
166                        TypeDefinitionReference::Object(otd) => concrete_groups
167                            .entry(otd.name())
168                            .or_default()
169                            .push(field_context),
170                        TypeDefinitionReference::Interface(_) => abstract_group.push(field_context),
171                        _ => {}
172                    }
173                    (abstract_group, concrete_groups)
174                },
175            );
176
177        if concrete_groups.is_empty() {
178            self.check_common_parent_group(&abstract_group, selection_set, errors);
179        } else {
180            for mut group in concrete_groups.into_values() {
181                group.extend(&abstract_group);
182                self.check_common_parent_group(&group, selection_set, errors);
183            }
184        }
185    }
186
187    fn check_common_parent_group(
188        &mut self,
189        fields_for_common_parent: &[&FieldContext<'a, E, S>],
190        selection_set: &'a E::SelectionSet,
191        errors: &mut Vec<Error<'a, E, S>>,
192    ) {
193        let Some((first, rest)) = fields_for_common_parent.split_first() else {
194            return;
195        };
196
197        let prev_len = errors.len();
198        errors.extend(rest.iter().filter_map(|other| {
199            if first.field.name() != other.field.name() {
200                Some(Error::FieldSelectionsDoNotMergeDifferingNames {
201                    selection_set,
202                    field_a: first.field,
203                    field_b: other.field,
204                })
205            } else if !<E::Arguments<false> as Arguments<false>>::equivalent(
206                first.field.arguments(),
207                other.field.arguments(),
208            ) {
209                Some(Error::FieldSelectionsDoNotMergeDifferingArguments {
210                    selection_set,
211                    field_a: first.field,
212                    field_b: other.field,
213                })
214            } else {
215                None
216            }
217        }));
218
219        if errors.len() == prev_len {
220            let nested_grouped_fields =
221                self.field_contexts_contained_fields(fields_for_common_parent.iter().copied());
222
223            for nested_fields_for_name in nested_grouped_fields.values() {
224                self.same_for_common_parents_by_name(
225                    nested_fields_for_name.as_slice(),
226                    selection_set,
227                    errors,
228                );
229            }
230        }
231    }
232
233    fn selection_set_contained_fields(
234        &mut self,
235        selection_set: &'a E::SelectionSet,
236        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
237    ) -> HashMap<&'a str, Vec<FieldContext<'a, E, S>>> {
238        let mut fields = HashMap::new();
239        self.visit_selections_for_fields(selection_set.iter(), &mut fields, parent_type, &[]);
240        fields
241    }
242
243    fn field_contexts_contained_fields<'b>(
244        &mut self,
245        field_contexts: impl Iterator<Item = &'b FieldContext<'a, E, S>>,
246    ) -> HashMap<&'a str, Vec<FieldContext<'a, E, S>>>
247    where
248        'a: 'b,
249    {
250        let mut fields = HashMap::new();
251        field_contexts.for_each(|field_context| {
252            if let Some(selection_set) = field_context.field.selection_set() {
253                if let Some(parent_type) = self
254                    .schema_definition
255                    .get_type_definition(field_context.field_definition.r#type().base_name())
256                {
257                    if self.selection_set_valid(selection_set, parent_type) {
258                        self.visit_selections_for_fields(
259                            selection_set.iter(),
260                            &mut fields,
261                            parent_type,
262                            &field_context.parent_fragments,
263                        );
264                    }
265                }
266            }
267        });
268        fields
269    }
270
271    fn visit_selections_for_fields(
272        &mut self,
273        selections: impl Iterator<Item = &'a E::Selection>,
274        fields: &mut HashMap<&'a str, Vec<FieldContext<'a, E, S>>>,
275        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
276        parent_fragments: &[&'a str],
277    ) {
278        selections.for_each(|selection| match selection.as_ref() {
279            SelectionReference::Field(field) => {
280                let fields_definition = parent_type.fields_definition();
281                if let Some(field_definition) = fields_definition
282                    .and_then(|fields_definition| fields_definition.get(field.name()))
283                {
284                    fields
285                        .entry(field.response_name())
286                        .or_default()
287                        .push(FieldContext {
288                            field,
289                            field_definition,
290                            parent_type,
291                            parent_fragments: parent_fragments.to_vec(),
292                        });
293                }
294            }
295            SelectionReference::FragmentSpread(fs) => {
296                let fragment_name = fs.name();
297                if !parent_fragments.contains(&fragment_name) {
298                    if let Some(fragment_definition) = self.cache.fragment_definition(fragment_name)
299                    {
300                        let type_condition = fragment_definition.type_condition();
301                        if let Some(scoped_type) =
302                            self.schema_definition.get_type_definition(type_condition)
303                        {
304                            if self.selection_set_valid(
305                                fragment_definition.selection_set(),
306                                parent_type,
307                            ) {
308                                let mut new_parent_fragments =
309                                    Vec::with_capacity(parent_fragments.len() + 1);
310                                new_parent_fragments.extend_from_slice(parent_fragments);
311                                new_parent_fragments.push(fragment_name);
312                                self.visit_selections_for_fields(
313                                    fragment_definition.selection_set().iter(),
314                                    fields,
315                                    scoped_type,
316                                    &new_parent_fragments,
317                                );
318                            }
319                        }
320                    }
321                }
322            }
323            SelectionReference::InlineFragment(i) => {
324                let scoped_type = match i.type_condition() {
325                    Some(type_condition) => {
326                        self.schema_definition.get_type_definition(type_condition)
327                    }
328                    None => Some(parent_type),
329                };
330                if let Some(scoped_type) = scoped_type {
331                    if self.selection_set_valid(i.selection_set(), scoped_type) {
332                        self.visit_selections_for_fields(
333                            i.selection_set().iter(),
334                            fields,
335                            scoped_type,
336                            parent_fragments,
337                        );
338                    }
339                }
340            }
341        });
342    }
343
344    fn same_output_type_shape(
345        schema_definition: &S,
346        type_a: &S::OutputType,
347        type_b: &S::OutputType,
348    ) -> bool {
349        match (
350            type_a.as_ref(schema_definition),
351            type_b.as_ref(schema_definition),
352        ) {
353            (
354                OutputTypeReference::Base(type_a_base, type_a_required),
355                OutputTypeReference::Base(type_b_base, type_b_required),
356            ) if type_a_required == type_b_required => {
357                !(type_a_base.is_scalar_or_enum() || type_b_base.is_scalar_or_enum())
358                    || type_a_base.name() == type_b_base.name()
359            }
360            (
361                OutputTypeReference::List(type_a_inner, type_a_required),
362                OutputTypeReference::List(type_b_inner, type_b_required),
363            ) if type_a_required == type_b_required => {
364                Self::same_output_type_shape(schema_definition, type_a_inner, type_b_inner)
365            }
366            _ => false,
367        }
368    }
369}
370
371impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
372    for FieldSelectionMerging<'a, E, S>
373{
374    type Error = Error<'a, E, S>;
375    type Errors = std::iter::Flatten<
376        std::collections::btree_map::IntoValues<Indexed<'a, E::SelectionSet>, Vec<Error<'a, E, S>>>,
377    >;
378
379    fn into_errors(self) -> Self::Errors {
380        self.cached_errors.into_values().flatten()
381    }
382}
383
384struct FieldContext<'a, E: ExecutableDocument, S: SchemaDefinition> {
385    field: &'a E::Field,
386    field_definition: &'a S::FieldDefinition,
387    parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
388    parent_fragments: Vec<&'a str>,
389}