Skip to content

Make the default subscription object optional and allow an override for subscribe/unsubscribe #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions include/graphqlservice/GraphQLService.h
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,9 @@ struct [[nodiscard]] RequestSubscribeParams

// Optional sub-class of RequestState which will be passed to each resolver and field accessor.
std::shared_ptr<RequestState> state {};

// Optional override for the default Subscription operation object.
std::shared_ptr<const Object> subscriptionObject {};
};

struct [[nodiscard]] RequestUnsubscribeParams
Expand All @@ -1245,6 +1248,9 @@ struct [[nodiscard]] RequestUnsubscribeParams

// Optional async execution awaitable.
await_async launch {};

// Optional override for the default Subscription operation object.
std::shared_ptr<const Object> subscriptionObject {};
};

using SubscriptionArguments = std::map<std::string_view, response::Value>;
Expand Down Expand Up @@ -1321,6 +1327,13 @@ struct [[nodiscard]] SubscriptionData : std::enable_shared_from_this<Subscriptio
const peg::ast_node& selection;
};

// Placeholder for an empty subscription object.
class SubscriptionPlaceholder
{
// Private constructor, since it should never actually be instantiated.
SubscriptionPlaceholder() noexcept = default;
};

// Forward declare just the class type so we can reference it in the Request::_validation member.
class ValidateExecutableVisitor;

