in src/Validation.cpp [642:848]
void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& operationDefinition)
{
auto operationType = strQuery;
peg::on_first_child<peg::operation_type>(operationDefinition,
[&operationType](const peg::ast_node& child) {
operationType = child.string_view();
});
std::string_view operationName;
peg::on_first_child<peg::operation_name>(operationDefinition,
[&operationName](const peg::ast_node& child) {
operationName = child.string_view();
});
_operationVariables = std::make_optional<VariableTypes>();
peg::for_each_child<peg::variable>(operationDefinition,
[this, operationName](const peg::ast_node& variable) {
std::string_view variableName;
ValidateArgument variableArgument;
for (const auto& child : variable.children)
{
if (child->is_type<peg::variable_name>())
{
// Skip the $ prefix
variableName = child->string_view().substr(1);
if (_operationVariables->find(variableName) != _operationVariables->end())
{
// https://spec.graphql.org/October2021/#sec-Variable-Uniqueness
auto position = child->begin();
std::ostringstream message;
message << "Conflicting variable";
if (!operationName.empty())
{
message << " operation: " << operationName;
}
message << " name: " << variableName;
_errors.push_back({ message.str(), { position.line, position.column } });
return;
}
}
else if (child->is_type<peg::named_type>() || child->is_type<peg::list_type>()
|| child->is_type<peg::nonnull_type>())
{
ValidateVariableTypeVisitor visitor(_schema, _types);
visitor.visit(*child);
if (!visitor.isInputType())
{
// https://spec.graphql.org/October2021/#sec-Variables-Are-Input-Types
auto position = child->begin();
std::ostringstream message;
message << "Invalid variable type";
if (!operationName.empty())
{
message << " operation: " << operationName;
}
message << " name: " << variableName;
_errors.push_back({ message.str(), { position.line, position.column } });
return;
}
variableArgument.type = visitor.getType();
}
else if (child->is_type<peg::default_value>())
{
ValidateArgumentValueVisitor visitor(_errors);
visitor.visit(*child->children.back());
auto argument = visitor.getArgumentValue();
if (!validateInputValue(false, argument, variableArgument.type))
{
// https://spec.graphql.org/October2021/#sec-Values-of-Correct-Type
auto position = child->begin();
std::ostringstream message;
message << "Incompatible variable default value";
if (!operationName.empty())
{
message << " operation: " << operationName;
}
message << " name: " << variableName;
_errors.push_back({ message.str(), { position.line, position.column } });
return;
}
variableArgument.defaultValue = true;
variableArgument.nonNullDefaultValue = argument.value != nullptr;
}
}
_variableDefinitions.emplace(variableName, variable);
_operationVariables->emplace(variableName, std::move(variableArgument));
});
peg::on_first_child<peg::directives>(operationDefinition,
[this, &operationType](const peg::ast_node& child) {
auto location = introspection::DirectiveLocation::QUERY;
if (operationType == strMutation)
{
location = introspection::DirectiveLocation::MUTATION;
}
else if (operationType == strSubscription)
{
location = introspection::DirectiveLocation::SUBSCRIPTION;
}
visitDirectives(location, child);
});
auto itrType = _operationTypes.find(operationType);
if (itrType == _operationTypes.end())
{
auto position = operationDefinition.begin();
std::ostringstream error;
error << "Unsupported operation type: " << operationType;
_errors.push_back({ error.str(), { position.line, position.column } });
return;
}
_scopedType = itrType->second;
_introspectionFieldCount = 0;
_fieldCount = 0;
const auto& selection = *operationDefinition.children.back();
visitSelection(selection);
if (operationType == strSubscription)
{
if (_fieldCount > 1)
{
// https://spec.graphql.org/October2021/#sec-Single-root-field
auto position = operationDefinition.begin();
std::ostringstream error;
error << "Subscription with more than one root field";
if (!operationName.empty())
{
error << " name: " << operationName;
}
_errors.push_back({ error.str(), { position.line, position.column } });
}
if (_introspectionFieldCount != 0)
{
// https://spec.graphql.org/October2021/#sec-Single-root-field
auto position = operationDefinition.begin();
std::ostringstream error;
error << "Subscription with Introspection root field";
if (!operationName.empty())
{
error << " name: " << operationName;
}
_errors.push_back({ error.str(), { position.line, position.column } });
}
}
_scopedType.reset();
_fragmentStack.clear();
_selectionFields.clear();
for (const auto& variable : _variableDefinitions)
{
if (_referencedVariables.find(variable.first) == _referencedVariables.end())
{
// https://spec.graphql.org/October2021/#sec-All-Variables-Used
auto position = variable.second.get().begin();
std::ostringstream error;
error << "Unused variable name: " << variable.first;
_errors.push_back({ error.str(), { position.line, position.column } });
}
}
_operationVariables.reset();
_variableDefinitions.clear();
_referencedVariables.clear();
}