Skip to content
21 changes: 17 additions & 4 deletions include/RequestLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "graphqlservice/internal/Grammar.h"
#include "graphqlservice/internal/Schema.h"

#include <unordered_set>

namespace graphql::generator {

using RequestSchemaType = std::shared_ptr<const schema::BaseType>;
Expand All @@ -40,9 +42,18 @@ struct ResponseField
ResponseFieldList children;
};

struct RequestVariable
struct RequestInputType
{
RequestSchemaType type;
std::unordered_set<std::string_view> dependencies {};
std::vector<std::string_view> declarations {};
};

using RequestInputTypeList = std::vector<RequestInputType>;

struct RequestVariable
{
RequestInputType inputType;
TypeModifierStack modifiers;
std::string_view name;
std::string_view cppName;
Expand Down Expand Up @@ -76,10 +87,12 @@ class RequestLoader
const ResponseType& getResponseType() const noexcept;
const RequestVariableList& getVariables() const noexcept;

const RequestSchemaTypeList& getReferencedInputTypes() const noexcept;
const RequestInputTypeList& getReferencedInputTypes() const noexcept;
const RequestSchemaTypeList& getReferencedEnums() const noexcept;

std::string getInputCppType(const RequestSchemaType& wrappedInputType) const noexcept;
std::string getInputCppType(const RequestSchemaType& inputType,
const TypeModifierStack& modifiers) const noexcept;
static std::string getOutputCppType(
std::string_view outputCppType, const TypeModifierStack& modifiers) noexcept;

Expand All @@ -99,7 +112,7 @@ class RequestLoader
void collectFragments() noexcept;
void collectVariables() noexcept;
void collectInputTypes(const RequestSchemaType& variableType) noexcept;
void reorderInputTypeDependencies() noexcept;
void reorderInputTypeDependencies();
void collectEnums(const RequestSchemaType& variableType) noexcept;
void collectEnums(const ResponseField& responseField) noexcept;

Expand Down Expand Up @@ -146,7 +159,7 @@ class RequestLoader
ResponseType _responseType;
RequestVariableList _variables;
internal::string_view_set _inputTypeNames;
RequestSchemaTypeList _referencedInputTypes;
RequestInputTypeList _referencedInputTypes;
internal::string_view_set _enumNames;
RequestSchemaTypeList _referencedEnums;
};
Expand Down
1 change: 1 addition & 0 deletions include/SchemaLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ struct InputType
InputFieldList fields;
std::string_view description;
std::unordered_set<std::string_view> dependencies {};
std::vector<std::string_view> declarations {};
};

using InputTypeList = std::vector<InputType>;
Expand Down
19 changes: 17 additions & 2 deletions include/graphqlservice/GraphQLClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,32 @@ enum class TypeModifier
List,
};

// Specialized to return true for all INPUT_OBJECT types.
template <typename Type>
constexpr bool isInputType() noexcept
{
return false;
}

// Serialize variable input values with chained type modifiers which add nullable or list wrappers.
template <typename Type>
struct ModifiedVariable
{
// Special-case an innermost nullable INPUT_OBJECT type.
template <TypeModifier... Other>
static constexpr bool onlyNoneModifiers() noexcept
{
return (... && (Other == TypeModifier::None));
}

// Peel off modifiers until we get to the underlying type.
template <typename U, TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
struct VariableTraits
{
// Peel off modifiers until we get to the underlying type.
using type = typename std::conditional_t<TypeModifier::Nullable == Modifier,
std::optional<typename VariableTraits<U, Other...>::type>,
typename std::conditional_t<isInputType<U>() && onlyNoneModifiers<Other...>(),
std::unique_ptr<U>, std::optional<typename VariableTraits<U, Other...>::type>>,
typename std::conditional_t<TypeModifier::List == Modifier,
std::vector<typename VariableTraits<U, Other...>::type>, U>>;
};
Expand Down Expand Up @@ -115,7 +130,7 @@ struct ModifiedVariable
if (nullableValue)
{
result = serialize<Other...>(std::move(*nullableValue));
nullableValue = std::nullopt;
nullableValue.reset();
}

return result;
Expand Down
28 changes: 25 additions & 3 deletions include/graphqlservice/GraphQLService.h
Original file line number Diff line number Diff line change
Expand Up @@ -591,20 +591,35 @@ enum class TypeModifier
List,
};

// Specialized to return true for all INPUT_OBJECT types.
template <typename Type>
constexpr bool isInputType() noexcept
{
return false;
}

