use crate::coordinate::{FieldArgumentCoordinate, TypeAttributeCoordinate};
use crate::validation::diagnostics::DiagnosticData;
use crate::{ast, executable, schema, ExecutableDocument, Node};
use super::operation::OperationValidationConfig;
use crate::ast::Name;
use crate::schema::Component;
use crate::validation::DiagnosticList;
use indexmap::IndexMap;
pub(crate) fn validate_field(
diagnostics: &mut DiagnosticList,
document: &ExecutableDocument,
against_type: Option<(&crate::Schema, &ast::NamedType)>,
field: &Node<executable::Field>,
context: OperationValidationConfig<'_>,
) {
super::directive::validate_directives(
diagnostics,
context.schema,
field.directives.iter(),
ast::DirectiveLocation::Field,
context.variables,
);
super::argument::validate_arguments(diagnostics, &field.arguments);
let Some((schema, against_type)) = against_type else {
return;
};
if let Ok(field_definition) = schema.type_field(against_type, &field.name) {
for argument in &field.arguments {
let arg_definition = field_definition
.arguments
.iter()
.find(|val| val.name == argument.name);
if let Some(arg_definition) = arg_definition {
if super::variable::validate_variable_usage(
diagnostics,
arg_definition,
context.variables,
argument,
)
.is_ok()
{
super::value::validate_values(
diagnostics,
schema,
&arg_definition.ty,
argument,
context.variables,
);
}
} else {
let loc = field_definition.location();
diagnostics.push(
argument.location(),
DiagnosticData::UndefinedArgument {
name: argument.name.clone(),
coordinate: TypeAttributeCoordinate {
ty: against_type.clone(),
attribute: field.name.clone(),
}
.into(),
definition_location: loc,
},
);
}
}
for arg_definition in &field_definition.arguments {
let arg_value = field.arguments.iter().find_map(|argument| {
(argument.name == arg_definition.name).then_some(&argument.value)
});
let is_null = match arg_value {
None => true,
Some(value) => value.is_null(),
};
if arg_definition.is_required() && is_null {
diagnostics.push(
field.location(),
DiagnosticData::RequiredArgument {
name: arg_definition.name.clone(),
coordinate: FieldArgumentCoordinate {
ty: against_type.clone(),
field: field.name.clone(),
argument: arg_definition.name.clone(),
}
.into(),
definition_location: arg_definition.location(),
},
);
}
}
if validate_leaf_field_selection(diagnostics, schema, field, &field_definition.ty).is_ok() {
super::selection::validate_selection_set(
diagnostics,
document,
Some((schema, field_definition.ty.inner_named_type())),
&field.selection_set,
context,
)
}
}
}
pub(crate) fn validate_field_definition(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
field: &Node<ast::FieldDefinition>,
) {
super::directive::validate_directives(
diagnostics,
Some(schema),
field.directives.iter(),
ast::DirectiveLocation::FieldDefinition,
Default::default(),
);
super::input_object::validate_argument_definitions(
diagnostics,
schema,
&field.arguments,
ast::DirectiveLocation::ArgumentDefinition,
);
}
pub(crate) fn validate_field_definitions(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
fields: &IndexMap<Name, Component<ast::FieldDefinition>>,
) {
for field in fields.values() {
validate_field_definition(diagnostics, schema, field);
let loc = field.location();
let type_location = field.ty.inner_named_type().location();
if let Some(field_ty) = schema.types.get(field.ty.inner_named_type()) {
if !field_ty.is_output_type() {
diagnostics.push(
loc,
DiagnosticData::OutputType {
name: field.name.clone(),
describe_type: field_ty.describe(),
type_location,
},
);
}
} else {
diagnostics.push(
type_location,
DiagnosticData::UndefinedDefinition {
name: field.ty.inner_named_type().clone(),
},
);
}
}
}
pub(crate) fn validate_leaf_field_selection(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
field: &Node<executable::Field>,
field_type: &ast::Type,
) -> Result<(), ()> {
let is_leaf = field.selection_set.is_empty();
let tname = field_type.inner_named_type();
let fname = &field.name;
let type_def = match schema.types.get(tname) {
Some(type_def) => type_def,
None => return Ok(()),
};
if is_leaf
&& matches!(
type_def,
schema::ExtendedType::Object(_)
| schema::ExtendedType::Interface(_)
| schema::ExtendedType::Union(_)
)
{
diagnostics.push(
field.location(),
DiagnosticData::MissingSubselection {
coordinate: TypeAttributeCoordinate {
ty: tname.clone(),
attribute: fname.clone(),
},
describe_type: type_def.describe(),
},
);
Err(())
} else {
Ok(())
}
}