void ValidateExecutableVisitor::visitDirectives()

in src/Validation.cpp [1967:2146]


void ValidateExecutableVisitor::visitDirectives(
	introspection::DirectiveLocation location, const peg::ast_node& directives)
{
	internal::string_view_set uniqueDirectives;

	for (const auto& directive : directives.children)
	{
		std::string_view directiveName;

		peg::on_first_child<peg::directive_name>(*directive,
			[&directiveName](const peg::ast_node& child) {
				directiveName = child.string_view();
			});

		const auto itrDirective = _directives.find(directiveName);

		if (itrDirective == _directives.end())
		{
			// https://spec.graphql.org/October2021/#sec-Directives-Are-Defined
			auto position = directive->begin();
			std::ostringstream message;

			message << "Undefined directive name: " << directiveName;

			_errors.push_back({ message.str(), { position.line, position.column } });
			continue;
		}

		if (!itrDirective->second.isRepeatable && !uniqueDirectives.emplace(directiveName).second)
		{
			// https://spec.graphql.org/October2021/#sec-Directives-Are-Unique-Per-Location
			auto position = directive->begin();
			std::ostringstream message;

			message << "Conflicting directive name: " << directiveName;

			_errors.push_back({ message.str(), { position.line, position.column } });
			continue;
		}

		if (itrDirective->second.locations.find(location) == itrDirective->second.locations.end())
		{
			// https://spec.graphql.org/October2021/#sec-Directives-Are-In-Valid-Locations
			auto position = directive->begin();
			std::ostringstream message;

			message << "Unexpected location for directive: " << directiveName;

			switch (location)
			{
				case introspection::DirectiveLocation::QUERY:
					message << " name: QUERY";
					break;

				case introspection::DirectiveLocation::MUTATION:
					message << " name: MUTATION";
					break;

				case introspection::DirectiveLocation::SUBSCRIPTION:
					message << " name: SUBSCRIPTION";
					break;

				case introspection::DirectiveLocation::FIELD:
					message << " name: FIELD";
					break;

				case introspection::DirectiveLocation::FRAGMENT_DEFINITION:
					message << " name: FRAGMENT_DEFINITION";
					break;

				case introspection::DirectiveLocation::FRAGMENT_SPREAD:
					message << " name: FRAGMENT_SPREAD";
					break;

				case introspection::DirectiveLocation::INLINE_FRAGMENT:
					message << " name: INLINE_FRAGMENT";
					break;

				default:
					break;
			}

			_errors.push_back({ message.str(), { position.line, position.column } });
			continue;
		}

		peg::on_first_child<peg::arguments>(*directive,
			[this, &directive, &directiveName, itrDirective](const peg::ast_node& child) {
				ValidateFieldArguments validateArguments;
				internal::string_view_map<schema_location> argumentLocations;
				std::list<std::string_view> argumentNames;

				for (auto& argument : child.children)
				{
					auto position = argument->begin();
					auto argumentName = argument->children.front()->string_view();

					if (validateArguments.find(argumentName) != validateArguments.end())
					{
						// https://spec.graphql.org/October2021/#sec-Argument-Uniqueness
						std::ostringstream message;

						message << "Conflicting argument directive: " << directiveName
								<< " name: " << argumentName;

						_errors.push_back({ message.str(), { position.line, position.column } });
						continue;
					}

					ValidateArgumentValueVisitor visitor(_errors);

					visitor.visit(*argument->children.back());
					validateArguments[argumentName] = visitor.getArgumentValue();
					argumentLocations[argumentName] = { position.line, position.column };
					argumentNames.push_back(argumentName);
				}

				for (auto argumentName : argumentNames)
				{
					auto itrArgument = itrDirective->second.arguments.find(argumentName);

					if (itrArgument == itrDirective->second.arguments.end())
					{
						// https://spec.graphql.org/October2021/#sec-Argument-Names
						std::ostringstream message;

						message << "Undefined argument directive: " << directiveName
								<< " name: " << argumentName;

						_errors.push_back({ message.str(), argumentLocations[argumentName] });
					}
				}

				for (auto& argument : itrDirective->second.arguments)
				{
					auto itrArgument = validateArguments.find(argument.first);
					const bool missing = itrArgument == validateArguments.end();

					if (!missing && itrArgument->second.value)
					{
						// The value was not null.
						if (!validateInputValue(argument.second.nonNullDefaultValue,
								itrArgument->second,
								argument.second.type))
						{
							// https://spec.graphql.org/October2021/#sec-Values-of-Correct-Type
							std::ostringstream message;

							message << "Incompatible argument directive: " << directiveName
									<< " name: " << argument.first;

							_errors.push_back({ message.str(), argumentLocations[argument.first] });
						}

						continue;
					}
					else if (argument.second.defaultValue)
					{
						// The argument has a default value.
						continue;
					}

					// See if the argument is wrapped in NON_NULL
					if (argument.second.type
						&& introspection::TypeKind::NON_NULL == argument.second.type->get().kind())
					{
						// https://spec.graphql.org/October2021/#sec-Required-Arguments
						auto position = directive->begin();
						std::ostringstream message;

						message << (missing ? "Missing argument directive: "
											: "Required non-null argument directive: ")
								<< directiveName << " name: " << argument.first;

						_errors.push_back({ message.str(), { position.line, position.column } });
					}
				}
			});
	}
}