bluejay-validator 0.4.0

A GraphQL validator
Documentation
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;
        }

        // Fast path: check if all fields share the same parent type (common case)
        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()
                    }
                    // Interface fields are from the abstract type itself, not a specific
                    // concrete type, so all interface parents are treated as the same group.
                    (
                        TypeDefinitionReference::Interface(_),
                        TypeDefinitionReference::Interface(_),
                    ) => true,
                    _ => false,
                });

        if all_same_parent {
            // All fields are from the same parent — treat as a single group
            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>,
}