use crate::ast;
use crate::ast::FieldDefinition;
use crate::ast::InputValueDefinition;
use crate::ast::Type;
use crate::collections::IndexMap;
use crate::collections::IndexSet;
use crate::parser::SourceSpan;
use crate::schema::validation::BuiltInScalars;
use crate::schema::Component;
use crate::schema::ComponentName;
use crate::schema::InterfaceType;
use crate::schema::Name;
use crate::validation::diagnostics::DiagnosticData;
use crate::validation::field::validate_field_definitions;
use crate::validation::DiagnosticList;
use crate::Node;
pub(crate) fn validate_interface_definition(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
built_in_scalars: &mut BuiltInScalars,
interface: &Node<InterfaceType>,
) {
super::directive::validate_directives(
diagnostics,
Some(schema),
interface.directives.iter_ast(),
ast::DirectiveLocation::Interface,
Default::default(),
);
for implements_interface in &interface.implements_interfaces {
if *implements_interface == interface.name {
diagnostics.push(
implements_interface.location(),
DiagnosticData::RecursiveInterfaceDefinition {
name: implements_interface.name.clone(),
},
);
}
}
validate_field_definitions(diagnostics, schema, built_in_scalars, &interface.fields);
if interface.fields.is_empty() {
diagnostics.push(
interface.location(),
DiagnosticData::EmptyFieldSet {
type_name: interface.name.clone(),
type_location: interface.location(),
extensions_locations: interface
.extensions()
.iter()
.map(|ext| ext.location())
.collect(),
},
);
}
validate_implements_interfaces(
diagnostics,
schema,
&interface.name,
interface.location(),
&interface.implements_interfaces,
);
for implements_interface in &interface.implements_interfaces {
if let Some(super_interface) = schema.get_interface(implements_interface) {
for super_field in super_interface.fields.values() {
if interface.fields.contains_key(&super_field.name) {
continue;
}
diagnostics.push(
interface.location(),
DiagnosticData::MissingInterfaceField {
name: interface.name.clone(),
implements_location: implements_interface.location(),
interface: implements_interface.name.clone(),
field: super_field.name.clone(),
field_location: super_field.location(),
},
);
}
}
}
validate_implementation_field_types(
diagnostics,
schema,
&interface.name,
&interface.fields,
&interface.implements_interfaces,
);
validate_implementation_field_arguments(
diagnostics,
schema,
&interface.name,
&interface.fields,
&interface.implements_interfaces,
);
}
pub(crate) fn validate_implements_interfaces(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
implementor_name: &Name,
implementor_location: Option<SourceSpan>,
implements_interfaces: &IndexSet<ComponentName>,
) {
let interface_definitions = implements_interfaces
.iter()
.filter_map(|name| {
schema
.get_interface(name)
.map(|interface| (name, interface))
})
.collect::<Vec<_>>();
for interface_name in implements_interfaces {
if schema.get_interface(interface_name).is_some() {
continue;
}
let loc = interface_name.location();
diagnostics.push(
loc,
DiagnosticData::UndefinedDefinition {
name: interface_name.name.clone(),
},
);
}
let transitive_interfaces = interface_definitions.iter().flat_map(|&(name, interface)| {
interface
.implements_interfaces
.iter()
.map(|component| &component.name)
.zip(std::iter::repeat(name))
});
for (transitive_interface, via_interface) in transitive_interfaces {
if implements_interfaces.contains(transitive_interface) {
continue;
}
let transitive_loc = transitive_interface.location();
diagnostics.push(
implementor_location,
DiagnosticData::TransitiveImplementedInterfaces {
interface: implementor_name.clone(),
via_interface: via_interface.name.clone(),
missing_interface: transitive_interface.clone(),
transitive_interface_location: transitive_loc,
},
);
}
}
pub(crate) fn is_valid_implementation_field_type(
schema: &crate::Schema,
interface_field_type: &Type,
impl_field_type: &Type,
) -> bool {
match (interface_field_type, impl_field_type) {
(Type::NonNullNamed(_) | Type::NonNullList(_), Type::Named(_) | Type::List(_)) => false,
(Type::NonNullNamed(iface_name), Type::NonNullNamed(impl_name)) => {
iface_name == impl_name || schema.is_subtype(iface_name, impl_name)
}
(Type::NonNullList(iface_inner), Type::NonNullList(impl_inner)) => {
is_valid_implementation_field_type(schema, iface_inner, impl_inner)
}
(Type::NonNullNamed(_), Type::NonNullList(_))
| (Type::NonNullList(_), Type::NonNullNamed(_)) => false,
(Type::Named(iface_name), Type::Named(impl_name) | Type::NonNullNamed(impl_name)) => {
iface_name == impl_name || schema.is_subtype(iface_name, impl_name)
}
(Type::List(iface_inner), Type::List(impl_inner) | Type::NonNullList(impl_inner)) => {
is_valid_implementation_field_type(schema, iface_inner, impl_inner)
}
(Type::Named(_), Type::List(_) | Type::NonNullList(_)) => false,
(Type::List(_), Type::Named(_) | Type::NonNullNamed(_)) => false,
}
}
pub(crate) fn validate_implementation_field_types(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
implementor_name: &Name,
implementor_fields: &IndexMap<Name, Component<FieldDefinition>>,
implements_interfaces: &IndexSet<ComponentName>,
) {
for interface_name in implements_interfaces {
let Some(interface) = schema.get_interface(interface_name) else {
continue;
};
for (field_name, interface_field) in &interface.fields {
let Some(impl_field) = implementor_fields.get(field_name) else {
continue; };
if !is_valid_implementation_field_type(schema, &interface_field.ty, &impl_field.ty) {
diagnostics.push(
impl_field.location(),
DiagnosticData::InvalidImplementationFieldType {
name: implementor_name.clone(),
interface: interface_name.name.clone(),
field: field_name.clone(),
interface_type: Type::clone(&interface_field.ty),
actual_type: Type::clone(&impl_field.ty),
field_location: impl_field.location(),
interface_field_location: interface_field.location(),
},
);
}
}
}
}
fn is_required_argument(arg: &InputValueDefinition) -> bool {
arg.ty.is_non_null() && arg.default_value.is_none()
}
pub(crate) fn validate_implementation_field_arguments(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
implementor_name: &Name,
implementor_fields: &IndexMap<Name, Component<FieldDefinition>>,
implements_interfaces: &IndexSet<ComponentName>,
) {
for interface_name in implements_interfaces {
let Some(interface) = schema.get_interface(interface_name) else {
continue;
};
for (field_name, interface_field) in &interface.fields {
let Some(impl_field) = implementor_fields.get(field_name) else {
continue; };
for iface_arg in &interface_field.arguments {
let impl_arg = impl_field
.arguments
.iter()
.find(|a| a.name == iface_arg.name);
match impl_arg {
None => {
diagnostics.push(
impl_field.location(),
DiagnosticData::MissingInterfaceFieldArgument {
name: implementor_name.clone(),
interface: interface_name.name.clone(),
field: field_name.clone(),
argument: iface_arg.name.clone(),
field_location: impl_field.location(),
interface_argument_location: iface_arg.location(),
},
);
}
Some(impl_arg) => {
if *iface_arg.ty != *impl_arg.ty {
diagnostics.push(
impl_arg.location(),
DiagnosticData::InvalidImplementationFieldArgumentType {
name: implementor_name.clone(),
interface: interface_name.name.clone(),
field: field_name.clone(),
argument: iface_arg.name.clone(),
interface_type: iface_arg.ty.clone(),
actual_type: impl_arg.ty.clone(),
argument_location: impl_arg.location(),
interface_argument_location: iface_arg.location(),
},
);
}
}
}
}
for impl_arg in &impl_field.arguments {
let in_interface = interface_field
.arguments
.iter()
.any(|a| a.name == impl_arg.name);
if !in_interface && is_required_argument(impl_arg) {
diagnostics.push(
impl_arg.location(),
DiagnosticData::ExtraRequiredImplementationFieldArgument {
name: implementor_name.clone(),
interface: interface_name.name.clone(),
field: field_name.clone(),
argument: impl_arg.name.clone(),
argument_location: impl_arg.location(),
interface_field_location: interface_field.location(),
},
);
}
}
}
}
}