use crate::collections::HashSet;
use crate::executable;
use crate::validation::diagnostics::DiagnosticData;
use crate::validation::DepthCounter;
use crate::validation::DepthGuard;
use crate::validation::DiagnosticList;
use crate::validation::ExecutableValidationContext;
use crate::validation::RecursionLimitError;
use crate::ExecutableDocument;
use crate::Name;
use crate::Node;
fn walk_selections<'doc>(
document: &'doc ExecutableDocument,
selections: &'doc executable::SelectionSet,
mut f: impl FnMut(&'doc executable::Selection),
) -> Result<(), RecursionLimitError> {
fn walk_selections_inner<'doc>(
document: &'doc ExecutableDocument,
selection_set: &'doc executable::SelectionSet,
seen: &mut HashSet<&'doc Name>,
mut guard: DepthGuard<'_>,
f: &mut dyn FnMut(&'doc executable::Selection),
) -> Result<(), RecursionLimitError> {
for selection in &selection_set.selections {
f(selection);
match selection {
executable::Selection::Field(_) => {
}
executable::Selection::FragmentSpread(fragment) => {
let new = seen.insert(&fragment.fragment_name);
if !new {
continue;
}
if let Some(fragment_definition) =
document.fragments.get(&fragment.fragment_name)
{
walk_selections_inner(
document,
&fragment_definition.selection_set,
seen,
guard.increment()?,
f,
)?;
}
}
executable::Selection::InlineFragment(fragment) => {
walk_selections_inner(
document,
&fragment.selection_set,
seen,
guard.increment()?,
f,
)?;
}
}
}
Ok(())
}
let mut depth = DepthCounter::new().with_limit(500);
walk_selections_inner(
document,
selections,
&mut HashSet::default(),
depth.guard(),
&mut f,
)
}
pub(crate) fn validate_subscription(
document: &executable::ExecutableDocument,
operation: &Node<executable::Operation>,
diagnostics: &mut DiagnosticList,
) {
if !operation.is_subscription() {
return;
}
let mut field_names = vec![];
let walked = walk_selections(document, &operation.selection_set, |selection| {
if let executable::Selection::Field(field) = selection {
field_names.push(field.name.clone());
if matches!(field.name.as_str(), "__type" | "__schema" | "__typename") {
diagnostics.push(
field.location(),
executable::BuildError::SubscriptionUsesIntrospection {
name: operation.name.clone(),
field: field.name.clone(),
},
);
}
}
if let Some(conditional_directive) = selection
.directives()
.iter()
.find(|d| matches!(d.name.as_str(), "skip" | "include"))
{
diagnostics.push(
conditional_directive.location(),
executable::BuildError::SubscriptionUsesConditionalSelection {
name: operation.name.clone(),
},
);
}
});
if walked.is_err() {
diagnostics.push(None, DiagnosticData::RecursionError {});
return;
}
if field_names.len() > 1 {
diagnostics.push(
operation.location(),
executable::BuildError::SubscriptionUsesMultipleFields {
name: operation.name.clone(),
fields: field_names,
},
);
}
}
pub(crate) fn validate_operation(
diagnostics: &mut DiagnosticList,
document: &ExecutableDocument,
operation: &executable::Operation,
context: &ExecutableValidationContext<'_>,
) {
let against_type = if let Some(schema) = context.schema() {
schema
.root_operation(operation.operation_type)
.map(|ty| (schema, ty))
} else {
None
};
super::directive::validate_directives(
diagnostics,
context.schema(),
operation.directives.iter(),
operation.operation_type.into(),
&operation.variables,
);
super::variable::validate_variable_definitions(
diagnostics,
context.schema(),
&operation.variables,
);
super::variable::validate_unused_variables(diagnostics, document, operation);
super::selection::validate_selection_set(
diagnostics,
document,
against_type,
&operation.selection_set,
&mut context.operation_context(&operation.variables),
);
}
pub(crate) fn validate_operation_definitions(
diagnostics: &mut DiagnosticList,
document: &ExecutableDocument,
context: &ExecutableValidationContext<'_>,
) {
for operation in document.operations.iter() {
validate_operation(diagnostics, document, operation, context);
}
}