void ValidateExecutableVisitor::visitOperationDefinition()

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();
}