// Extract individual arguments with chained type modifiers which add nullable or list wrappers.
// If the argument is not optional, use require and let it throw a schema_exception when the
// argument is missing or not the correct type. If it's optional, use find and check the second
// element in the pair to see if it was found or if you just got the default value for that type.
template <typename Type>
struct ModifiedArgument
{
// Special-case an innermost nullable INPUT_OBJECT type.
template <TypeModifier... Other>
static constexpr bool onlyNoneModifiers() noexcept
{
return (... && (Other == TypeModifier::None));
}

// Peel off modifiers until we get to the underlying type.
template <typename U, TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
struct ArgumentTraits
{
// Peel off modifiers until we get to the underlying type.
using type = typename std::conditional_t<TypeModifier::Nullable == Modifier,
std::optional<typename ArgumentTraits<U, Other...>::type>,
typename std::conditional_t<isInputType<U>() && onlyNoneModifiers<Other...>(),
std::unique_ptr<U>, std::optional<typename ArgumentTraits<U, Other...>::type>>,
typename std::conditional_t<TypeModifier::List == Modifier,
std::vector<typename ArgumentTraits<U, Other...>::type>, U>>;
};
Expand Down Expand Up @@ -678,12 +693,19 @@ struct ModifiedArgument
if (valueItr == arguments.get<response::MapType>().cend()
|| valueItr->second.type() == response::Type::Null)
{
return std::nullopt;
return {};
}

auto result = require<Other...>(name, arguments);

return std::make_optional<decltype(result)>(std::move(result));
if constexpr (isInputType<Type>() && onlyNoneModifiers<Other...>())
{
return std::make_unique<decltype(result)>(std::move(result));
}
else
{
return std::make_optional<decltype(result)>(std::move(result));
}
}

// Peel off list modifiers.
Expand Down
1 change: 1 addition & 0 deletions samples/client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ cmake_minimum_required(VERSION 3.15)
add_subdirectory(query)
add_subdirectory(mutate)
add_subdirectory(subscribe)
add_subdirectory(nestedinput)

add_subdirectory(benchmark)

Expand Down
10 changes: 8 additions & 2 deletions samples/client/mutate/MutateClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ static const std::array<std::string_view, 4> s_namesTaskState = {
"Unassigned"sv,
};

template <>
constexpr bool isInputType<Variables::CompleteTaskInput>() noexcept
{
return true;
}

template <>
response::Value ModifiedVariable<TaskState>::serialize(TaskState&& value)
{
Expand Down Expand Up @@ -132,7 +138,7 @@ const std::string& GetRequestText() noexcept
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

mutation CompleteTaskMutation($input: CompleteTaskInput! = {id: "ZmFrZVRhc2tJZA==", isComplete: true, clientMutationId: "Hi There!"}, $skipClientMutationId: Boolean!) {
mutation CompleteTaskMutation($input: CompleteTaskInput = {id: "ZmFrZVRhc2tJZA==", isComplete: true, clientMutationId: "Hi There!"}, $skipClientMutationId: Boolean!) {
completedTask: completeTask(input: $input) {
completedTask: task {
completedTaskId: id
Expand Down Expand Up @@ -165,7 +171,7 @@ response::Value serializeVariables(Variables&& variables)
{
response::Value result { response::Type::Map };

result.emplace_back(R"js(input)js"s, ModifiedVariable<Variables::CompleteTaskInput>::serialize(std::move(variables.input)));
result.emplace_back(R"js(input)js"s, ModifiedVariable<Variables::CompleteTaskInput>::serialize<TypeModifier::Nullable>(std::move(variables.input)));
result.emplace_back(R"js(skipClientMutationId)js"s, ModifiedVariable<bool>::serialize(std::move(variables.skipClientMutationId)));

return result;
Expand Down
4 changes: 2 additions & 2 deletions samples/client/mutate/MutateClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static_assert(graphql::internal::MinorVersion == 2, "regenerate with clientgen:
/// # Copyright (c) Microsoft Corporation. All rights reserved.
/// # Licensed under the MIT License.
///
/// mutation CompleteTaskMutation($input: CompleteTaskInput! = {id: "ZmFrZVRhc2tJZA==", isComplete: true, clientMutationId: "Hi There!"}, $skipClientMutationId: Boolean!) {
/// mutation CompleteTaskMutation($input: CompleteTaskInput = {id: "ZmFrZVRhc2tJZA==", isComplete: true, clientMutationId: "Hi There!"}, $skipClientMutationId: Boolean!) {
/// completedTask: completeTask(input: $input) {
/// completedTask: task {
/// completedTaskId: id
Expand Down Expand Up @@ -66,7 +66,7 @@ struct Variables
std::optional<std::string> clientMutationId {};
};

CompleteTaskInput input {};
std::unique_ptr<CompleteTaskInput> input {};
bool skipClientMutationId {};
};

Expand Down
2 changes: 1 addition & 1 deletion samples/client/mutate/mutate.today.graphql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

mutation CompleteTaskMutation($input: CompleteTaskInput! = {id: "ZmFrZVRhc2tJZA==", isComplete: true, clientMutationId: "Hi There!"}, $skipClientMutationId: Boolean!) {
mutation CompleteTaskMutation($input: CompleteTaskInput = {id: "ZmFrZVRhc2tJZA==", isComplete: true, clientMutationId: "Hi There!"}, $skipClientMutationId: Boolean!) {
completedTask: completeTask(input: $input) {
completedTask: task {
completedTaskId: id
Expand Down
13 changes: 13 additions & 0 deletions samples/client/nestedinput/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

cmake_minimum_required(VERSION 3.15)

# Normally this would be handled by find_package(cppgraphqlgen CONFIG).
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/cppgraphqlgen-functions.cmake)

if(GRAPHQL_UPDATE_SAMPLES AND GRAPHQL_BUILD_CLIENTGEN)
update_graphql_client_files(nestedinput schema.graphql query.graphql NestedInput nestedinput)
endif()

add_graphql_client_target(nestedinput)
Loading