use crate::executable::{
document::{Error, Rule, Visitor},
Cache,
};
use bluejay_core::definition::{
FieldDefinition, FieldsDefinition, ObjectTypeDefinition, OutputType, OutputTypeReference,
SchemaDefinition, TypeDefinitionReference,
};
use bluejay_core::executable::{
ExecutableDocument, Field, FragmentDefinition, FragmentSpread, InlineFragment, Selection,
SelectionReference,
};
use bluejay_core::{Arguments, AsIter, Indexed};
use std::collections::{BTreeMap, HashMap};
use std::ops::Not;
pub struct FieldSelectionMerging<'a, E: ExecutableDocument, S: SchemaDefinition> {
cache: &'a Cache<'a, E, S>,
schema_definition: &'a S,
cached_errors: BTreeMap<Indexed<'a, E::SelectionSet>, Vec<Error<'a, E, S>>>,
}
impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition> Visitor<'a, E, S>
for FieldSelectionMerging<'a, E, S>
{
fn new(_: &'a E, schema_definition: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
Self {
cache,
schema_definition,
cached_errors: BTreeMap::new(),
}
}
fn visit_selection_set(
&mut self,
selection_set: &'a E::SelectionSet,
r#type: TypeDefinitionReference<'a, S::TypeDefinition>,
) {
self.selection_set_valid(selection_set, r#type);
}
}
impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> FieldSelectionMerging<'a, E, S> {
fn selection_set_valid(
&mut self,
selection_set: &'a E::SelectionSet,
parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
) -> bool {
if let Some(errors) = self.cached_errors.get(&Indexed(selection_set)) {
errors.is_empty()
} else {
self.cached_errors
.insert(Indexed(selection_set), Vec::new());
let grouped_fields = self.selection_set_contained_fields(selection_set, parent_type);
let errors = self.fields_in_set_can_merge(grouped_fields, selection_set);
let is_valid = errors.is_empty();
self.cached_errors.insert(Indexed(selection_set), errors);
is_valid
}
}
fn fields_in_set_can_merge(
&mut self,
grouped_fields: HashMap<&'a str, Vec<FieldContext<'a, E, S>>>,
selection_set: &'a E::SelectionSet,
) -> Vec<Error<'a, E, S>> {
let mut errors = Vec::new();
grouped_fields.values().for_each(|fields_for_name| {
self.same_response_shape(fields_for_name, selection_set, &mut errors);
self.same_for_common_parents_by_name(
fields_for_name.as_slice(),
selection_set,
&mut errors,
);
});
errors
}
fn same_response_shape(
&mut self,
fields_for_name: &[FieldContext<'a, E, S>],
selection_set: &'a E::SelectionSet,
errors: &mut Vec<Error<'a, E, S>>,
) {
if fields_for_name.len() <= 1 {
return;
}
let (first, rest) = fields_for_name.split_first().unwrap();
let prev_len = errors.len();
errors.extend(rest.iter().filter_map(|other| {
Self::same_output_type_shape(
self.schema_definition,
first.field_definition.r#type(),
other.field_definition.r#type(),
)
.not()
.then_some(Error::FieldSelectionsDoNotMergeIncompatibleTypes {
selection_set,
field_a: first.field,
field_definition_a: first.field_definition,
field_b: other.field,
field_definition_b: other.field_definition,
})
}));
if errors.len() == prev_len {
let nested_grouped_fields =
self.field_contexts_contained_fields(fields_for_name.iter());
for nested_fields_for_name in nested_grouped_fields.values() {
self.same_response_shape(nested_fields_for_name, selection_set, errors);
}
}
}
fn same_for_common_parents_by_name(
&mut self,
fields_for_name: &[FieldContext<'a, E, S>],
selection_set: &'a E::SelectionSet,
errors: &mut Vec<Error<'a, E, S>>,
) {
if fields_for_name.len() <= 1 {
return;
}
let all_same_parent =
fields_for_name
.windows(2)
.all(|w| match (&w[0].parent_type, &w[1].parent_type) {
(TypeDefinitionReference::Object(a), TypeDefinitionReference::Object(b)) => {
a.name() == b.name()
}
(
TypeDefinitionReference::Interface(_),
TypeDefinitionReference::Interface(_),
) => true,
_ => false,
});
if all_same_parent {
let refs: Vec<_> = fields_for_name.iter().collect();
self.check_common_parent_group(&refs, selection_set, errors);
return;
}
type Group<'a, 'b, E, S> = Vec<&'b FieldContext<'a, E, S>>;
type ConcreteGroups<'a, 'b, E, S> = HashMap<&'a str, Group<'a, 'b, E, S>>;
let (abstract_group, concrete_groups): (Group<'a, '_, E, S>, ConcreteGroups<'a, '_, E, S>) =
fields_for_name.iter().fold(
(Vec::new(), HashMap::new()),
|(mut abstract_group, mut concrete_groups), field_context| {
match field_context.parent_type {
TypeDefinitionReference::Object(otd) => concrete_groups
.entry(otd.name())
.or_default()
.push(field_context),
TypeDefinitionReference::Interface(_) => abstract_group.push(field_context),
_ => {}
}
(abstract_group, concrete_groups)
},
);
if concrete_groups.is_empty() {
self.check_common_parent_group(&abstract_group, selection_set, errors);
} else {
for mut group in concrete_groups.into_values() {
group.extend(&abstract_group);
self.check_common_parent_group(&group, selection_set, errors);
}
}
}
fn check_common_parent_group(
&mut self,
fields_for_common_parent: &[&FieldContext<'a, E, S>],
selection_set: &'a E::SelectionSet,
errors: &mut Vec<Error<'a, E, S>>,
) {
let Some((first, rest)) = fields_for_common_parent.split_first() else {
return;
};
let prev_len = errors.len();
errors.extend(rest.iter().filter_map(|other| {
if first.field.name() != other.field.name() {
Some(Error::FieldSelectionsDoNotMergeDifferingNames {
selection_set,
field_a: first.field,
field_b: other.field,
})
} else if !<E::Arguments<false> as Arguments<false>>::equivalent(
first.field.arguments(),
other.field.arguments(),
) {
Some(Error::FieldSelectionsDoNotMergeDifferingArguments {
selection_set,
field_a: first.field,
field_b: other.field,
})
} else {
None
}
}));
if errors.len() == prev_len {
let nested_grouped_fields =
self.field_contexts_contained_fields(fields_for_common_parent.iter().copied());
for nested_fields_for_name in nested_grouped_fields.values() {
self.same_for_common_parents_by_name(
nested_fields_for_name.as_slice(),
selection_set,
errors,
);
}
}
}
fn selection_set_contained_fields(
&mut self,
selection_set: &'a E::SelectionSet,
parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
) -> HashMap<&'a str, Vec<FieldContext<'a, E, S>>> {
let mut fields = HashMap::new();
self.visit_selections_for_fields(selection_set.iter(), &mut fields, parent_type, &[]);
fields
}
fn field_contexts_contained_fields<'b>(
&mut self,
field_contexts: impl Iterator<Item = &'b FieldContext<'a, E, S>>,
) -> HashMap<&'a str, Vec<FieldContext<'a, E, S>>>
where
'a: 'b,
{
let mut fields = HashMap::new();
field_contexts.for_each(|field_context| {
if let Some(selection_set) = field_context.field.selection_set() {
if let Some(parent_type) = self
.schema_definition
.get_type_definition(field_context.field_definition.r#type().base_name())
{
if self.selection_set_valid(selection_set, parent_type) {
self.visit_selections_for_fields(
selection_set.iter(),
&mut fields,
parent_type,
&field_context.parent_fragments,
);
}
}
}
});
fields
}
fn visit_selections_for_fields(
&mut self,
selections: impl Iterator<Item = &'a E::Selection>,
fields: &mut HashMap<&'a str, Vec<FieldContext<'a, E, S>>>,
parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
parent_fragments: &[&'a str],
) {
selections.for_each(|selection| match selection.as_ref() {
SelectionReference::Field(field) => {
let fields_definition = parent_type.fields_definition();
if let Some(field_definition) = fields_definition
.and_then(|fields_definition| fields_definition.get(field.name()))
{
fields
.entry(field.response_name())
.or_default()
.push(FieldContext {
field,
field_definition,
parent_type,
parent_fragments: parent_fragments.to_vec(),
});
}
}
SelectionReference::FragmentSpread(fs) => {
let fragment_name = fs.name();
if !parent_fragments.contains(&fragment_name) {
if let Some(fragment_definition) = self.cache.fragment_definition(fragment_name)
{
let type_condition = fragment_definition.type_condition();
if let Some(scoped_type) =
self.schema_definition.get_type_definition(type_condition)
{
if self.selection_set_valid(
fragment_definition.selection_set(),
parent_type,
) {
let mut new_parent_fragments =
Vec::with_capacity(parent_fragments.len() + 1);
new_parent_fragments.extend_from_slice(parent_fragments);
new_parent_fragments.push(fragment_name);
self.visit_selections_for_fields(
fragment_definition.selection_set().iter(),
fields,
scoped_type,
&new_parent_fragments,
);
}
}
}
}
}
SelectionReference::InlineFragment(i) => {
let scoped_type = match i.type_condition() {
Some(type_condition) => {
self.schema_definition.get_type_definition(type_condition)
}
None => Some(parent_type),
};
if let Some(scoped_type) = scoped_type {
if self.selection_set_valid(i.selection_set(), scoped_type) {
self.visit_selections_for_fields(
i.selection_set().iter(),
fields,
scoped_type,
parent_fragments,
);
}
}
}
});
}
fn same_output_type_shape(
schema_definition: &S,
type_a: &S::OutputType,
type_b: &S::OutputType,
) -> bool {
match (
type_a.as_ref(schema_definition),
type_b.as_ref(schema_definition),
) {
(
OutputTypeReference::Base(type_a_base, type_a_required),
OutputTypeReference::Base(type_b_base, type_b_required),
) if type_a_required == type_b_required => {
!(type_a_base.is_scalar_or_enum() || type_b_base.is_scalar_or_enum())
|| type_a_base.name() == type_b_base.name()
}
(
OutputTypeReference::List(type_a_inner, type_a_required),
OutputTypeReference::List(type_b_inner, type_b_required),
) if type_a_required == type_b_required => {
Self::same_output_type_shape(schema_definition, type_a_inner, type_b_inner)
}
_ => false,
}
}
}
impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
for FieldSelectionMerging<'a, E, S>
{
type Error = Error<'a, E, S>;
type Errors = std::iter::Flatten<
std::collections::btree_map::IntoValues<Indexed<'a, E::SelectionSet>, Vec<Error<'a, E, S>>>,
>;
fn into_errors(self) -> Self::Errors {
self.cached_errors.into_values().flatten()
}
}
struct FieldContext<'a, E: ExecutableDocument, S: SchemaDefinition> {
field: &'a E::Field,
field_definition: &'a S::FieldDefinition,
parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
parent_fragments: Vec<&'a str>,
}