use crate::ast;
use crate::collections::HashMap;
use crate::coordinate::TypeAttributeCoordinate;
use crate::schema::validation::BuiltInScalars;
use crate::schema::InputObjectType;
use crate::validation::diagnostics::DiagnosticData;
use crate::validation::value::value_of_correct_type;
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>,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
match &*def.ty {
ast::Type::NonNullNamed(name) => {
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 if seen.first() == Some(name) {
return Err(CycleError::Recursed(vec![def.clone()]));
}
Ok(())
}
_ => Ok(()),
}
}
fn input_object_definition(
&self,
mut seen: RecursionGuard<'_>,
input_object: &InputObjectType,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
for input_value in input_object.fields.values() {
self.input_value_definition(&mut seen, input_value)?;
}
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)
}
}
struct FindOneOfCycle<'a> {
schema: &'a crate::Schema,
}
impl FindOneOfCycle<'_> {
fn input_value_definition(
&self,
seen: &mut RecursionGuard<'_>,
is_one_of: bool,
def: &Node<ast::InputValueDefinition>,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
let name = match &*def.ty {
ast::Type::NonNullNamed(name) => name,
ast::Type::Named(name) if is_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 if seen.first() == Some(name) {
return Err(CycleError::Recursed(vec![def.clone()]));
}
Ok(())
}
fn input_object_definition(
&self,
mut seen: RecursionGuard<'_>,
input_object: &InputObjectType,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
let is_one_of = input_object.is_one_of();
if is_one_of {
let mut last_err = None;
for field in input_object.fields.values() {
match self.input_value_definition(&mut seen, is_one_of, field) {
Err(e) => last_err = Some(e),
Ok(()) => return Ok(()),
}
}
last_err.map_or(Ok(()), Err)
} else {
for field in input_object.fields.values() {
self.input_value_definition(&mut seen, is_one_of, field)?;
}
Ok(())
}
}
fn check(
schema: &crate::Schema,
input_object: &InputObjectType,
) -> Result<(), CycleError<ast::InputValueDefinition>> {
let mut recursion_stack = RecursionStack::with_root(input_object.name.clone());
FindOneOfCycle { 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::DirectiveLocation::InputObject,
Default::default(),
);
match FindRecursiveInputValue::check(schema, input_object) {
Ok(_) => match FindOneOfCycle::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",
},
);
}
},
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",
},
);
}
}
for directive in &input_object.directives.0 {
if directive.name == "oneOf" {
if let Some(ext_id) = directive.extension_id() {
diagnostics.push(
directive.location(),
DiagnosticData::OneOfDirectiveOnExtension {
type_name: input_object.name.clone(),
extension_location: ext_id.location(),
},
);
}
}
}
if input_object.is_one_of() {
for (field_name, field) in &input_object.fields {
if field.ty.is_non_null() {
diagnostics.push(
field.location(),
DiagnosticData::OneOfInputObjectFieldNonNull {
coordinate: TypeAttributeCoordinate {
ty: input_object.name.clone(),
attribute: field_name.clone(),
},
definition_location: field.location(),
},
);
}
if field.default_value.is_some() {
let default_location = field.default_value.as_ref().and_then(|v| v.location());
diagnostics.push(
field.location(),
DiagnosticData::UnsupportedDefault {
coordinate: TypeAttributeCoordinate {
ty: input_object.name.clone(),
attribute: field_name.clone(),
},
default_location,
},
);
}
}
}
let fields: Vec<_> = input_object.fields.values().cloned().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(), );
if input_value.ty.is_non_null() && input_value.default_value.is_none() {
if let Some(deprecated) = input_value.directives.get("deprecated") {
diagnostics.push(
deprecated.location(),
DiagnosticData::DeprecatedRequiredInputValue {
name: input_value.name.clone(),
describe,
definition_location: input_value.location(),
},
);
}
}
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(),
},
);
}
if schema.validate_default_values {
if let Some(default) = &input_value.default_value {
let var_defs = &[];
value_of_correct_type(
diagnostics,
schema,
&input_value.ty,
default,
var_defs,
None,
);
}
}
} else if is_built_in {
} else {
let loc = named_type.location();
diagnostics.push(
loc,
DiagnosticData::UndefinedDefinition {
name: named_type.clone(),
},
);
}
}
}