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, HashSet};
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            errors.append(&mut self.same_response_shape(fields_for_name, selection_set));
76            errors.append(
77                &mut self
78                    .same_for_common_parents_by_name(fields_for_name.as_slice(), selection_set),
79            );
80        });
81
82        errors
83    }
84
85    fn same_response_shape(
86        &mut self,
87        fields_for_name: &[FieldContext<'a, E, S>],
88        selection_set: &'a E::SelectionSet,
89    ) -> Vec<Error<'a, E, S>> {
90        if fields_for_name.len() <= 1 {
91            return Vec::new();
92        }
93
94        fields_for_name
95            .split_first()
96            .and_then(|(first, rest)| {
97                let errors: Vec<_> = rest
98                    .iter()
99                    .filter_map(|other| {
100                        Self::same_output_type_shape(
101                            self.schema_definition,
102                            first.field_definition.r#type(),
103                            other.field_definition.r#type(),
104                        )
105                        .not()
106                        .then_some(
107                            Error::FieldSelectionsDoNotMergeIncompatibleTypes {
108                                selection_set,
109                                field_a: first.field,
110                                field_definition_a: first.field_definition,
111                                field_b: other.field,
112                                field_definition_b: other.field_definition,
113                            },
114                        )
115                    })
116                    .collect();
117
118                errors.is_empty().not().then_some(errors)
119            })
120            .unwrap_or_else(|| {
121                let nested_grouped_fields =
122                    self.field_contexts_contained_fields(fields_for_name.iter());
123
124                nested_grouped_fields
125                    .values()
126                    .flat_map(|nested_fields_for_name| {
127                        self.same_response_shape(nested_fields_for_name, selection_set)
128                    })
129                    .collect()
130            })
131    }
132
133    fn same_for_common_parents_by_name(
134        &mut self,
135        fields_for_name: &[FieldContext<'a, E, S>],
136        selection_set: &'a E::SelectionSet,
137    ) -> Vec<Error<'a, E, S>> {
138        if fields_for_name.len() <= 1 {
139            return Vec::new();
140        }
141
142        type Group<'a, 'b, E, S> = Vec<&'b FieldContext<'a, E, S>>;
143        type ConcreteGroups<'a, 'b, E, S> = HashMap<&'a str, Group<'a, 'b, E, S>>;
144
145        let (abstract_group, concrete_groups): (Group<'a, '_, E, S>, ConcreteGroups<'a, '_, E, S>) =
146            fields_for_name.iter().fold(
147                (Vec::new(), HashMap::new()),
148                |(mut abstract_group, mut concrete_groups), field_context| {
149                    match field_context.parent_type {
150                        TypeDefinitionReference::Object(otd) => concrete_groups
151                            .entry(otd.name())
152                            .or_default()
153                            .push(field_context),
154                        TypeDefinitionReference::Interface(_) => abstract_group.push(field_context),
155                        _ => {}
156                    }
157                    (abstract_group, concrete_groups)
158                },
159            );
160
161        let groups = if concrete_groups.is_empty() {
162            vec![abstract_group]
163        } else {
164            concrete_groups
165                .into_values()
166                .map(|mut group| {
167                    group.extend(&abstract_group);
168                    group
169                })
170                .collect()
171        };
172
173        groups
174            .iter()
175            .flat_map(|fields_for_common_parent| {
176                fields_for_common_parent
177                    .split_first()
178                    .and_then(|(first, rest)| {
179                        let errors: Vec<_> = rest
180                            .iter()
181                            .filter_map(|other| {
182                                if first.field.name() != other.field.name() {
183                                    Some(Error::FieldSelectionsDoNotMergeDifferingNames {
184                                        selection_set,
185                                        field_a: first.field,
186                                        field_b: other.field,
187                                    })
188                                } else if !<E::Arguments<false> as Arguments<false>>::equivalent(
189                                    first.field.arguments(),
190                                    other.field.arguments(),
191                                ) {
192                                    Some(Error::FieldSelectionsDoNotMergeDifferingArguments {
193                                        selection_set,
194                                        field_a: first.field,
195                                        field_b: other.field,
196                                    })
197                                } else {
198                                    None
199                                }
200                            })
201                            .collect();
202
203                        errors.is_empty().not().then_some(errors)
204                    })
205                    .unwrap_or_else(|| {
206                        let nested_grouped_fields = self.field_contexts_contained_fields(
207                            fields_for_common_parent.iter().copied(),
208                        );
209
210                        nested_grouped_fields
211                            .values()
212                            .flat_map(|nested_fields_for_name| {
213                                self.same_for_common_parents_by_name(
214                                    nested_fields_for_name.as_slice(),
215                                    selection_set,
216                                )
217                            })
218                            .collect()
219                    })
220            })
221            .collect()
222    }
223
224    fn selection_set_contained_fields(
225        &mut self,
226        selection_set: &'a E::SelectionSet,
227        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
228    ) -> HashMap<&'a str, Vec<FieldContext<'a, E, S>>> {
229        let mut fields = HashMap::new();
230        self.visit_selections_for_fields(
231            selection_set.iter(),
232            &mut fields,
233            parent_type,
234            &HashSet::new(),
235        );
236        fields
237    }
238
239    fn field_contexts_contained_fields<'b>(
240        &mut self,
241        field_contexts: impl Iterator<Item = &'b FieldContext<'a, E, S>>,
242    ) -> HashMap<&'a str, Vec<FieldContext<'a, E, S>>>
243    where
244        'a: 'b,
245    {
246        let mut fields = HashMap::new();
247        field_contexts.for_each(|field_context| {
248            if let Some(selection_set) = field_context.field.selection_set() {
249                if let Some(parent_type) = self
250                    .schema_definition
251                    .get_type_definition(field_context.field_definition.r#type().base_name())
252                {
253                    if self.selection_set_valid(selection_set, parent_type) {
254                        self.visit_selections_for_fields(
255                            selection_set.iter(),
256                            &mut fields,
257                            parent_type,
258                            &field_context.parent_fragments,
259                        );
260                    }
261                }
262            }
263        });
264        fields
265    }
266
267    fn visit_selections_for_fields(
268        &mut self,
269        selections: impl Iterator<Item = &'a E::Selection>,
270        fields: &mut HashMap<&'a str, Vec<FieldContext<'a, E, S>>>,
271        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
272        parent_fragments: &HashSet<&'a str>,
273    ) {
274        selections.for_each(|selection| match selection.as_ref() {
275            SelectionReference::Field(field) => {
276                let fields_definition = parent_type.fields_definition();
277                if let Some(field_definition) = fields_definition
278                    .and_then(|fields_definition| fields_definition.get(field.name()))
279                {
280                    fields
281                        .entry(field.response_name())
282                        .or_default()
283                        .push(FieldContext {
284                            field,
285                            field_definition,
286                            parent_type,
287                            parent_fragments: parent_fragments.to_owned(),
288                        });
289                }
290            }
291            SelectionReference::FragmentSpread(fs) => {
292                let fragment_name = fs.name();
293                if !parent_fragments.contains(fragment_name) {
294                    if let Some(fragment_definition) = self.cache.fragment_definition(fragment_name)
295                    {
296                        let type_condition = fragment_definition.type_condition();
297                        if let Some(scoped_type) =
298                            self.schema_definition.get_type_definition(type_condition)
299                        {
300                            if self.selection_set_valid(
301                                fragment_definition.selection_set(),
302                                parent_type,
303                            ) {
304                                let mut new_parent_fragments = HashSet::new();
305                                new_parent_fragments.clone_from(parent_fragments);
306                                new_parent_fragments.insert(fragment_name);
307                                self.visit_selections_for_fields(
308                                    fragment_definition.selection_set().iter(),
309                                    fields,
310                                    scoped_type,
311                                    &new_parent_fragments,
312                                );
313                            }
314                        }
315                    }
316                }
317            }
318            SelectionReference::InlineFragment(i) => {
319                let scoped_type = match i.type_condition() {
320                    Some(type_condition) => {
321                        self.schema_definition.get_type_definition(type_condition)
322                    }
323                    None => Some(parent_type),
324                };
325                if let Some(scoped_type) = scoped_type {
326                    if self.selection_set_valid(i.selection_set(), scoped_type) {
327                        self.visit_selections_for_fields(
328                            i.selection_set().iter(),
329                            fields,
330                            scoped_type,
331                            parent_fragments,
332                        );
333                    }
334                }
335            }
336        });
337    }
338
339    fn same_output_type_shape(
340        schema_definition: &S,
341        type_a: &S::OutputType,
342        type_b: &S::OutputType,
343    ) -> bool {
344        match (
345            type_a.as_ref(schema_definition),
346            type_b.as_ref(schema_definition),
347        ) {
348            (
349                OutputTypeReference::Base(type_a_base, type_a_required),
350                OutputTypeReference::Base(type_b_base, type_b_required),
351            ) if type_a_required == type_b_required => {
352                !(type_a_base.is_scalar_or_enum() || type_b_base.is_scalar_or_enum())
353                    || type_a_base.name() == type_b_base.name()
354            }
355            (
356                OutputTypeReference::List(type_a_inner, type_a_required),
357                OutputTypeReference::List(type_b_inner, type_b_required),
358            ) if type_a_required == type_b_required => {
359                Self::same_output_type_shape(schema_definition, type_a_inner, type_b_inner)
360            }
361            _ => false,
362        }
363    }
364}
365
366impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
367    for FieldSelectionMerging<'a, E, S>
368{
369    type Error = Error<'a, E, S>;
370    type Errors = std::iter::Flatten<
371        std::collections::btree_map::IntoValues<Indexed<'a, E::SelectionSet>, Vec<Error<'a, E, S>>>,
372    >;
373
374    fn into_errors(self) -> Self::Errors {
375        self.cached_errors.into_values().flatten()
376    }
377}
378
379struct FieldContext<'a, E: ExecutableDocument, S: SchemaDefinition> {
380    field: &'a E::Field,
381    field_definition: &'a S::FieldDefinition,
382    parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
383    parent_fragments: HashSet<&'a str>,
384}