void ValidateExecutableVisitor::visitField()

in src/Validation.cpp [1560:1830]


void ValidateExecutableVisitor::visitField(const peg::ast_node& field)
{
	peg::on_first_child<peg::directives>(field, [this](const peg::ast_node& child) {
		visitDirectives(introspection::DirectiveLocation::FIELD, child);
	});

	std::string_view name;

	peg::on_first_child<peg::field_name>(field, [&name](const peg::ast_node& child) {
		name = child.string_view();
	});

	auto itrType = getScopedTypeFields();

	if (itrType == _typeFields.end())
	{
		// https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections
		auto position = field.begin();
		std::ostringstream message;

		message << "Field on scalar type: " << _scopedType->get().name() << " name: " << name;

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

	ValidateType innerType;
	ValidateType wrappedType;

	switch (_scopedType->get().kind())
	{
		case introspection::TypeKind::OBJECT:
		case introspection::TypeKind::INTERFACE:
		{
			// https://spec.graphql.org/October2021/#sec-Field-Selections
			innerType = getFieldType(itrType->second, name);
			wrappedType = getWrappedFieldType(itrType->second, name);
			break;
		}

		case introspection::TypeKind::UNION:
		{
			if (name != R"gql(__typename)gql"sv)
			{
				// https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections
				auto position = field.begin();
				std::ostringstream message;

				message << "Field on union type: " << _scopedType->get().name()
						<< " name: " << name;

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

			// https://spec.graphql.org/October2021/#sec-Field-Selections
			innerType = getValidateType(_schema->LookupType("String"sv));
			wrappedType = getValidateType(
				_schema->WrapType(introspection::TypeKind::NON_NULL, getSharedType(innerType)));
			break;
		}

		default:
			break;
	}

	if (!innerType)
	{
		// https://spec.graphql.org/October2021/#sec-Field-Selections
		auto position = field.begin();
		std::ostringstream message;

		message << "Undefined field type: " << _scopedType->get().name() << " name: " << name;

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

	std::string_view alias;

	peg::on_first_child<peg::alias_name>(field, [&alias](const peg::ast_node& child) {
		alias = child.string_view();
	});

	if (alias.empty())
	{
		alias = name;
	}

	ValidateFieldArguments validateArguments;
	internal::string_view_map<schema_location> argumentLocations;
	std::list<std::string_view> argumentNames;

	peg::on_first_child<peg::arguments>(field,
		[this, name, &validateArguments, &argumentLocations, &argumentNames](
			const peg::ast_node& child) {
			for (auto& argument : child.children)
			{
				auto argumentName = argument->children.front()->string_view();
				auto position = argument->begin();

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

					message << "Conflicting argument type: " << _scopedType->get().name()
							<< " field: " << name << " 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);
			}
		});

	ValidateType objectType =
		(_scopedType->get().kind() == introspection::TypeKind::OBJECT ? _scopedType
																	  : ValidateType {});
	ValidateField validateField(std::move(wrappedType),
		std::move(objectType),
		name,
		std::move(validateArguments));
	auto itrValidateField = _selectionFields.find(alias);

	if (itrValidateField != _selectionFields.end())
	{
		if (itrValidateField->second == validateField)
		{
			// We already validated this field.
			return;
		}
		else
		{
			// https://spec.graphql.org/October2021/#sec-Field-Selection-Merging
			auto position = field.begin();
			std::ostringstream message;

			message << "Conflicting field type: " << _scopedType->get().name() << " name: " << name;

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

	auto itrField = itrType->second.find(name);

	if (itrField != itrType->second.end())
	{
		for (auto argumentName : argumentNames)
		{
			auto itrArgument = itrField->second.arguments.find(argumentName);

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

				message << "Undefined argument type: " << _scopedType->get().name()
						<< " field: " << name << " name: " << argumentName;

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

		for (auto& argument : itrField->second.arguments)
		{
			auto itrArgument = validateField.arguments.find(argument.first);
			const bool missing = itrArgument == validateField.arguments.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 type: " << _scopedType->get().name()
							<< " field: " << name << " 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 = field.begin();
				std::ostringstream message;

				message << (missing ? "Missing argument type: "
									: "Required non-null argument type: ")
						<< _scopedType->get().name() << " field: " << name
						<< " name: " << argument.first;

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

	_selectionFields.emplace(alias, std::move(validateField));

	const peg::ast_node* selection = nullptr;

	peg::on_first_child<peg::selection_set>(field, [&selection](const peg::ast_node& child) {
		selection = &child;
	});

	size_t subFieldCount = 0;

	if (selection != nullptr)
	{
		auto outerType = std::move(_scopedType);
		auto outerFields = std::move(_selectionFields);
		auto outerFieldCount = _fieldCount;
		auto outerIntrospectionFieldCount = _introspectionFieldCount;

		_fieldCount = 0;
		_introspectionFieldCount = 0;
		_selectionFields.clear();
		_scopedType = std::move(innerType);

		visitSelection(*selection);

		innerType = std::move(_scopedType);
		_scopedType = std::move(outerType);
		_selectionFields = std::move(outerFields);
		subFieldCount = _fieldCount;
		_introspectionFieldCount = outerIntrospectionFieldCount;
		_fieldCount = outerFieldCount;
	}

	if (subFieldCount == 0 && !isScalarType(innerType->get().kind()))
	{
		// https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections
		auto position = field.begin();
		std::ostringstream message;

		message << "Missing fields on non-scalar type: " << innerType->get().name();

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

	++_fieldCount;

	constexpr auto c_introspectionFieldPrefix = R"gql(__)gql"sv;

	if (name.size() >= c_introspectionFieldPrefix.size()
		&& name.substr(0, c_introspectionFieldPrefix.size()) == c_introspectionFieldPrefix)
	{
		++_introspectionFieldCount;
	}
}