// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "ClientGenerator.h"
#include "GeneratorUtil.h"
#include "graphqlservice/internal/Version.h"
#include "graphqlservice/introspection/IntrospectionSchema.h"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 26495)
#pragma warning(disable : 26812)
#endif // _MSC_VER
#include
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
#include
#include
#include
#include
#include
#include
#include
using namespace std::literals;
namespace graphql::generator::client {
Generator::Generator(
SchemaOptions&& schemaOptions, RequestOptions&& requestOptions, GeneratorOptions&& options)
: _schemaLoader(std::move(schemaOptions))
, _requestLoader(std::move(requestOptions), _schemaLoader)
, _options(std::move(options))
, _headerDir(getHeaderDir())
, _sourceDir(getSourceDir())
, _headerPath(getHeaderPath())
, _sourcePath(getSourcePath())
{
}
std::string Generator::getHeaderDir() const noexcept
{
if (!_options.paths.headerPath.empty())
{
return std::filesystem::path { _options.paths.headerPath }.string();
}
else
{
return {};
}
}
std::string Generator::getSourceDir() const noexcept
{
if (!_options.paths.sourcePath.empty())
{
return std::filesystem::path(_options.paths.sourcePath).string();
}
else
{
return {};
}
}
std::string Generator::getHeaderPath() const noexcept
{
std::filesystem::path fullPath { _headerDir };
fullPath /= (std::string { _schemaLoader.getFilenamePrefix() } + "Client.h");
return fullPath.string();
}
std::string Generator::getSourcePath() const noexcept
{
std::filesystem::path fullPath { _sourceDir };
fullPath /= (std::string { _schemaLoader.getFilenamePrefix() } + "Client.cpp");
return fullPath.string();
}
const std::string& Generator::getClientNamespace() const noexcept
{
static const auto s_namespace = R"cpp(graphql::client)cpp"s;
return s_namespace;
}
const std::string& Generator::getOperationNamespace(const Operation& operation) const noexcept
{
static const auto s_namespaces = [this]() noexcept {
internal::string_view_map result;
const auto& operations = _requestLoader.getOperations();
result.reserve(operations.size());
for (const auto& entry : operations)
{
std::ostringstream oss;
oss << _requestLoader.getOperationType(entry) << R"cpp(::)cpp"
<< _requestLoader.getOperationNamespace(entry);
result.emplace(entry.name, oss.str());
}
return result;
}();
return s_namespaces.find(operation.name)->second;
}
std::string Generator::getResponseFieldCppType(
const ResponseField& responseField, std::string_view currentScope /* = {} */) const noexcept
{
std::string result;
switch (responseField.type->kind())
{
case introspection::TypeKind::OBJECT:
case introspection::TypeKind::INTERFACE:
case introspection::TypeKind::UNION:
{
std::ostringstream oss;
if (!currentScope.empty())
{
oss << currentScope << R"cpp(::)cpp";
}
oss << responseField.cppName << R"cpp(_)cpp" << responseField.type->name();
result = SchemaLoader::getSafeCppName(oss.str());
break;
}
default:
result = _schemaLoader.getCppType(responseField.type->name());
}
return result;
}
std::vector Generator::Build() const noexcept
{
std::vector builtFiles;
if (outputHeader() && _options.verbose)
{
builtFiles.push_back(_headerPath);
}
if (outputSource())
{
builtFiles.push_back(_sourcePath);
}
return builtFiles;
}
bool Generator::outputHeader() const noexcept
{
std::ofstream headerFile(_headerPath, std::ios_base::trunc);
IncludeGuardScope includeGuard { headerFile,
std::filesystem::path(_headerPath).filename().string() };
headerFile << R"cpp(#include "graphqlservice/GraphQLClient.h"
#include "graphqlservice/GraphQLParse.h"
#include "graphqlservice/GraphQLResponse.h"
#include "graphqlservice/internal/Version.h"
// Check if the library version is compatible with clientgen )cpp"
<< graphql::internal::MajorVersion << R"cpp(.)cpp" << graphql::internal::MinorVersion
<< R"cpp(.0
static_assert(graphql::internal::MajorVersion == )cpp"
<< graphql::internal::MajorVersion
<< R"cpp(, "regenerate with clientgen: major version mismatch");
static_assert(graphql::internal::MinorVersion == )cpp"
<< graphql::internal::MinorVersion
<< R"cpp(, "regenerate with clientgen: minor version mismatch");
#include
#include
#include
)cpp";
PendingBlankLine pendingSeparator { headerFile };
NamespaceScope clientNamespaceScope { headerFile, getClientNamespace() };
outputRequestComment(headerFile);
NamespaceScope schemaNamespaceScope { headerFile, _schemaLoader.getSchemaNamespace() };
outputGetRequestDeclaration(headerFile);
const auto& operations = _requestLoader.getOperations();
std::unordered_set declaredEnum;
for (const auto& operation : operations)
{
// Define all of the enums referenced either in variables or the response.
for (const auto& enumType : _requestLoader.getReferencedEnums(operation))
{
const auto cppType = _schemaLoader.getCppType(enumType->name());
if (!declaredEnum.insert(cppType).second)
{
continue;
}
pendingSeparator.reset();
headerFile << R"cpp(enum class [[nodiscard]] )cpp" << cppType << R"cpp(
{
)cpp";
for (const auto& enumValue : enumType->enumValues())
{
headerFile << R"cpp( )cpp" << SchemaLoader::getSafeCppName(enumValue->name())
<< R"cpp(,
)cpp";
}
headerFile << R"cpp(};
)cpp";
pendingSeparator.add();
}
}
std::unordered_set declaredInput;
std::unordered_set forwardDeclaredInput;
for (const auto& operation : operations)
{
// Define all of the input object structs referenced in variables.
for (const auto& inputType : _requestLoader.getReferencedInputTypes(operation))
{
const auto cppType = _schemaLoader.getCppType(inputType.type->name());
if (!declaredInput.insert(cppType).second)
{
continue;
}
pendingSeparator.reset();
if (!inputType.declarations.empty())
{
// Forward declare nullable dependencies
for (auto declaration : inputType.declarations)
{
if (declaredInput.find(declaration) == declaredInput.end()
&& forwardDeclaredInput.insert(declaration).second)
{
headerFile << R"cpp(struct )cpp" << declaration << R"cpp(;
)cpp";
pendingSeparator.add();
}
}
pendingSeparator.reset();
}
headerFile << R"cpp(struct [[nodiscard]] )cpp" << cppType << R"cpp(
{
explicit )cpp" << cppType
<< R"cpp(()cpp";
bool firstField = true;
for (const auto& inputField : inputType.type->inputFields())
{
if (!firstField)
{
headerFile << R"cpp(,)cpp";
}
firstField = false;
const auto inputCppType = _requestLoader.getInputCppType(inputField->type().lock());
headerFile << R"cpp(
)cpp" << inputCppType
<< R"cpp( )cpp" << SchemaLoader::getSafeCppName(inputField->name())
<< R"cpp(Arg = )cpp" << inputCppType << R"cpp( {})cpp";
}
headerFile << R"cpp() noexcept;
)cpp" << cppType << R"cpp((const )cpp"
<< cppType << R"cpp(& other);
)cpp" << cppType << R"cpp(()cpp"
<< cppType << R"cpp(&& other) noexcept;
)cpp" << cppType << R"cpp(& operator=(const )cpp"
<< cppType << R"cpp(& other);
)cpp" << cppType << R"cpp(& operator=()cpp"
<< cppType << R"cpp(&& other) noexcept;
)cpp";
for (const auto& inputField : inputType.type->inputFields())
{
headerFile << R"cpp( )cpp"
<< _requestLoader.getInputCppType(inputField->type().lock())
<< R"cpp( )cpp" << SchemaLoader::getSafeCppName(inputField->name())
<< R"cpp( {};
)cpp";
}
headerFile << R"cpp(};
)cpp";
pendingSeparator.add();
}
}
pendingSeparator.reset();
schemaNamespaceScope.exit();
pendingSeparator.add();
for (const auto& operation : operations)
{
pendingSeparator.reset();
NamespaceScope operationNamespaceScope { headerFile, getOperationNamespace(operation) };
headerFile << R"cpp(
using )cpp" << _schemaLoader.getSchemaNamespace()
<< R"cpp(::GetRequestText;
using )cpp" << _schemaLoader.getSchemaNamespace()
<< R"cpp(::GetRequestObject;
)cpp";
outputGetOperationNameDeclaration(headerFile);
// Alias all of the enums referenced either in variables or the response.
for (const auto& enumType : _requestLoader.getReferencedEnums(operation))
{
headerFile << R"cpp(using )cpp" << _schemaLoader.getSchemaNamespace() << R"cpp(::)cpp"
<< _schemaLoader.getCppType(enumType->name()) << R"cpp(;
)cpp";
pendingSeparator.add();
}
pendingSeparator.reset();
// Alias all of the input object structs referenced in variables.
for (const auto& inputType : _requestLoader.getReferencedInputTypes(operation))
{
headerFile << R"cpp(using )cpp" << _schemaLoader.getSchemaNamespace() << R"cpp(::)cpp"
<< _schemaLoader.getCppType(inputType.type->name()) << R"cpp(;
)cpp";
pendingSeparator.add();
}
pendingSeparator.reset();
const auto& variables = _requestLoader.getVariables(operation);
if (!variables.empty())
{
headerFile << R"cpp(struct [[nodiscard]] Variables
{
)cpp";
for (const auto& variable : variables)
{
headerFile << R"cpp( )cpp"
<< _requestLoader.getInputCppType(variable.inputType.type,
variable.modifiers)
<< R"cpp( )cpp" << variable.cppName << R"cpp( {};
)cpp";
}
headerFile << R"cpp(};
[[nodiscard]] response::Value serializeVariables(Variables&& variables);
)cpp";
pendingSeparator.add();
}
pendingSeparator.reset();
const auto& responseType = _requestLoader.getResponseType(operation);
headerFile << R"cpp(struct [[nodiscard]] Response
{)cpp";
pendingSeparator.add();
for (const auto& responseField : responseType.fields)
{
pendingSeparator.reset();
if (outputResponseFieldType(headerFile, responseField, 1))
{
pendingSeparator.add();
}
}
for (const auto& responseField : responseType.fields)
{
pendingSeparator.reset();
headerFile << R"cpp( )cpp"
<< RequestLoader::getOutputCppType(getResponseFieldCppType(responseField),
responseField.modifiers)
<< R"cpp( )cpp" << responseField.cppName << R"cpp( {};
)cpp";
}
headerFile << R"cpp(};
[[nodiscard]] Response parseResponse(response::Value&& response);
)cpp";
pendingSeparator.add();
}
return true;
}
void Generator::outputRequestComment(std::ostream& headerFile) const noexcept
{
headerFile << R"cpp(
///
/// Operation)cpp";
const auto& operations = _requestLoader.getOperations();
if (operations.size() > 1)
{
headerFile << R"cpp(s)cpp";
}
headerFile << R"cpp(: )cpp";
bool firstOperation = true;
for (const auto& operation : operations)
{
if (!firstOperation)
{
headerFile << R"cpp(, )cpp";
}
firstOperation = false;
headerFile << _requestLoader.getOperationType(operation) << R"cpp( )cpp"
<< _requestLoader.getOperationDisplayName(operation);
}
headerFile << R"cpp(
///
///
)cpp";
std::istringstream request { std::string { _requestLoader.getRequestText() } };
for (std::string line; std::getline(request, line);)
{
headerFile << R"cpp(/// )cpp" << line << std::endl;
}
headerFile << R"cpp(///
)cpp";
}
void Generator::outputGetRequestDeclaration(std::ostream& headerFile) const noexcept
{
headerFile << R"cpp(
// Return the original text of the request document.
[[nodiscard]] const std::string& GetRequestText() noexcept;
// Return a pre-parsed, pre-validated request object.
[[nodiscard]] const peg::ast& GetRequestObject() noexcept;
)cpp";
}
void Generator::outputGetOperationNameDeclaration(std::ostream& headerFile) const noexcept
{
headerFile << R"cpp(
// Return the name of this operation in the shared request document.
[[nodiscard]] const std::string& GetOperationName() noexcept;
)cpp";
}
bool Generator::outputResponseFieldType(std::ostream& headerFile,
const ResponseField& responseField, size_t indent /* = 0 */) const noexcept
{
switch (responseField.type->kind())
{
case introspection::TypeKind::OBJECT:
case introspection::TypeKind::INTERFACE:
case introspection::TypeKind::UNION:
// This is a complex type that requires a custom struct declaration.
break;
default:
// This is a scalar type, it doesn't require a type declaration.
return false;
}
const std::string indentTabs(indent, '\t');
std::unordered_set fieldNames;
PendingBlankLine pendingSeparator { headerFile };
headerFile << indentTabs << R"cpp(struct [[nodiscard]] )cpp"
<< getResponseFieldCppType(responseField) << R"cpp(
)cpp" << indentTabs
<< R"cpp({)cpp";
switch (responseField.type->kind())
{
case introspection::TypeKind::OBJECT:
case introspection::TypeKind::INTERFACE:
case introspection::TypeKind::UNION:
{
for (const auto& field : responseField.children)
{
pendingSeparator.reset();
if (fieldNames.emplace(field.name).second
&& outputResponseFieldType(headerFile, field, indent + 1))
{
pendingSeparator.add();
}
}
fieldNames.clear();
for (const auto& field : responseField.children)
{
pendingSeparator.reset();
if (fieldNames.emplace(field.name).second)
{
headerFile << indentTabs << R"cpp( )cpp"
<< RequestLoader::getOutputCppType(getResponseFieldCppType(field),
field.modifiers)
<< R"cpp( )cpp" << field.cppName << R"cpp( {};
)cpp";
}
}
break;
}
default:
break;
}
headerFile << indentTabs << R"cpp(};
)cpp";
return true;
}
bool Generator::outputSource() const noexcept
{
std::ofstream sourceFile(_sourcePath, std::ios_base::trunc);
sourceFile << R"cpp(// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// WARNING! Do not edit this file manually, your changes will be overwritten.
#include ")cpp" << _schemaLoader.getFilenamePrefix()
<< R"cpp(Client.h"
#include "graphqlservice/internal/SortedMap.h"
#include
#include
#include
#include
#include
#include
using namespace std::literals;
)cpp";
NamespaceScope clientNamespaceScope { sourceFile, getClientNamespace() };
NamespaceScope schemaNamespaceScope { sourceFile, _schemaLoader.getSchemaNamespace() };
PendingBlankLine pendingSeparator { sourceFile };
outputGetRequestImplementation(sourceFile);
const auto& operations = _requestLoader.getOperations();
std::unordered_set outputInputMethods;
for (const auto& operation : operations)
{
for (const auto& inputType : _requestLoader.getReferencedInputTypes(operation))
{
const auto cppType = _schemaLoader.getCppType(inputType.type->name());
if (!outputInputMethods.insert(cppType).second)
{
continue;
}
pendingSeparator.reset();
sourceFile << cppType << R"cpp(::)cpp" << cppType << R"cpp(()cpp";
bool firstField = true;
for (const auto& inputField : inputType.type->inputFields())
{
if (!firstField)
{
sourceFile << R"cpp(,)cpp";
}
firstField = false;
sourceFile << R"cpp(
)cpp" << _requestLoader.getInputCppType(inputField->type().lock())
<< R"cpp( )cpp" << SchemaLoader::getSafeCppName(inputField->name())
<< R"cpp(Arg)cpp";
}
sourceFile << R"cpp() noexcept
)cpp";
firstField = true;
for (const auto& inputField : inputType.type->inputFields())
{
sourceFile << (firstField ? R"cpp( : )cpp" : R"cpp( , )cpp");
firstField = false;
const auto name = SchemaLoader::getSafeCppName(inputField->name());
sourceFile << name << R"cpp( { std::move()cpp" << name << R"cpp(Arg) }
)cpp";
}
sourceFile << R"cpp({
}
)cpp" << cppType << R"cpp(::)cpp"
<< cppType << R"cpp((const )cpp" << cppType << R"cpp(& other)
)cpp";
firstField = true;
for (const auto& inputField : inputType.type->inputFields())
{
sourceFile << (firstField ? R"cpp( : )cpp" : R"cpp( , )cpp");
firstField = false;
const auto name = SchemaLoader::getSafeCppName(inputField->name());
const auto [type, modifiers] =
RequestLoader::unwrapSchemaType(inputField->type().lock());
sourceFile << name << R"cpp( { ModifiedVariable<)cpp"
<< _schemaLoader.getCppType(type->name()) << R"cpp(>::duplicate)cpp"
<< getTypeModifierList(modifiers) << R"cpp((other.)cpp" << name
<< R"cpp() }
)cpp";
}
sourceFile << R"cpp({
}
)cpp" << cppType << R"cpp(::)cpp"
<< cppType << R"cpp(()cpp" << cppType << R"cpp(&& other) noexcept
)cpp";
firstField = true;
for (const auto& inputField : inputType.type->inputFields())
{
sourceFile << (firstField ? R"cpp( : )cpp" : R"cpp( , )cpp");
firstField = false;
const auto name = SchemaLoader::getSafeCppName(inputField->name());
sourceFile << name << R"cpp( { std::move(other.)cpp" << name << R"cpp() }
)cpp";
}
sourceFile << R"cpp({
}
)cpp" << cppType << R"cpp(& )cpp"
<< cppType << R"cpp(::operator=(const )cpp" << cppType << R"cpp(& other)
{
)cpp" << cppType << R"cpp( value { other };
std::swap(*this, value);
return *this;
}
)cpp" << cppType << R"cpp(& )cpp"
<< cppType << R"cpp(::operator=()cpp" << cppType << R"cpp(&& other) noexcept
{
)cpp";
for (const auto& inputField : inputType.type->inputFields())
{
const auto name = SchemaLoader::getSafeCppName(inputField->name());
sourceFile << R"cpp( )cpp" << name << R"cpp( = std::move(other.)cpp" << name
<< R"cpp();
)cpp";
}
sourceFile << R"cpp(
return *this;
}
)cpp";
pendingSeparator.add();
}
}
pendingSeparator.reset();
schemaNamespaceScope.exit();
sourceFile << R"cpp(
using namespace )cpp"
<< _schemaLoader.getSchemaNamespace() << R"cpp(;
)cpp";
pendingSeparator.add();
std::unordered_set outputModifiedVariableEnum;
std::unordered_set outputModifiedVariableInput;
std::unordered_set outputModifiedResponseEnum;
for (const auto& operation : operations)
{
const auto& variables = _requestLoader.getVariables(operation);
if (!variables.empty())
{
for (const auto& enumType : _requestLoader.getReferencedEnums(operation))
{
const auto cppType = _schemaLoader.getCppType(enumType->name());
if (!outputModifiedVariableEnum.insert(cppType).second)
{
continue;
}
pendingSeparator.reset();
const auto& enumValues = enumType->enumValues();
sourceFile << R"cpp(template <>
response::Value Variable<)cpp"
<< cppType << R"cpp(>::serialize()cpp" << cppType << R"cpp(&& value)
{
static const std::array s_names = {)cpp";
bool firstValue = true;
for (const auto& enumValue : enumValues)
{
if (!firstValue)
{
sourceFile << R"cpp(,)cpp";
}
firstValue = false;
sourceFile << R"cpp(
R"gql()cpp" << enumValue->name()
<< R"cpp()gql"sv)cpp";
pendingSeparator.add();
}
pendingSeparator.reset();
sourceFile << R"cpp( };
response::Value result { response::Type::EnumValue };
result.set(std::string { s_names[static_cast(value)] });
return result;
}
)cpp";
pendingSeparator.add();
}
for (const auto& inputType : _requestLoader.getReferencedInputTypes(operation))
{
const auto cppType = _schemaLoader.getCppType(inputType.type->name());
if (!outputModifiedVariableInput.insert(cppType).second)
{
continue;
}
pendingSeparator.reset();
sourceFile << R"cpp(template <>
response::Value Variable<)cpp"
<< cppType << R"cpp(>::serialize()cpp" << cppType << R"cpp(&& inputValue)
{
response::Value result { response::Type::Map };
)cpp";
for (const auto& inputField : inputType.type->inputFields())
{
const auto [type, modifiers] =
RequestLoader::unwrapSchemaType(inputField->type().lock());
sourceFile << R"cpp( result.emplace_back(R"js()cpp" << inputField->name()
<< R"cpp()js"s, ModifiedVariable<)cpp";
sourceFile << _schemaLoader.getCppType(type->name()) << R"cpp(>::serialize)cpp"
<< getTypeModifierList(modifiers)
<< R"cpp((std::move(inputValue.)cpp"
<< SchemaLoader::getSafeCppName(inputField->name()) << R"cpp()));
)cpp";
}
sourceFile << R"cpp(
return result;
}
)cpp";
pendingSeparator.add();
}
}
for (const auto& enumType : _requestLoader.getReferencedEnums(operation))
{
const auto cppType = _schemaLoader.getCppType(enumType->name());
if (!outputModifiedResponseEnum.insert(cppType).second)
{
continue;
}
pendingSeparator.reset();
const auto& enumValues = enumType->enumValues();
sourceFile << R"cpp(template <>
)cpp" << cppType << R"cpp( Response<)cpp"
<< cppType << R"cpp(>::parse(response::Value&& value)
{
if (!value.maybe_enum())
{
throw std::logic_error { R"ex(not a valid )cpp"
<< enumType->name() << R"cpp( value)ex" };
}
static const std::array, )cpp" << enumValues.size()
<< R"cpp(> s_values = {)cpp";
std::vector> sortedValues(
enumValues.size());
std::transform(enumValues.cbegin(),
enumValues.cend(),
sortedValues.begin(),
[](const auto& value) noexcept {
return std::make_pair(value->name(),
SchemaLoader::getSafeCppName(value->name()));
});
std::sort(sortedValues.begin(),
sortedValues.end(),
[](const auto& lhs, const auto& rhs) noexcept {
return internal::shorter_or_less {}(lhs.first, rhs.first);
});
bool firstValue = true;
for (const auto& enumValue : sortedValues)
{
if (!firstValue)
{
sourceFile << R"cpp(,)cpp";
}
firstValue = false;
sourceFile << R"cpp(
std::make_pair(R"gql()cpp"
<< enumValue.first << R"cpp()gql"sv, )cpp" << cppType << R"cpp(::)cpp"
<< enumValue.second << R"cpp())cpp";
pendingSeparator.add();
}
pendingSeparator.reset();
sourceFile << R"cpp( };
const auto result = internal::sorted_map_lookup(
s_values,
std::string_view { value.get() });
if (!result)
{
throw std::logic_error { R"ex(not a valid )cpp"
<< enumType->name() << R"cpp( value)ex" };
}
return *result;
}
)cpp";
pendingSeparator.add();
}
std::ostringstream oss;
oss << getOperationNamespace(operation) << R"cpp(::Response)cpp";
const auto currentScope = oss.str();
const auto& responseType = _requestLoader.getResponseType(operation);
for (const auto& responseField : responseType.fields)
{
if (outputModifiedResponseImplementation(sourceFile, currentScope, responseField))
{
pendingSeparator.add();
}
}
pendingSeparator.reset();
NamespaceScope operationNamespaceScope { sourceFile, getOperationNamespace(operation) };
outputGetOperationNameImplementation(sourceFile, operation);
if (!variables.empty())
{
sourceFile << R"cpp(
response::Value serializeVariables(Variables&& variables)
{
response::Value result { response::Type::Map };
)cpp";
for (const auto& variable : variables)
{
sourceFile << R"cpp( result.emplace_back(R"js()cpp" << variable.name
<< R"cpp()js"s, ModifiedVariable<)cpp"
<< _schemaLoader.getCppType(variable.inputType.type->name())
<< R"cpp(>::serialize)cpp" << getTypeModifierList(variable.modifiers)
<< R"cpp((std::move(variables.)cpp" << variable.cppName << R"cpp()));
)cpp";
}
sourceFile << R"cpp(
return result;
}
)cpp";
}
sourceFile << R"cpp(
Response parseResponse(response::Value&& response)
{
Response result;
if (response.type() == response::Type::Map)
{
auto members = response.release();
for (auto& member : members)
{
)cpp";
std::unordered_set fieldNames;
for (const auto& responseField : responseType.fields)
{
if (fieldNames.emplace(responseField.name).second)
{
sourceFile << R"cpp( if (member.first == R"js()cpp" << responseField.name
<< R"cpp()js"sv)
{
result.)cpp"
<< responseField.cppName << R"cpp( = ModifiedResponse<)cpp"
<< getResponseFieldCppType(responseField, currentScope)
<< R"cpp(>::parse)cpp" << getTypeModifierList(responseField.modifiers)
<< R"cpp((std::move(member.second));
continue;
}
)cpp";
}
}
sourceFile << R"cpp( }
}
return result;
}
)cpp";
pendingSeparator.add();
}
return true;
}
void Generator::outputGetRequestImplementation(std::ostream& sourceFile) const noexcept
{
sourceFile << R"cpp(
const std::string& GetRequestText() noexcept
{
static const auto s_request = R"gql(
)cpp";
std::istringstream request { std::string { _requestLoader.getRequestText() } };
for (std::string line; std::getline(request, line);)
{
sourceFile << R"cpp( )cpp" << line << std::endl;
}
sourceFile << R"cpp( )gql"s;
return s_request;
}
const peg::ast& GetRequestObject() noexcept
{
static const auto s_request = []() noexcept {
auto ast = peg::parseString(GetRequestText());
// This has already been validated against the schema by clientgen.
ast.validated = true;
return ast;
}();
return s_request;
}
)cpp";
}
void Generator::outputGetOperationNameImplementation(
std::ostream& sourceFile, const Operation& operation) const noexcept
{
sourceFile << R"cpp(
const std::string& GetOperationName() noexcept
{
static const auto s_name = R"gql()cpp"
<< operation.name << R"cpp()gql"s;
return s_name;
}
)cpp";
}
bool Generator::outputModifiedResponseImplementation(std::ostream& sourceFile,
const std::string& outerScope, const ResponseField& responseField) const noexcept
{
std::ostringstream oss;
oss << outerScope << R"cpp(::)cpp" << getResponseFieldCppType(responseField);
const auto cppType = oss.str();
std::unordered_set fieldNames;
switch (responseField.type->kind())
{
case introspection::TypeKind::OBJECT:
case introspection::TypeKind::INTERFACE:
case introspection::TypeKind::UNION:
{
for (const auto& field : responseField.children)
{
if (fieldNames.emplace(field.name).second)
{
outputModifiedResponseImplementation(sourceFile, cppType, field);
}
}
break;
}
default:
// This is a scalar type, it doesn't require a type declaration.
return false;
}
fieldNames.clear();
// This is a complex type that requires a custom ModifiedResponse implementation.
sourceFile << R"cpp(
template <>
)cpp" << cppType
<< R"cpp( Response<)cpp" << cppType << R"cpp(>::parse(response::Value&& response)
{
)cpp" << cppType
<< R"cpp( result;
if (response.type() == response::Type::Map)
{
auto members = response.release();
for (auto& member : members)
{
)cpp";
switch (responseField.type->kind())
{
case introspection::TypeKind::OBJECT:
case introspection::TypeKind::INTERFACE:
case introspection::TypeKind::UNION:
{
for (const auto& field : responseField.children)
{
if (fieldNames.emplace(field.name).second)
{
sourceFile << R"cpp( if (member.first == R"js()cpp" << field.name
<< R"cpp()js"sv)
{
result.)cpp" << field.cppName
<< R"cpp( = ModifiedResponse<)cpp"
<< getResponseFieldCppType(field, cppType) << R"cpp(>::parse)cpp"
<< getTypeModifierList(field.modifiers)
<< R"cpp((std::move(member.second));
continue;
}
)cpp";
}
}
break;
}
default:
break;
}
sourceFile << R"cpp( }
}
return result;
}
)cpp";
return true;
}
std::string Generator::getTypeModifierList(const TypeModifierStack& modifiers) noexcept
{
if (modifiers.empty())
{
return {};
}
bool firstModifier = true;
std::ostringstream oss;
oss << '<';
for (auto modifier : modifiers)
{
if (!firstModifier)
{
oss << R"cpp(, )cpp";
}
firstModifier = false;
switch (modifier)
{
case service::TypeModifier::None:
oss << R"cpp(TypeModifier::None)cpp";
break;
case service::TypeModifier::Nullable:
oss << R"cpp(TypeModifier::Nullable)cpp";
break;
case service::TypeModifier::List:
oss << R"cpp(TypeModifier::List)cpp";
break;
}
}
oss << '>';
return oss.str();
}
} // namespace graphql::generator::client
namespace po = boost::program_options;
void outputVersion(std::ostream& ostm)
{
ostm << graphql::internal::FullVersion << std::endl;
}
void outputUsage(std::ostream& ostm, const po::options_description& options)
{
ostm << "Usage:\tclientgen [options]