// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "RequestLoader.h"
#include "SchemaLoader.h"
#include "Validation.h"
#include "graphqlservice/internal/Grammar.h"
#include "graphqlservice/introspection/IntrospectionSchema.h"
#include
#include
#include
#include
#include
#include
using namespace std::literals;
namespace graphql::generator {
RequestLoader::RequestLoader(RequestOptions&& requestOptions, const SchemaLoader& schemaLoader)
: _requestOptions(std::move(requestOptions))
, _schemaLoader(schemaLoader)
{
std::ifstream document { _requestOptions.requestFilename };
std::istreambuf_iterator itr { document }, itrEnd;
_requestText = std::string { itr, itrEnd };
buildSchema();
_ast = peg::parseFile(_requestOptions.requestFilename);
if (!_ast.root)
{
throw std::logic_error("Unable to parse the request document, but there was no error "
"message from the parser!");
}
validateRequest();
findOperation();
collectFragments();
for (auto& operation : _operations)
{
const peg::ast_node* selection = nullptr;
peg::on_first_child<:selection_set>(*operation.operation,
[&selection](const peg::ast_node& child) {
selection = &child;
});
if (!selection)
{
throw std::logic_error(
"Request successfully validated, but there was no selection set on "
"the operation object!");
}
SelectionVisitor visitor { _schemaLoader,
_fragments,
_schema,
operation.responseType.type };
visitor.visit(*selection);
operation.responseType.fields = visitor.getFields();
collectVariables(operation);
// Variables can reference both input types and enums.
for (const auto& variable : operation.variables)
{
collectInputTypes(operation, variable.inputType.type);
collectEnums(operation, variable.inputType.type);
}
// Handle nested input types by fully declaring the dependencies first.
reorderInputTypeDependencies(operation);
// The response can also reference enums.
for (const auto& responseField : operation.responseType.fields)
{
collectEnums(operation, responseField);
}
}
}
std::string_view RequestLoader::getRequestFilename() const noexcept
{
return _requestOptions.requestFilename;
}
const OperationList& RequestLoader::getOperations() const noexcept
{
return _operations;
}
std::string_view RequestLoader::getOperationDisplayName(const Operation& operation) const noexcept
{
return operation.name.empty() ? "(unnamed)"sv : operation.name;
}
std::string RequestLoader::getOperationNamespace(const Operation& operation) const noexcept
{
std::string result;
if (operation.name.empty())
{
result = operation.type;
result.front() = static_cast(std::toupper(result.front()));
}
else
{
result = operation.name;
}
return result;
}
std::string_view RequestLoader::getOperationType(const Operation& operation) const noexcept
{
return operation.type;
}
std::string_view RequestLoader::getRequestText() const noexcept
{
return trimWhitespace(_requestText);
}
const ResponseType& RequestLoader::getResponseType(const Operation& operation) const noexcept
{
return operation.responseType;
}
const RequestVariableList& RequestLoader::getVariables(const Operation& operation) const noexcept
{
return operation.variables;
}
const RequestInputTypeList& RequestLoader::getReferencedInputTypes(
const Operation& operation) const noexcept
{
return operation.referencedInputTypes;
}
const RequestSchemaTypeList& RequestLoader::getReferencedEnums(
const Operation& operation) const noexcept
{
return operation.referencedEnums;
}
std::string RequestLoader::getInputCppType(const RequestSchemaType& wrappedInputType) const noexcept
{
const auto [inputType, modifiers] = unwrapSchemaType(RequestSchemaType { wrappedInputType });
return getInputCppType(inputType, modifiers);
}
std::string RequestLoader::getInputCppType(
const RequestSchemaType& inputType, const TypeModifierStack& modifiers) const noexcept
{
bool nonNull = true;
size_t templateCount = 0;
std::ostringstream cppType;
for (auto modifier : modifiers)
{
if (!nonNull)
{
cppType << R"cpp(std::optional<)cpp";
++templateCount;
}
switch (modifier)
{
case service::TypeModifier::None:
nonNull = true;
break;
case service::TypeModifier::Nullable:
nonNull = false;
break;
case service::TypeModifier::List:
nonNull = true;
cppType << R"cpp(std::vector<)cpp";
++templateCount;
break;
}
}
if (!nonNull)
{
switch (inputType->kind())
{
case introspection::TypeKind::INPUT_OBJECT:
// If it's nullable, we want to return std::unique_ptr instead of std::optional for
// innermost complex types
cppType << R"cpp(std::unique_ptr<)cpp";
++templateCount;
break;
default:
cppType << R"cpp(std::optional<)cpp";
++templateCount;
break;
}
}
cppType << _schemaLoader.getCppType(inputType->name());
for (size_t i = 0; i < templateCount; ++i)
{
cppType << R"cpp(>)cpp";
}
return cppType.str();
}
std::string RequestLoader::getOutputCppType(
std::string_view outputCppType, const TypeModifierStack& modifiers) noexcept
{
bool nonNull = true;
size_t templateCount = 0;
std::ostringstream cppType;
for (auto modifier : modifiers)
{
if (!nonNull)
{
cppType << R"cpp(std::optional<)cpp";
++templateCount;
}
switch (modifier)
{
case service::TypeModifier::None:
nonNull = true;
break;
case service::TypeModifier::Nullable:
nonNull = false;
break;
case service::TypeModifier::List:
nonNull = true;
cppType << R"cpp(std::vector<)cpp";
++templateCount;
break;
}
}
if (!nonNull)
{
cppType << R"cpp(std::optional<)cpp";
++templateCount;
}
cppType << outputCppType;
for (size_t i = 0; i < templateCount; ++i)
{
cppType << R"cpp(>)cpp";
}
return cppType.str();
}
std::pair RequestLoader::unwrapSchemaType(
RequestSchemaType&& type) noexcept
{
std::pair result { std::move(type), {} };
bool wrapped = true;
bool nonNull = false;
while (wrapped)
{
switch (result.first->kind())
{
case introspection::TypeKind::NON_NULL:
nonNull = true;
result.first = { result.first->ofType().lock() };
break;
case introspection::TypeKind::LIST:
if (!nonNull)
{
result.second.push_back(service::TypeModifier::Nullable);
}
nonNull = false;
result.second.push_back(service::TypeModifier::List);
result.first = { result.first->ofType().lock() };
break;
default:
if (!nonNull)
{
result.second.push_back(service::TypeModifier::Nullable);
}
nonNull = false;
wrapped = false;
break;
}
}
return result;
}
void RequestLoader::buildSchema()
{
_schema = std::make_shared<:schema>(_requestOptions.noIntrospection);
introspection::AddTypesToSchema(_schema);
addTypesToSchema();
}
void RequestLoader::addTypesToSchema()
{
if (!_schemaLoader.getScalarTypes().empty())
{
for (const auto& scalarType : _schemaLoader.getScalarTypes())
{
_schema->AddType(scalarType.type,
schema::ScalarType::Make(scalarType.type,
scalarType.description,
scalarType.specifiedByURL));
}
}
std::map<:string_view std::shared_ptr>> enumTypes;
if (!_schemaLoader.getEnumTypes().empty())
{
for (const auto& enumType : _schemaLoader.getEnumTypes())
{
const auto itr = enumTypes
.emplace(std::make_pair(enumType.type,
schema::EnumType::Make(enumType.type, enumType.description)))
.first;
_schema->AddType(enumType.type, itr->second);
}
}
std::map<:string_view std::shared_ptr>> inputTypes;
if (!_schemaLoader.getInputTypes().empty())
{
for (const auto& inputType : _schemaLoader.getInputTypes())
{
const auto itr =
inputTypes
.emplace(std::make_pair(inputType.type,
schema::InputObjectType::Make(inputType.type, inputType.description)))
.first;
_schema->AddType(inputType.type, itr->second);
}
}
std::map<:string_view std::shared_ptr>> unionTypes;
if (!_schemaLoader.getUnionTypes().empty())
{
for (const auto& unionType : _schemaLoader.getUnionTypes())
{
const auto itr =
unionTypes
.emplace(std::make_pair(unionType.type,
schema::UnionType::Make(unionType.type, unionType.description)))
.first;
_schema->AddType(unionType.type, itr->second);
}
}
std::map<:string_view std::shared_ptr>> interfaceTypes;
if (!_schemaLoader.getInterfaceTypes().empty())
{
for (const auto& interfaceType : _schemaLoader.getInterfaceTypes())
{
const auto itr =
interfaceTypes
.emplace(std::make_pair(interfaceType.type,
schema::InterfaceType::Make(interfaceType.type, interfaceType.description)))
.first;
_schema->AddType(interfaceType.type, itr->second);
}
}
std::map<:string_view std::shared_ptr>> objectTypes;
if (!_schemaLoader.getObjectTypes().empty())
{
for (const auto& objectType : _schemaLoader.getObjectTypes())
{
const auto itr =
objectTypes
.emplace(std::make_pair(objectType.type,
schema::ObjectType::Make(objectType.type, objectType.description)))
.first;
_schema->AddType(objectType.type, itr->second);
}
}
for (const auto& enumType : _schemaLoader.getEnumTypes())
{
const auto itr = enumTypes.find(enumType.type);
if (itr != enumTypes.cend() && !enumType.values.empty())
{
std::vector<:enumvaluetype> values(enumType.values.size());
std::transform(enumType.values.cbegin(),
enumType.values.cend(),
values.begin(),
[](const EnumValueType& value) noexcept {
return schema::EnumValueType {
value.value,
value.description,
value.deprecationReason,
};
});
itr->second->AddEnumValues(std::move(values));
}
}
for (const auto& inputType : _schemaLoader.getInputTypes())
{
const auto itr = inputTypes.find(inputType.type);
if (itr != inputTypes.cend() && !inputType.fields.empty())
{
std::vector<:shared_ptr schema::inputvalue>> fields(inputType.fields.size());
std::transform(inputType.fields.cbegin(),
inputType.fields.cend(),
fields.begin(),
[this](const InputField& field) noexcept {
return schema::InputValue::Make(field.name,
field.description,
getSchemaType(field.type, field.modifiers),
field.defaultValueString);
});
itr->second->AddInputValues(std::move(fields));
}
}
for (const auto& unionType : _schemaLoader.getUnionTypes())
{
const auto itr = unionTypes.find(unionType.type);
if (!unionType.options.empty())
{
std::vector<:weak_ptr schema::basetype>> options(unionType.options.size());
std::transform(unionType.options.cbegin(),
unionType.options.cend(),
options.begin(),
[this](std::string_view option) noexcept {
return _schema->LookupType(option);
});
itr->second->AddPossibleTypes(std::move(options));
}
}
for (const auto& interfaceType : _schemaLoader.getInterfaceTypes())
{
const auto itr = interfaceTypes.find(interfaceType.type);
if (!interfaceType.fields.empty())
{
std::vector<:shared_ptr schema::field>> fields(interfaceType.fields.size());
std::transform(interfaceType.fields.cbegin(),
interfaceType.fields.cend(),
fields.begin(),
[this](const OutputField& field) noexcept {
std::vector<:shared_ptr schema::inputvalue>> arguments(
field.arguments.size());
std::transform(field.arguments.cbegin(),
field.arguments.cend(),
arguments.begin(),
[this](const InputField& argument) noexcept {
return schema::InputValue::Make(argument.name,
argument.description,
getSchemaType(argument.type, argument.modifiers),
argument.defaultValueString);
});
return schema::Field::Make(field.name,
field.description,
field.deprecationReason,
getSchemaType(field.type, field.modifiers),
std::move(arguments));
});
itr->second->AddFields(std::move(fields));
}
}
for (const auto& objectType : _schemaLoader.getObjectTypes())
{
const auto itr = objectTypes.find(objectType.type);
if (!objectType.interfaces.empty())
{
std::vector<:shared_ptr schema::interfacetype>> interfaces(
objectType.interfaces.size());
std::transform(objectType.interfaces.cbegin(),
objectType.interfaces.cend(),
interfaces.begin(),
[&interfaceTypes](std::string_view interfaceName) noexcept {
return interfaceTypes[interfaceName];
});
itr->second->AddInterfaces(std::move(interfaces));
}
if (!objectType.fields.empty())
{
std::vector<:shared_ptr schema::field>> fields(objectType.fields.size());
std::transform(objectType.fields.cbegin(),
objectType.fields.cend(),
fields.begin(),
[this](const OutputField& field) noexcept {
std::vector<:shared_ptr schema::inputvalue>> arguments(
field.arguments.size());
std::transform(field.arguments.cbegin(),
field.arguments.cend(),
arguments.begin(),
[this](const InputField& argument) noexcept {
return schema::InputValue::Make(argument.name,
argument.description,
getSchemaType(argument.type, argument.modifiers),
argument.defaultValueString);
});
return schema::Field::Make(field.name,
field.description,
field.deprecationReason,
getSchemaType(field.type, field.modifiers),
std::move(arguments));
});
itr->second->AddFields(std::move(fields));
}
}
for (const auto& directive : _schemaLoader.getDirectives())
{
std::vector<:directivelocation> locations(directive.locations.size());
std::transform(directive.locations.cbegin(),
directive.locations.cend(),
locations.begin(),
[](std::string_view locationName) noexcept {
response::Value locationValue(response::Type::EnumValue);
locationValue.set<:string>(std::string { locationName });
return service::Argument<:directivelocation>::convert(
locationValue);
});
std::vector<:shared_ptr schema::inputvalue>> arguments(
directive.arguments.size());
std::transform(directive.arguments.cbegin(),
directive.arguments.cend(),
arguments.begin(),
[this](const InputField& argument) noexcept {
return schema::InputValue::Make(argument.name,
argument.description,
getSchemaType(argument.type, argument.modifiers),
argument.defaultValueString);
});
_schema->AddDirective(schema::Directive::Make(directive.name,
directive.description,
std::move(locations),
std::move(arguments),
directive.isRepeatable));
}
for (const auto& operationType : _schemaLoader.getOperationTypes())
{
const auto itr = objectTypes.find(operationType.type);
if (operationType.operation == service::strQuery)
{
_schema->AddQueryType(itr->second);
}
else if (operationType.operation == service::strMutation)
{
_schema->AddMutationType(itr->second);
}
else if (operationType.operation == service::strSubscription)
{
_schema->AddSubscriptionType(itr->second);
}
}
}
RequestSchemaType RequestLoader::getSchemaType(
std::string_view type, const TypeModifierStack& modifiers) const noexcept
{
RequestSchemaType introspectionType = _schema->LookupType(type);
if (introspectionType)
{
bool nonNull = true;
for (auto itr = modifiers.crbegin(); itr != modifiers.crend(); ++itr)
{
if (nonNull)
{
switch (*itr)
{
case service::TypeModifier::None:
case service::TypeModifier::List:
introspectionType = _schema->WrapType(introspection::TypeKind::NON_NULL,
std::move(introspectionType));
break;
case service::TypeModifier::Nullable:
// If the next modifier is Nullable that cancels the non-nullable state.
nonNull = false;
break;
}
}
switch (*itr)
{
case service::TypeModifier::None:
{
nonNull = true;
break;
}
case service::TypeModifier::List:
{
nonNull = true;
introspectionType = _schema->WrapType(introspection::TypeKind::LIST,
std::move(introspectionType));
break;
}
case service::TypeModifier::Nullable:
break;
}
}
if (nonNull)
{
introspectionType =
_schema->WrapType(introspection::TypeKind::NON_NULL, std::move(introspectionType));
}
}
return introspectionType;
}
void RequestLoader::validateRequest() const
{
service::ValidateExecutableVisitor validation { _schema };
validation.visit(*_ast.root);
auto errors = validation.getStructuredErrors();
if (!errors.empty())
{
throw service::schema_exception { std::move(errors) };
}
}
std::string_view RequestLoader::trimWhitespace(std::string_view content) noexcept
{
const auto isSpacePredicate = [](char ch) noexcept {
return std::isspace(static_cast(ch)) != 0;
};
const auto skip = std::distance(content.begin(),
std::find_if_not(content.begin(), content.end(), isSpacePredicate));
const auto length =
std::distance(std::find_if_not(content.rbegin(), content.rend(), isSpacePredicate),
content.rend());
if (skip >= 0 && length >= skip)
{
content = content.substr(static_cast(skip), static_cast(length - skip));
}
return content;
}
void RequestLoader::findOperation()
{
peg::on_first_child_if<:operation_definition>(*_ast.root,
[this](const peg::ast_node& operationDefinition) noexcept -> bool {
std::string_view operationType = service::strQuery;
peg::on_first_child<:operation_type>(operationDefinition,
[&operationType](const peg::ast_node& child) {
operationType = child.string_view();
});
std::string_view name;
peg::on_first_child<:operation_name>(operationDefinition,
[&name](const peg::ast_node& child) {
name = child.string_view();
});
if (!_requestOptions.operationName || name == *_requestOptions.operationName)
{
Operation operation { &operationDefinition, name, operationType };
_operations.push_back(std::move(operation));
if (_requestOptions.operationName)
{
return true;
}
}
return false;
});
if (_operations.empty())
{
std::ostringstream message;
message << "Missing operation";
if (_requestOptions.operationName && !_requestOptions.operationName->empty())
{
message << " name: " << *_requestOptions.operationName;
}
throw service::schema_exception { { message.str() } };
}
std::list<:schema_error> errors;
for (auto& operation : _operations)
{
if (operation.type == service::strQuery)
{
operation.responseType.type = _schema->queryType();
}
else if (operation.type == service::strMutation)
{
operation.responseType.type = _schema->mutationType();
}
else if (operation.type == service::strSubscription)
{
operation.responseType.type = _schema->subscriptionType();
}
if (!operation.responseType.type)
{
std::ostringstream message;
const auto position = operation.operation->begin();
message << "Unsupported operation type: " << operation.type;
if (!operation.name.empty())
{
message << " name: " << operation.name;
}
service::schema_error error { message.str(),
service::schema_location { position.line, position.column } };
errors.push_back(std::move(error));
continue;
}
operation.responseType.cppType = SchemaLoader::getSafeCppName(
operation.name.empty() ? operation.responseType.type->name() : operation.name);
}
if (!errors.empty())
{
throw service::schema_exception { std::move(errors) };
}
}
void RequestLoader::collectFragments() noexcept
{
peg::for_each_child<:fragment_definition>(*_ast.root, [this](const peg::ast_node& child) {
_fragments.emplace(child.children.front()->string_view(), &child);
});
}
void RequestLoader::collectVariables(Operation& operation) noexcept
{
peg::for_each_child<:variable>(*operation.operation,
[this, &operation](const peg::ast_node& variableDefinition) {
RequestVariable variable;
TypeVisitor variableType;
service::schema_location defaultValueLocation;
for (const auto& child : variableDefinition.children)
{
if (child->is_type<:variable_name>())
{
// Skip the $ prefix
variable.name = child->string_view().substr(1);
variable.cppName = _schemaLoader.getSafeCppName(variable.name);
}
else if (child->is_type<:named_type>() || child->is_type<:list_type>()
|| child->is_type<:nonnull_type>())
{
variableType.visit(*child);
}
else if (child->is_type<:default_value>())
{
const auto position = child->begin();
DefaultValueVisitor defaultValue;
defaultValue.visit(*child->children.back());
variable.defaultValue = defaultValue.getValue();
variable.defaultValueString = child->children.back()->string_view();
defaultValueLocation = { position.line, position.column };
}
}
const auto [type, modifiers] = variableType.getType();
variable.inputType.type = _schema->LookupType(type);
variable.modifiers = modifiers;
if (!variable.defaultValueString.empty()
&& variable.defaultValue.type() == response::Type::Null
&& (modifiers.empty() || modifiers.front() != service::TypeModifier::Nullable))
{
std::ostringstream error;
error << "Expected Non-Null default value for variable name: " << variable.name;
throw service::schema_exception {
{ service::schema_error { error.str(), std::move(defaultValueLocation) } }
};
}
variable.position = variableDefinition.begin();
operation.variables.push_back(std::move(variable));
});
}
void RequestLoader::collectInputTypes(
Operation& operation, const RequestSchemaType& variableType) noexcept
{
switch (variableType->kind())
{
case introspection::TypeKind::INPUT_OBJECT:
{
if (operation.inputTypeNames.emplace(variableType->name()).second)
{
operation.referencedInputTypes.push_back({ variableType });
// Input types can reference other input types and enums.
for (const auto& inputField : variableType->inputFields())
{
const auto fieldType = inputField->type().lock();
collectInputTypes(operation, fieldType);
collectEnums(operation, fieldType);
}
}
break;
}
case introspection::TypeKind::LIST:
case introspection::TypeKind::NON_NULL:
{
collectInputTypes(operation, { variableType->ofType().lock() });
break;
}
default:
break;
}
}
void RequestLoader::reorderInputTypeDependencies(Operation& operation)
{
if (operation.referencedInputTypes.empty())
{
return;
}
// Build the dependency list for each input type.
std::for_each(operation.referencedInputTypes.begin(),
operation.referencedInputTypes.end(),
[](RequestInputType& entry) noexcept {
const auto& fields = entry.type->inputFields();
std::for_each(fields.begin(),
fields.end(),
[&entry](const std::shared_ptr& field) noexcept {
const auto [inputType, modifiers] = unwrapSchemaType(field->type().lock());
if (inputType->kind() == introspection::TypeKind::INPUT_OBJECT)
{
// https://spec.graphql.org/October2021/#sec-Input-Objects.Circular-References
if (!modifiers.empty() && modifiers.front() != service::TypeModifier::None)
{
entry.declarations.push_back(inputType->name());
}
else
{
entry.dependencies.insert(inputType->name());
}
}
});
});
std::unordered_set<:string_view> handled;
auto itr = operation.referencedInputTypes.begin();
while (itr != operation.referencedInputTypes.end())
{
// Put all of the input types without unhandled dependencies at the front.
const auto itrDependent = std::stable_partition(itr,
operation.referencedInputTypes.end(),
[&handled](const RequestInputType& entry) noexcept {
return std::find_if(entry.dependencies.cbegin(),
entry.dependencies.cend(),
[&handled](std::string_view dependency) noexcept {
return handled.find(dependency) == handled.cend();
})
== entry.dependencies.cend();
});
// Check to make sure we made progress. This should already be guaranteed by the same check
// in SchemaLoader::reorderInputTypeDependencies, which will throw an exception if there are
// any cycles in the full set of input types. This is only validating a sub-set of those
// input types which are referenced in the request.
if (itrDependent == itr)
{
std::ostringstream error;
error << "Input object cycle type: " << itr->type;
throw std::logic_error(error.str());
}
if (itrDependent != operation.referencedInputTypes.end())
{
std::for_each(itr, itrDependent, [&handled](const RequestInputType& entry) noexcept {
handled.insert(entry.type->name());
});
}
itr = itrDependent;
}
}
void RequestLoader::collectEnums(
Operation& operation, const RequestSchemaType& variableType) noexcept
{
switch (variableType->kind())
{
case introspection::TypeKind::ENUM:
{
if (operation.enumNames.emplace(variableType->name()).second)
{
operation.referencedEnums.push_back(variableType);
}
break;
}
case introspection::TypeKind::LIST:
case introspection::TypeKind::NON_NULL:
{
collectEnums(operation, variableType->ofType().lock());
break;
}
default:
break;
}
}
void RequestLoader::collectEnums(Operation& operation, const ResponseField& responseField) noexcept
{
switch (responseField.type->kind())
{
case introspection::TypeKind::ENUM:
{
if (operation.enumNames.emplace(responseField.type->name()).second)
{
operation.referencedEnums.push_back(responseField.type);
}
break;
}
case introspection::TypeKind::OBJECT:
case introspection::TypeKind::INTERFACE:
case introspection::TypeKind::UNION:
{
for (const auto& field : responseField.children)
{
collectEnums(operation, field);
}
break;
}
default:
break;
}
}
RequestLoader::SelectionVisitor::SelectionVisitor(const SchemaLoader& schemaLoader,
const FragmentDefinitionMap& fragments, const std::shared_ptr<:schema>& schema,
const RequestSchemaType& type)
: _schemaLoader(schemaLoader)
, _fragments(fragments)
, _schema(schema)
, _type(type)
{
}
ResponseFieldList RequestLoader::SelectionVisitor::getFields()
{
auto fields = std::move(_fields);
return fields;
}
void RequestLoader::SelectionVisitor::visit(const peg::ast_node& selection)
{
for (const auto& child : selection.children)
{
if (child->is_type<:field>())
{
visitField(*child);
}
else if (child->is_type<:fragment_spread>())
{
visitFragmentSpread(*child);
}
else if (child->is_type<:inline_fragment>())
{
visitInlineFragment(*child);
}
}
}
void RequestLoader::SelectionVisitor::visitField(const peg::ast_node& field)
{
std::string_view name;
peg::on_first_child<:field_name>(field, [&name](const peg::ast_node& child) {
name = child.string_view();
});
std::string_view alias;
peg::on_first_child<:alias_name>(field, [&alias](const peg::ast_node& child) {
alias = child.string_view();
});
if (alias.empty())
{
alias = name;
}
if (!_names.emplace(alias).second)
{
// Skip fields which map to the same response name as a field we've already visited.
// Validation should handle merging multiple references to the same field or to
// compatible fields.
return;
}
ResponseField responseField;
responseField.name = alias;
responseField.cppName = SchemaLoader::getSafeCppName(alias);
responseField.position = field.begin();
// Special case to handle __typename on any ResponseType
if (name == R"gql(__typename)gql"sv)
{
responseField.type = _schema->LookupType("String"sv);
_fields.push_back(std::move(responseField));
return;
}
if (_schema->supportsIntrospection() && _type == _schema->queryType())
{
if (name == R"gql(__schema)gql"sv)
{
responseField.type = _schema->LookupType("__Schema"sv);
}
else if (name == R"gql(__type)gql"sv)
{
responseField.type = _schema->LookupType("__Type"sv);
responseField.modifiers = { service::TypeModifier::Nullable };
}
}
if (!responseField.type)
{
const auto& typeFields = _type->fields();
const auto itr = std::find_if(typeFields.begin(),
typeFields.end(),
[name](const std::shared_ptr& typeField) noexcept {
return typeField->name() == name;
});
if (itr == typeFields.end())
{
// Skip fields that are not found on the current type.
return;
}
responseField.type = (*itr)->type().lock();
}
auto [unwrappedType, unwrappedModifiers] = unwrapSchemaType(std::move(responseField.type));
responseField.type = std::move(unwrappedType);
responseField.modifiers = std::move(unwrappedModifiers);
const peg::ast_node* selection = nullptr;
peg::on_first_child<:selection_set>(field, [&selection](const peg::ast_node& child) {
selection = &child;
});
if (selection)
{
switch (responseField.type->kind())
{
case introspection::TypeKind::OBJECT:
case introspection::TypeKind::INTERFACE:
case introspection::TypeKind::UNION:
{
SelectionVisitor selectionVisitor { _schemaLoader,
_fragments,
_schema,
responseField.type };
selectionVisitor.visit(*selection);
auto selectionFields = selectionVisitor.getFields();
if (!selectionFields.empty())
{
responseField.children = std::move(selectionFields);
}
break;
}
default:
break;
}
}
_fields.push_back(std::move(responseField));
}
void RequestLoader::SelectionVisitor::visitFragmentSpread(const peg::ast_node& fragmentSpread)
{
const auto name = fragmentSpread.children.front()->string_view();
auto itr = _fragments.find(name);
if (itr == _fragments.end())
{
auto position = fragmentSpread.begin();
std::ostringstream error;
error << "Unknown fragment name: " << name;
throw service::schema_exception {
{ service::schema_error { error.str(), { position.line, position.column } } }
};
}
const auto typeCondition = itr->second->children[1].get();
const auto fragmentType = _schema->LookupType(typeCondition->children.front()->string_view());
const auto& selection = *(itr->second->children.back());
SelectionVisitor selectionVisitor { _schemaLoader, _fragments, _schema, fragmentType };
selectionVisitor.visit(selection);
mergeFragmentFields(selectionVisitor.getFields());
}
void RequestLoader::SelectionVisitor::visitInlineFragment(const peg::ast_node& inlineFragment)
{
const peg::ast_node* typeCondition = nullptr;
peg::on_first_child<:type_condition>(inlineFragment,
[&typeCondition](const peg::ast_node& child) {
typeCondition = &child;
});
peg::on_first_child<:selection_set>(inlineFragment,
[this, typeCondition](const peg::ast_node& child) {
const auto fragmentType = typeCondition
? _schema->LookupType(typeCondition->children.front()->string_view())
: _type;
const auto& selection = child;
SelectionVisitor selectionVisitor { _schemaLoader, _fragments, _schema, fragmentType };
selectionVisitor.visit(selection);
mergeFragmentFields(selectionVisitor.getFields());
});
}
void RequestLoader::SelectionVisitor::mergeFragmentFields(
ResponseFieldList&& fragmentFields) noexcept
{
fragmentFields.erase(std::remove_if(fragmentFields.begin(),
fragmentFields.end(),
[this](const ResponseField& fragmentField) noexcept {
return !_names.emplace(fragmentField.name).second;
}),
fragmentFields.cend());
if (!fragmentFields.empty())
{
_fields.reserve(_fields.size() + fragmentFields.size());
for (auto& fragmentField : fragmentFields)
{
_fields.push_back(std::move(fragmentField));
}
}
}
} // namespace graphql::generator