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}