bool ValidateExecutableVisitor::validateInputValue()

in src/Validation.cpp [932:1263]


bool ValidateExecutableVisitor::validateInputValue(
	bool hasNonNullDefaultValue, const ValidateArgumentValuePtr& argument, const ValidateType& type)
{
	if (!type)
	{
		_errors.push_back({ "Unknown input type", argument.position });
		return false;
	}

	if (argument.value && std::holds_alternative<ValidateArgumentVariable>(argument.value->data))
	{
		if (_operationVariables)
		{
			const auto& variable = std::get<ValidateArgumentVariable>(argument.value->data);
			auto itrVariable = _operationVariables->find(variable.name);

			if (itrVariable == _operationVariables->end())
			{
				// https://spec.graphql.org/October2021/#sec-All-Variable-Uses-Defined
				std::ostringstream message;

				message << "Undefined variable name: " << variable.name;

				_errors.push_back({ message.str(), argument.position });
				return false;
			}

			_referencedVariables.emplace(variable.name);

			return validateVariableType(
				hasNonNullDefaultValue || itrVariable->second.nonNullDefaultValue,
				itrVariable->second.type,
				argument.position,
				type);
		}
		else
		{
			// In fragment definitions, variables can hold any type. It's only when we are
			// transitively visiting them through an operation definition that they are assigned a
			// type, and the type may not be exactly the same in all operations definitions which
			// reference the fragment.
			return true;
		}
	}

	const auto kind = type->get().kind();

	if (!argument.value)
	{
		// The null literal matches any nullable type and does not match a non-nullable type.
		if (kind == introspection::TypeKind::NON_NULL && !hasNonNullDefaultValue)
		{
			_errors.push_back({ "Expected Non-Null value", argument.position });
			return false;
		}

		return true;
	}

	switch (kind)
	{
		case introspection::TypeKind::NON_NULL:
		{
			// Unwrap and check the next one.
			const auto ofType = getValidateType(type->get().ofType().lock());

			if (!ofType)
			{
				_errors.push_back({ "Unknown Non-Null type", argument.position });
				return false;
			}

			return validateInputValue(hasNonNullDefaultValue, argument, ofType);
		}

		case introspection::TypeKind::LIST:
		{
			if (!std::holds_alternative<ValidateArgumentList>(argument.value->data))
			{
				_errors.push_back({ "Expected List value", argument.position });
				return false;
			}

			const auto ofType = getValidateType(type->get().ofType().lock());

			if (!ofType)
			{
				_errors.push_back({ "Unknown List type", argument.position });
				return false;
			}

			// Check every value against the target type.
			for (const auto& value : std::get<ValidateArgumentList>(argument.value->data).values)
			{
				if (!validateInputValue(false, value, ofType))
				{
					// Error messages are added in the recursive call, so just bubble up the result.
					return false;
				}
			}

			return true;
		}

		case introspection::TypeKind::INPUT_OBJECT:
		{
			const auto name = type->get().name();

			if (name.empty())
			{
				_errors.push_back({ "Unknown Input Object type", argument.position });
				return false;
			}

			if (!std::holds_alternative<ValidateArgumentMap>(argument.value->data))
			{
				std::ostringstream message;

				message << "Expected Input Object value name: " << name;

				_errors.push_back({ message.str(), argument.position });
				return false;
			}

			auto itrFields = getInputTypeFields(name);

			if (itrFields == _inputTypeFields.end())
			{
				std::ostringstream message;

				message << "Expected Input Object fields name: " << name;

				_errors.push_back({ message.str(), argument.position });
				return false;
			}

			const auto& values = std::get<ValidateArgumentMap>(argument.value->data).values;
			internal::string_view_set subFields;

			// Check every value against the target type.
			for (const auto& entry : values)
			{
				auto itrField = itrFields->second.find(entry.first);

				if (itrField == itrFields->second.end())
				{
					// https://spec.graphql.org/October2021/#sec-Input-Object-Field-Names
					std::ostringstream message;

					message << "Undefined Input Object field type: " << name
							<< " name: " << entry.first;

					_errors.push_back({ message.str(), entry.second.position });
					return false;
				}

				if (entry.second.value || !itrField->second.defaultValue)
				{
					if (!validateInputValue(itrField->second.nonNullDefaultValue,
							entry.second,
							itrField->second.type))
					{
						// Error messages are added in the recursive call, so just bubble up the
						// result.
						return false;
					}

					// The recursive call may invalidate the iterator, so reacquire it.
					itrFields = getInputTypeFields(name);
				}

				subFields.emplace(entry.first);
			}

			// See if all required fields were specified.
			for (const auto& entry : itrFields->second)
			{
				if (entry.second.defaultValue || subFields.find(entry.first) != subFields.end())
				{
					continue;
				}

				if (!entry.second.type)
				{
					std::ostringstream message;

					message << "Unknown Input Object field type: " << name
							<< " name: " << entry.first;

					_errors.push_back({ message.str(), argument.position });
					return false;
				}

				const auto fieldKind = entry.second.type->get().kind();

				if (fieldKind == introspection::TypeKind::NON_NULL)
				{
					// https://spec.graphql.org/October2021/#sec-Input-Object-Required-Fields
					std::ostringstream message;

					message << "Missing Input Object field type: " << name
							<< " name: " << entry.first;

					_errors.push_back({ message.str(), argument.position });
					return false;
				}
			}

			return true;
		}

		case introspection::TypeKind::ENUM:
		{
			const auto name = type->get().name();

			if (name.empty())
			{
				_errors.push_back({ "Unknown Enum value", argument.position });
				return false;
			}

			if (!std::holds_alternative<ValidateArgumentEnumValue>(argument.value->data))
			{
				std::ostringstream message;

				message << "Expected Enum value name: " << name;

				_errors.push_back({ message.str(), argument.position });
				return false;
			}

			const auto& value = std::get<ValidateArgumentEnumValue>(argument.value->data).value;
			auto itrEnumValues = _enumValues.find(name);

			if (itrEnumValues == _enumValues.end()
				|| itrEnumValues->second.find(value) == itrEnumValues->second.end())
			{
				std::ostringstream message;

				message << "Undefined Enum value type: " << name << " name: " << value;

				_errors.push_back({ message.str(), argument.position });
				return false;
			}

			return true;
		}

		case introspection::TypeKind::SCALAR:
		{
			const auto name = type->get().name();

			if (name.empty())
			{
				_errors.push_back({ "Unknown Scalar value", argument.position });
				return false;
			}

			if (name == R"gql(Int)gql"sv)
			{
				if (!std::holds_alternative<int>(argument.value->data))
				{
					_errors.push_back({ "Expected Int value", argument.position });
					return false;
				}
			}
			else if (name == R"gql(Float)gql"sv)
			{
				if (!std::holds_alternative<double>(argument.value->data)
					&& !std::holds_alternative<int>(argument.value->data))
				{
					_errors.push_back({ "Expected Float value", argument.position });
					return false;
				}
			}
			else if (name == R"gql(String)gql"sv)
			{
				if (!std::holds_alternative<std::string_view>(argument.value->data))
				{
					_errors.push_back({ "Expected String value", argument.position });
					return false;
				}
			}
			else if (name == R"gql(ID)gql"sv)
			{
				if (std::holds_alternative<std::string_view>(argument.value->data))
				{
					try
					{
						auto decoded = internal::Base64::fromBase64(
							std::get<std::string_view>(argument.value->data));

						return true;
					}
					catch (const std::logic_error&)
					{
						// Eat the exception and fail validation
					}
				}

				_errors.push_back({ "Expected ID value", argument.position });
				return false;
			}
			else if (name == R"gql(Boolean)gql"sv)
			{
				if (!std::holds_alternative<bool>(argument.value->data))
				{
					_errors.push_back({ "Expected Boolean value", argument.position });
					return false;
				}
			}

			if (_scalarTypes.find(name) == _scalarTypes.end())
			{
				std::ostringstream message;

				message << "Undefined Scalar type name: " << name;

				_errors.push_back({ message.str(), argument.position });
				return false;
			}

			return true;
		}

		default:
		{
			_errors.push_back({ "Unexpected value type", argument.position });
			return false;
		}
	}
}