use crate::ast;
use crate::collections::IndexMap;
use crate::coordinate::FieldArgumentCoordinate;
use crate::coordinate::TypeAttributeCoordinate;
use crate::executable;
use crate::schema;
use crate::schema::validation::BuiltInScalars;
use crate::schema::Component;
use crate::validation::diagnostics::DiagnosticData;
use crate::validation::DiagnosticList;
use crate::validation::OperationValidationContext;
use crate::ExecutableDocument;
use crate::Name;
use crate::Node;
pub(crate) fn validate_field(
diagnostics: &mut DiagnosticList,
document: &ExecutableDocument,
against_type: Option<(&crate::Schema, &ast::NamedType)>,
field: &Node<executable::Field>,
context: &mut OperationValidationContext<'_>,
) {
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 {
super::selection::validate_selection_set(
diagnostics,
document,
None,
&field.selection_set,
context,
);
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(),
expected_type: arg_definition.ty.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,
against_type,
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,
built_in_scalars: &mut BuiltInScalars,
field: &Node<ast::FieldDefinition>,
) {
crate::schema::validation::validate_type_system_name(diagnostics, &field.name, "a field");
super::directive::validate_directives(
diagnostics,
Some(schema),
field.directives.iter(),
ast::DirectiveLocation::FieldDefinition,
Default::default(),
);
super::input_object::validate_argument_definitions(
diagnostics,
schema,
built_in_scalars,
&field.arguments,
ast::DirectiveLocation::ArgumentDefinition,
);
}
pub(crate) fn validate_field_definitions(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
built_in_scalars: &mut BuiltInScalars,
fields: &IndexMap<Name, Component<ast::FieldDefinition>>,
) {
for field in fields.values() {
validate_field_definition(diagnostics, schema, built_in_scalars, field);
let loc = field.location();
let named_type = field.ty.inner_named_type();
let type_location = named_type.location();
let is_built_in = built_in_scalars.record_type_ref(schema, named_type);
if let Some(field_ty) = schema.types.get(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 if is_built_in {
} else {
diagnostics.push(
type_location,
DiagnosticData::UndefinedDefinition {
name: named_type.clone(),
},
);
}
}
}
pub(crate) fn validate_leaf_field_selection(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
parent_type: &ast::NamedType,
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: parent_type.clone(),
attribute: fname.clone(),
},
output_type: tname.clone(),
describe_type: type_def.describe(),
},
);
Err(())
} else {
Ok(())
}
}