Skip to content

Commit a33724a

Browse files
committed
Distinguish AwaitableScalar from AwaitableObject
1 parent 5eeba63 commit a33724a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+908
-795
lines changed

doc/resolvers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ service::AwaitableResolver resolveId(service::ResolverParams&& params);
7373
```
7474
In this example, the `resolveId` method invokes `getId`:
7575
```cpp
76-
virtual service::FieldResult<response::IdType> getId(service::FieldParams&& params) const override;
76+
virtual service::AwaitableScalar<response::IdType> getId(service::FieldParams&& params) const override;
7777
```
7878

7979
There are a couple of interesting quirks in this example:

include/graphqlservice/GraphQLService.h

Lines changed: 138 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -295,19 +295,25 @@ struct FieldParams : SelectionSetParams
295295
// Field accessors may return either a result of T, an awaitable of T, or a std::future<T>, so at
296296
// runtime the implementer may choose to return by value or defer/parallelize expensive operations
297297
// by returning an async future or an awaitable coroutine.
298+
//
299+
// If the overhead of conversion to response::Value is too expensive, scalar type field accessors
300+
// can store and return a std::shared_ptr<const response::Value> directly. However, be careful using
301+
// this mechanism, because there's no validation that the response::Value you return matches the
302+
// GraphQL type of this field. It will be inserted directly into the response document without
303+
// modification.
298304
template <typename T>
299-
class FieldResult
305+
class AwaitableScalar
300306
{
301307
public:
302308
template <typename U>
303-
FieldResult(U&& value)
309+
AwaitableScalar(U&& value)
304310
: _value { std::forward<U>(value) }
305311
{
306312
}
307313

308314
struct promise_type
309315
{
310-
FieldResult<T> get_return_object() noexcept
316+
AwaitableScalar<T> get_return_object() noexcept
311317
{
312318
return { _promise.get_future() };
313319
}
@@ -421,6 +427,108 @@ class FieldResult
421427
std::variant<T, std::future<T>, std::shared_ptr<const response::Value>> _value;
422428
};
423429

430+
// Field accessors may return either a result of T, an awaitable of T, or a std::future<T>, so at
431+
// runtime the implementer may choose to return by value or defer/parallelize expensive operations
432+
// by returning an async future or an awaitable coroutine.
433+
template <typename T>
434+
class AwaitableObject
435+
{
436+
public:
437+
template <typename U>
438+
AwaitableObject(U&& value)
439+
: _value { std::forward<U>(value) }
440+
{
441+
}
442+
443+
struct promise_type
444+
{
445+
AwaitableObject<T> get_return_object() noexcept
446+
{
447+
return { _promise.get_future() };
448+
}
449+
450+
coro::suspend_never initial_suspend() const noexcept
451+
{
452+
return {};
453+
}
454+
455+
coro::suspend_never final_suspend() const noexcept
456+
{
457+
return {};
458+
}
459+
460+
void return_value(const T& value) noexcept(std::is_nothrow_copy_constructible_v<T>)
461+
{
462+
_promise.set_value(value);
463+
}
464+
465+
void return_value(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>)
466+
{
467+
_promise.set_value(std::move(value));
468+
}
469+
470+
void unhandled_exception() noexcept
471+
{
472+
_promise.set_exception(std::current_exception());
473+
}
474+
475+
private:
476+
std::promise<T> _promise;
477+
};
478+
479+
bool await_ready() const noexcept
480+
{
481+
return std::visit(
482+
[](const auto& value) noexcept {
483+
using value_type = std::decay_t<decltype(value)>;
484+
485+
if constexpr (std::is_same_v<value_type, T>)
486+
{
487+
return true;
488+
}
489+
else if constexpr (std::is_same_v<value_type, std::future<T>>)
490+
{
491+
using namespace std::literals;
492+
493+
return value.wait_for(0s) != std::future_status::timeout;
494+
}
495+
},
496+
_value);
497+
}
498+
499+
void await_suspend(coro::coroutine_handle<> h) const
500+
{
501+
std::thread(
502+
[this](coro::coroutine_handle<> h) noexcept {
503+
std::get<std::future<T>>(_value).wait();
504+
h.resume();
505+
},
506+
std::move(h))
507+
.detach();
508+
}
509+
510+
T await_resume()
511+
{
512+
return std::visit(
513+
[](auto&& value) -> T {
514+
using value_type = std::decay_t<decltype(value)>;
515+
516+
if constexpr (std::is_same_v<value_type, T>)
517+
{
518+
return T { std::move(value) };
519+
}
520+
else if constexpr (std::is_same_v<value_type, std::future<T>>)
521+
{
522+
return value.get();
523+
}
524+
},
525+
std::move(_value));
526+
}
527+
528+
private:
529+
std::variant<T, std::future<T>> _value;
530+
};
531+
424532
// Fragments are referenced by name and have a single type condition (except for inline
425533
// fragments, where the type condition is common but optional). They contain a set of fields
426534
// (with optional aliases and sub-selections) and potentially references to other fragments.
@@ -708,7 +816,8 @@ struct ModifiedResult
708816
std::vector<typename ResultTraits<U, Other...>::type>,
709817
typename std::conditional_t<std::is_base_of_v<Object, U>, std::shared_ptr<U>, U>>>;
710818

711-
using future_type = FieldResult<type>;
819+
using future_type = typename std::conditional_t<std::is_base_of_v<Object, U>,
820+
AwaitableObject<type>, AwaitableScalar<type>>;
712821
};
713822

714823
template <typename U>
@@ -718,7 +827,7 @@ struct ModifiedResult
718827
typename std::conditional_t<std::is_base_of_v<Object, U>, std::shared_ptr<U>, U>;
719828

720829
using future_type = typename std::conditional_t<std::is_base_of_v<Object, U>,
721-
FieldResult<std::shared_ptr<Object>>, FieldResult<type>>;
830+
AwaitableObject<std::shared_ptr<Object>>, AwaitableScalar<type>>;
722831
};
723832

724833
// Convert a single value of the specified type to JSON.
@@ -730,20 +839,13 @@ struct ModifiedResult
730839
static typename std::enable_if_t<TypeModifier::None == Modifier && sizeof...(Other) == 0
731840
&& !std::is_same_v<Object, Type> && std::is_base_of_v<Object, Type>,
732841
AwaitableResolver>
733-
convert(FieldResult<typename ResultTraits<Type>::type> result, ResolverParams params)
842+
convert(AwaitableObject<typename ResultTraits<Type>::type> result, ResolverParams params)
734843
{
735844
// Call through to the Object specialization with a static_pointer_cast for subclasses of
736845
// Object.
737846
static_assert(std::is_same_v<std::shared_ptr<Type>, typename ResultTraits<Type>::type>,
738847
"this is the derived object type");
739848

740-
auto value = result.get_value();
741-
742-
if (value)
743-
{
744-
co_return ResolverResult { response::Value { std::shared_ptr { std::move(value) } } };
745-
}
746-
747849
co_await params.launch;
748850

749851
auto awaitedResult = co_await ModifiedResult<Object>::convert(
@@ -772,13 +874,6 @@ struct ModifiedResult
772874
convert(
773875
typename ResultTraits<Type, Modifier, Other...>::future_type result, ResolverParams params)
774876
{
775-
auto value = result.get_value();
776-
777-
if (value)
778-
{
779-
co_return ResolverResult { response::Value { std::shared_ptr { std::move(value) } } };
780-
}
781-
782877
co_await params.launch;
783878

784879
auto awaitedResult = co_await std::move(result);
@@ -806,11 +901,15 @@ struct ModifiedResult
806901
typename ResultTraits<Type, Modifier, Other...>::type>,
807902
"this is the optional version");
808903

809-
auto value = result.get_value();
810-
811-
if (value)
904+
if constexpr (!std::is_base_of_v<Object, Type>)
812905
{
813-
co_return ResolverResult { response::Value { std::shared_ptr { std::move(value) } } };
906+
auto value = result.get_value();
907+
908+
if (value)
909+
{
910+
co_return ResolverResult { response::Value {
911+
std::shared_ptr { std::move(value) } } };
912+
}
814913
}
815914

816915
co_await params.launch;
@@ -833,11 +932,15 @@ struct ModifiedResult
833932
static typename std::enable_if_t<TypeModifier::List == Modifier, AwaitableResolver> convert(
834933
typename ResultTraits<Type, Modifier, Other...>::future_type result, ResolverParams params)
835934
{
836-
auto value = result.get_value();
837-
838-
if (value)
935+
if constexpr (!std::is_base_of_v<Object, Type>)
839936
{
840-
co_return ResolverResult { response::Value { std::shared_ptr { std::move(value) } } };
937+
auto value = result.get_value();
938+
939+
if (value)
940+
{
941+
co_return ResolverResult { response::Value {
942+
std::shared_ptr { std::move(value) } } };
943+
}
841944
}
842945

843946
std::vector<AwaitableResolver> children;
@@ -988,25 +1091,25 @@ using ObjectResult = ModifiedResult<Object>;
9881091
// Export all of the built-in converters
9891092
template <>
9901093
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<int>::convert(
991-
FieldResult<int> result, ResolverParams params);
1094+
AwaitableScalar<int> result, ResolverParams params);
9921095
template <>
9931096
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<double>::convert(
994-
FieldResult<double> result, ResolverParams params);
1097+
AwaitableScalar<double> result, ResolverParams params);
9951098
template <>
9961099
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<std::string>::convert(
997-
FieldResult<std::string> result, ResolverParams params);
1100+
AwaitableScalar<std::string> result, ResolverParams params);
9981101
template <>
9991102
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<bool>::convert(
1000-
FieldResult<bool> result, ResolverParams params);
1103+
AwaitableScalar<bool> result, ResolverParams params);
10011104
template <>
10021105
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::IdType>::convert(
1003-
FieldResult<response::IdType> result, ResolverParams params);
1106+
AwaitableScalar<response::IdType> result, ResolverParams params);
10041107
template <>
10051108
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::Value>::convert(
1006-
FieldResult<response::Value> result, ResolverParams params);
1109+
AwaitableScalar<response::Value> result, ResolverParams params);
10071110
template <>
10081111
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<Object>::convert(
1009-
FieldResult<std::shared_ptr<Object>> result, ResolverParams params);
1112+
AwaitableObject<std::shared_ptr<Object>> result, ResolverParams params);
10101113
#endif // GRAPHQL_DLLEXPORTS
10111114

10121115
// Subscription callbacks receive the response::Value representing the result of evaluating the

include/graphqlservice/introspection/DirectiveObject.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class Directive
2828
{
2929
virtual ~Concept() = default;
3030

31-
virtual service::FieldResult<std::string> getName() const = 0;
32-
virtual service::FieldResult<std::optional<std::string>> getDescription() const = 0;
33-
virtual service::FieldResult<std::vector<DirectiveLocation>> getLocations() const = 0;
34-
virtual service::FieldResult<std::vector<std::shared_ptr<InputValue>>> getArgs() const = 0;
35-
virtual service::FieldResult<bool> getIsRepeatable() const = 0;
31+
virtual service::AwaitableScalar<std::string> getName() const = 0;
32+
virtual service::AwaitableScalar<std::optional<std::string>> getDescription() const = 0;
33+
virtual service::AwaitableScalar<std::vector<DirectiveLocation>> getLocations() const = 0;
34+
virtual service::AwaitableObject<std::vector<std::shared_ptr<InputValue>>> getArgs() const = 0;
35+
virtual service::AwaitableScalar<bool> getIsRepeatable() const = 0;
3636
};
3737

3838
template <class T>
@@ -44,27 +44,27 @@ class Directive
4444
{
4545
}
4646

47-
service::FieldResult<std::string> getName() const final
47+
service::AwaitableScalar<std::string> getName() const final
4848
{
4949
return { _pimpl->getName() };
5050
}
5151

52-
service::FieldResult<std::optional<std::string>> getDescription() const final
52+
service::AwaitableScalar<std::optional<std::string>> getDescription() const final
5353
{
5454
return { _pimpl->getDescription() };
5555
}
5656

57-
service::FieldResult<std::vector<DirectiveLocation>> getLocations() const final
57+
service::AwaitableScalar<std::vector<DirectiveLocation>> getLocations() const final
5858
{
5959
return { _pimpl->getLocations() };
6060
}
6161

62-
service::FieldResult<std::vector<std::shared_ptr<InputValue>>> getArgs() const final
62+
service::AwaitableObject<std::vector<std::shared_ptr<InputValue>>> getArgs() const final
6363
{
6464
return { _pimpl->getArgs() };
6565
}
6666

67-
service::FieldResult<bool> getIsRepeatable() const final
67+
service::AwaitableScalar<bool> getIsRepeatable() const final
6868
{
6969
return { _pimpl->getIsRepeatable() };
7070
}

include/graphqlservice/introspection/EnumValueObject.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ class EnumValue
2727
{
2828
virtual ~Concept() = default;
2929

30-
virtual service::FieldResult<std::string> getName() const = 0;
31-
virtual service::FieldResult<std::optional<std::string>> getDescription() const = 0;
32-
virtual service::FieldResult<bool> getIsDeprecated() const = 0;
33-
virtual service::FieldResult<std::optional<std::string>> getDeprecationReason() const = 0;
30+
virtual service::AwaitableScalar<std::string> getName() const = 0;
31+
virtual service::AwaitableScalar<std::optional<std::string>> getDescription() const = 0;
32+
virtual service::AwaitableScalar<bool> getIsDeprecated() const = 0;
33+
virtual service::AwaitableScalar<std::optional<std::string>> getDeprecationReason() const = 0;
3434
};
3535

3636
template <class T>
@@ -42,22 +42,22 @@ class EnumValue
4242
{
4343
}
4444

45-
service::FieldResult<std::string> getName() const final
45+
service::AwaitableScalar<std::string> getName() const final
4646
{
4747
return { _pimpl->getName() };
4848
}
4949

50-
service::FieldResult<std::optional<std::string>> getDescription() const final
50+
service::AwaitableScalar<std::optional<std::string>> getDescription() const final
5151
{
5252
return { _pimpl->getDescription() };
5353
}
5454

55-
service::FieldResult<bool> getIsDeprecated() const final
55+
service::AwaitableScalar<bool> getIsDeprecated() const final
5656
{
5757
return { _pimpl->getIsDeprecated() };
5858
}
5959

60-
service::FieldResult<std::optional<std::string>> getDeprecationReason() const final
60+
service::AwaitableScalar<std::optional<std::string>> getDeprecationReason() const final
6161
{
6262
return { _pimpl->getDeprecationReason() };
6363
}

0 commit comments

Comments
 (0)