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