use crate::ast;
use crate::collections::HashMap;
use crate::schema::validation::BuiltInScalars;
use crate::schema::InputObjectType;
use crate::validation::diagnostics::DiagnosticData;
use crate::validation::CycleError;
use crate::validation::DiagnosticList;
use crate::validation::RecursionGuard;
use crate::validation::RecursionStack;
use crate::Name;
use crate::Node;
struct FindRecursiveInputValue<'a> {
schema: &'a crate::Schema,
}
impl FindRecursiveInputValue<'_> {
fn input_value_definition(
&self,
seen: &mut RecursionGuard<'_>,
def: &Node<ast::InputValueDefinition>,
required_by_one_of: bool,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
let name = match &*def.ty {
ast::Type::NonNullNamed(name) => name,
ast::Type::Named(name) if required_by_one_of => name,
_ => return Ok(()),
};
if !seen.contains(name) {
if let Some(object_def) = self.schema.get_input_object(name) {
self.input_object_definition(seen.push(name)?, object_def)
.map_err(|err| err.trace(def))?
}
} else {
return Err(CycleError::Recursed(vec![def.clone()]));
}
Ok(())
}
fn input_object_definition(
&self,
mut seen: RecursionGuard<'_>,
input_object: &InputObjectType,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
if input_object.directives.has("oneOf") {
let mut cycle = None;
for input_value in input_object.fields.values() {
match self.input_value_definition(&mut seen, input_value, true) {
Ok(()) => return Ok(()),
Err(error) => cycle = Some(error),
}
}
return cycle.map_or(Ok(()), Err);
}
for input_value in input_object.fields.values() {
self.input_value_definition(&mut seen, input_value, false)?;
}
Ok(())
}
fn check(
schema: &crate::Schema,
input_object: &InputObjectType,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
let mut recursion_stack = RecursionStack::with_root(input_object.name.clone());
FindRecursiveInputValue { schema }
.input_object_definition(recursion_stack.guard(), input_object)
}
}
pub(crate) fn validate_input_object_definition(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
built_in_scalars: &mut BuiltInScalars,
input_object: &Node<InputObjectType>,
) {
super::directive::validate_directives(
diagnostics,
Some(schema),
input_object.directives.iter_ast(),
ast::DirectiveLocation::InputObject,
Default::default(),
);
if input_object.directives.has("oneOf") {
for field in input_object.fields.values() {
if field.ty.is_non_null() {
diagnostics.push(
field.ty.location(),
DiagnosticData::NonNullableOneOfField {
name: input_object.name.clone(),
field: field.name.clone(),
},
);
}
if let Some(default) = &field.default_value {
diagnostics.push(
default.location(),
DiagnosticData::OneOfFieldDefault {
name: input_object.name.clone(),
field: field.name.clone(),
},
);
}
}
}
for field in input_object.fields.values() {
if let Some(default) = &field.default_value {
super::value::value_of_correct_type(diagnostics, schema, &field.ty, default, &[]);
}
if field.is_required() {
if let Some(deprecated) = field.directives.get("deprecated") {
diagnostics.push(
deprecated.location(),
DiagnosticData::DeprecatedRequiredInputValue {
name: field.name.clone(),
describe: "input field",
},
);
}
}
}
match FindRecursiveInputValue::check(schema, input_object) {
Ok(_) => {}
Err(CycleError::Recursed(trace)) => diagnostics.push(
input_object.location(),
DiagnosticData::RecursiveInputObjectDefinition {
name: input_object.name.clone(),
trace,
},
),
Err(CycleError::Limit(_)) => {
diagnostics.push(
input_object.location(),
DiagnosticData::DeeplyNestedType {
name: input_object.name.clone(),
describe_type: "input object",
},
);
}
}
let fields: Vec<_> = input_object
.fields
.values()
.map(|c| c.node.clone())
.collect();
validate_input_value_definitions(
diagnostics,
schema,
built_in_scalars,
&fields,
ast::DirectiveLocation::InputFieldDefinition,
"an input object field",
);
if input_object.fields.is_empty() {
diagnostics.push(
input_object.location(),
DiagnosticData::EmptyInputValueSet {
type_name: input_object.name.clone(),
type_location: input_object.location(),
extensions_locations: input_object
.extensions()
.iter()
.map(|ext| ext.location())
.collect(),
},
);
}
}
pub(crate) fn validate_argument_definitions(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
built_in_scalars: &mut BuiltInScalars,
input_values: &[Node<ast::InputValueDefinition>],
directive_location: ast::DirectiveLocation,
) {
validate_input_value_definitions(
diagnostics,
schema,
built_in_scalars,
input_values,
directive_location,
"an argument",
);
let mut seen: HashMap<Name, &Node<ast::InputValueDefinition>> = HashMap::default();
for input_value in input_values {
let name = &input_value.name;
if let Some(prev_value) = seen.get(name) {
let (original_definition, redefined_definition) =
(prev_value.location(), input_value.location());
diagnostics.push(
original_definition,
DiagnosticData::UniqueInputValue {
name: name.clone(),
original_definition,
redefined_definition,
},
);
} else {
seen.insert(name.clone(), input_value);
}
}
}
pub(crate) fn validate_input_value_definitions(
diagnostics: &mut DiagnosticList,
schema: &crate::Schema,
built_in_scalars: &mut BuiltInScalars,
input_values: &[Node<ast::InputValueDefinition>],
directive_location: ast::DirectiveLocation,
describe: &'static str,
) {
for input_value in input_values {
crate::schema::validation::validate_type_system_name(
diagnostics,
&input_value.name,
describe,
);
super::directive::validate_directives(
diagnostics,
Some(schema),
input_value.directives.iter(),
directive_location,
Default::default(), );
let loc = input_value.location();
let named_type = input_value.ty.inner_named_type();
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_input_type() {
diagnostics.push(
loc,
DiagnosticData::InputType {
name: input_value.name.clone(),
describe_type: field_ty.describe(),
type_location: input_value.ty.location(),
},
);
}
} else if is_built_in {
} else {
let loc = named_type.location();
diagnostics.push(
loc,
DiagnosticData::UndefinedDefinition {
name: named_type.clone(),
},
);
}
}
}