Expand Down
4 changes: 2 additions & 2 deletions samples/learn/schema/StarWarsSchema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ namespace learn {

Operations::Operations(std::shared_ptr<object::Query> query, std::shared_ptr<object::Mutation> mutation)
: service::Request({
{ "query", query },
{ "mutation", mutation }
{ service::strQuery, query },
{ service::strMutation, mutation }
}, GetSchema())
, _query(std::move(query))
, _mutation(std::move(mutation))
Expand Down
5 changes: 4 additions & 1 deletion samples/learn/schema/StarWarsSchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ class [[nodiscard]] Operations final

template <class TQuery, class TMutation>
explicit Operations(std::shared_ptr<TQuery> query, std::shared_ptr<TMutation> mutation)
: Operations { std::make_shared<object::Query>(std::move(query)), std::make_shared<object::Mutation>(std::move(mutation)) }
: Operations {
std::make_shared<object::Query>(std::move(query)),
std::make_shared<object::Mutation>(std::move(mutation))
}
{
}

Expand Down
9 changes: 5 additions & 4 deletions samples/today/TodayMock.h
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ class NextAppointmentChange

std::shared_ptr<object::Node> getNodeChange(const response::IdType&) const
{
return {};
throw std::runtime_error("Unexpected call to getNodeChange");
}

private:
Expand All @@ -561,8 +561,9 @@ class NextAppointmentChange
class NodeChange
{
public:
using nodeChange = std::function<std::shared_ptr<object::Node>(
const std::shared_ptr<service::RequestState>&, response::IdType&&)>;
using nodeChange =
std::function<std::shared_ptr<object::Node>(service::ResolverContext resolverContext,
const std::shared_ptr<service::RequestState>&, response::IdType&&)>;

explicit NodeChange(nodeChange&& changeNode)
: _changeNode(std::move(changeNode))
Expand All @@ -577,7 +578,7 @@ class NodeChange
std::shared_ptr<object::Node> getNodeChange(
const service::FieldParams& params, response::IdType&& idArg) const
{
return _changeNode(params.state, std::move(idArg));
return _changeNode(params.resolverContext, params.state, std::move(idArg));
}

private:
Expand Down
6 changes: 3 additions & 3 deletions samples/today/nointrospection/TodaySchema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ namespace today {

Operations::Operations(std::shared_ptr<object::Query> query, std::shared_ptr<object::Mutation> mutation, std::shared_ptr<object::Subscription> subscription)
: service::Request({
{ "query", query },
{ "mutation", mutation },
{ "subscription", subscription }
{ service::strQuery, query },
{ service::strMutation, mutation },
{ service::strSubscription, subscription }
}, GetSchema())
, _query(std::move(query))
, _mutation(std::move(mutation))
Expand Down
10 changes: 7 additions & 3 deletions samples/today/nointrospection/TodaySchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,13 @@ class [[nodiscard]] Operations final
public:
explicit Operations(std::shared_ptr<object::Query> query, std::shared_ptr<object::Mutation> mutation, std::shared_ptr<object::Subscription> subscription);

template <class TQuery, class TMutation, class TSubscription>
explicit Operations(std::shared_ptr<TQuery> query, std::shared_ptr<TMutation> mutation, std::shared_ptr<TSubscription> subscription)
: Operations { std::make_shared<object::Query>(std::move(query)), std::make_shared<object::Mutation>(std::move(mutation)), std::make_shared<object::Subscription>(std::move(subscription)) }
template <class TQuery, class TMutation, class TSubscription = service::SubscriptionPlaceholder>
explicit Operations(std::shared_ptr<TQuery> query, std::shared_ptr<TMutation> mutation, std::shared_ptr<TSubscription> subscription = {})
: Operations {
std::make_shared<object::Query>(std::move(query)),
std::make_shared<object::Mutation>(std::move(mutation)),
subscription ? std::make_shared<object::Subscription>(std::move(subscription)) : std::shared_ptr<object::Subscription> {}
}
{
}

Expand Down
6 changes: 3 additions & 3 deletions samples/today/schema/TodaySchema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ namespace today {

Operations::Operations(std::shared_ptr<object::Query> query, std::shared_ptr<object::Mutation> mutation, std::shared_ptr<object::Subscription> subscription)
: service::Request({
{ "query", query },
{ "mutation", mutation },
{ "subscription", subscription }
{ service::strQuery, query },
{ service::strMutation, mutation },
{ service::strSubscription, subscription }
}, GetSchema())
, _query(std::move(query))
, _mutation(std::move(mutation))
Expand Down
10 changes: 7 additions & 3 deletions samples/today/schema/TodaySchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,13 @@ class [[nodiscard]] Operations final
public:
explicit Operations(std::shared_ptr<object::Query> query, std::shared_ptr<object::Mutation> mutation, std::shared_ptr<object::Subscription> subscription);

template <class TQuery, class TMutation, class TSubscription>
explicit Operations(std::shared_ptr<TQuery> query, std::shared_ptr<TMutation> mutation, std::shared_ptr<TSubscription> subscription)
: Operations { std::make_shared<object::Query>(std::move(query)), std::make_shared<object::Mutation>(std::move(mutation)), std::make_shared<object::Subscription>(std::move(subscription)) }
template <class TQuery, class TMutation, class TSubscription = service::SubscriptionPlaceholder>
explicit Operations(std::shared_ptr<TQuery> query, std::shared_ptr<TMutation> mutation, std::shared_ptr<TSubscription> subscription = {})
: Operations {
std::make_shared<object::Query>(std::move(query)),
std::make_shared<object::Mutation>(std::move(mutation)),
subscription ? std::make_shared<object::Subscription>(std::move(subscription)) : std::shared_ptr<object::Subscription> {}
}
{
}

Expand Down
6 changes: 0 additions & 6 deletions samples/validation/ValidationMock.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@ class Mutation
explicit Mutation() = default;
};

class Subscription
{
public:
explicit Subscription() = default;
};

} // namespace graphql::validation

#endif // VALIDATIONMOCK_H
6 changes: 3 additions & 3 deletions samples/validation/schema/ValidationSchema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ namespace validation {

Operations::Operations(std::shared_ptr<object::Query> query, std::shared_ptr<object::Mutation> mutation, std::shared_ptr<object::Subscription> subscription)
: service::Request({
{ "query", query },
{ "mutation", mutation },
{ "subscription", subscription }
{ service::strQuery, query },
{ service::strMutation, mutation },
{ service::strSubscription, subscription }
}, GetSchema())
, _query(std::move(query))
, _mutation(std::move(mutation))
Expand Down
10 changes: 7 additions & 3 deletions samples/validation/schema/ValidationSchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,13 @@ class [[nodiscard]] Operations final
public:
explicit Operations(std::shared_ptr<object::Query> query, std::shared_ptr<object::Mutation> mutation, std::shared_ptr<object::Subscription> subscription);

template <class TQuery, class TMutation, class TSubscription>
explicit Operations(std::shared_ptr<TQuery> query, std::shared_ptr<TMutation> mutation, std::shared_ptr<TSubscription> subscription)
: Operations { std::make_shared<object::Query>(std::move(query)), std::make_shared<object::Mutation>(std::move(mutation)), std::make_shared<object::Subscription>(std::move(subscription)) }
template <class TQuery, class TMutation, class TSubscription = service::SubscriptionPlaceholder>
explicit Operations(std::shared_ptr<TQuery> query, std::shared_ptr<TMutation> mutation, std::shared_ptr<TSubscription> subscription = {})
: Operations {
std::make_shared<object::Query>(std::move(query)),
std::make_shared<object::Mutation>(std::move(mutation)),
subscription ? std::make_shared<object::Subscription>(std::move(subscription)) : std::shared_ptr<object::Subscription> {}
}
{
}

Expand Down
45 changes: 32 additions & 13 deletions src/GraphQLService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1827,12 +1827,21 @@ AwaitableSubscribe Request::subscribe(RequestSubscribeParams params)
const auto spThis = shared_from_this();
const auto launch = std::move(params.launch);
std::unique_lock lock { spThis->_subscriptionMutex };
const auto key = spThis->addSubscription(std::move(params));
const auto itrOperation = spThis->_operations.find(strSubscription);

if (itrOperation != spThis->_operations.end())
if (itrOperation == _operations.end())
{
// There may be an empty entry in the operations map, but if it's completely missing then
// that means the schema doesn't support subscriptions at all.
throw std::logic_error("Subscriptions not supported");
}

const auto optionalOrDefaultSubscription =
params.subscriptionObject ? std::move(params.subscriptionObject) : itrOperation->second;
const auto key = spThis->addSubscription(std::move(params));

if (optionalOrDefaultSubscription)
{
const auto operation = itrOperation->second;
const auto registration = spThis->_subscriptions.at(key);
const SelectionSetParams selectionSetParams {
ResolverContext::NotifySubscribe,
Expand All @@ -1851,24 +1860,25 @@ AwaitableSubscribe Request::subscribe(RequestSubscribeParams params)
{
co_await launch;

auto errors = std::move((co_await operation->resolve(selectionSetParams,
registration->selection,
registration->data->fragments,
registration->data->variables))
.errors);
auto errors =
std::move((co_await optionalOrDefaultSubscription->resolve(selectionSetParams,
registration->selection,
registration->data->fragments,
registration->data->variables))
.errors);

if (!errors.empty())
{
throw schema_exception { std::move(errors) };
}
}
catch (const std::exception& ex)
catch (...)
{
lock.lock();

// Rethrow the exception, but don't leave it subscribed if the resolver failed.
spThis->removeSubscription(key);
throw ex;
throw;
}
}

Expand All @@ -1880,11 +1890,20 @@ AwaitableUnsubscribe Request::unsubscribe(RequestUnsubscribeParams params)
const auto spThis = shared_from_this();
std::unique_lock lock { spThis->_subscriptionMutex };
const auto itrOperation = spThis->_operations.find(strSubscription);

if (itrOperation == _operations.end())
{
// There may be an empty entry in the operations map, but if it's completely missing then
// that means the schema doesn't support subscriptions at all.
throw std::logic_error("Subscriptions not supported");
}

const auto optionalOrDefaultSubscription =
params.subscriptionObject ? std::move(params.subscriptionObject) : itrOperation->second;
std::list<schema_error> errors {};

if (itrOperation != spThis->_operations.end())
if (optionalOrDefaultSubscription)
{
const auto operation = itrOperation->second;
const auto registration = spThis->_subscriptions.at(params.key);
const SelectionSetParams selectionSetParams {
ResolverContext::NotifyUnsubscribe,
Expand All @@ -1900,7 +1919,7 @@ AwaitableUnsubscribe Request::unsubscribe(RequestUnsubscribeParams params)
lock.unlock();

co_await params.launch;
errors = std::move((co_await operation->resolve(selectionSetParams,
errors = std::move((co_await optionalOrDefaultSubscription->resolve(selectionSetParams,
registration->selection,
registration->data->fragments,
registration->data->variables))
Expand Down
42 changes: 38 additions & 4 deletions src/SchemaGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ static_assert(graphql::internal::MinorVersion == )cpp"

if (!_loader.isIntrospection())
{
bool hasSubscription = false;
bool firstOperation = true;

headerFile << R"cpp(class [[nodiscard]] Operations final
Expand All @@ -382,6 +383,8 @@ static_assert(graphql::internal::MinorVersion == )cpp"

for (const auto& operation : _loader.getOperationTypes())
{
hasSubscription = hasSubscription || operation.operation == service::strSubscription;

if (!firstOperation)
{
headerFile << R"cpp(, )cpp";
Expand Down Expand Up @@ -410,6 +413,11 @@ static_assert(graphql::internal::MinorVersion == )cpp"

firstOperation = false;
headerFile << R"cpp(class T)cpp" << operation.cppType;

if (hasSubscription && operation.operation == service::strSubscription)
{
headerFile << R"cpp( = service::SubscriptionPlaceholder)cpp";
}
}

headerFile << R"cpp(>
Expand All @@ -427,6 +435,11 @@ static_assert(graphql::internal::MinorVersion == )cpp"
firstOperation = false;
headerFile << R"cpp(std::shared_ptr<T)cpp" << operation.cppType << R"cpp(> )cpp"
<< operation.operation;

if (hasSubscription && operation.operation == service::strSubscription)
{
headerFile << R"cpp( = {})cpp";
}
}

headerFile << R"cpp()
Expand All @@ -442,11 +455,27 @@ static_assert(graphql::internal::MinorVersion == )cpp"
}

firstOperation = false;
headerFile << R"cpp( std::make_shared<object::)cpp" << operation.cppType
<< R"cpp(>(std::move()cpp" << operation.operation << R"cpp()))cpp";

if (hasSubscription && operation.operation == service::strSubscription)
{
headerFile << R"cpp(
)cpp" << operation.operation
<< R"cpp( ? std::make_shared<object::)cpp" << operation.cppType
<< R"cpp(>(std::move()cpp" << operation.operation
<< R"cpp()) : std::shared_ptr<object::)cpp" << operation.cppType
<< R"cpp(> {})cpp";
}
else
{
headerFile << R"cpp(
std::make_shared<object::)cpp"
<< operation.cppType << R"cpp(>(std::move()cpp"
<< operation.operation << R"cpp()))cpp";
}
}

headerFile << R"cpp( }
headerFile << R"cpp(
}
{
}
)cpp";
Expand Down Expand Up @@ -1448,7 +1477,12 @@ Operations::Operations()cpp";
}

firstOperation = false;
sourceFile << R"cpp( { ")cpp" << operation.operation << R"cpp(", )cpp"

std::string operationName(operation.operation);

operationName[0] =
static_cast<char>(std::toupper(static_cast<unsigned char>(operationName[0])));
sourceFile << R"cpp( { service::str)cpp" << operationName << R"cpp(, )cpp"
<< operation.operation << R"cpp( })cpp";
}

Expand Down
Loading