Skip to content

Commit fd89495

Browse files
authored
Merge pull request #183 from wravery/next
Distinguish AwaitableScalar from AwaitableObject
2 parents 5eeba63 + 92e36af commit fd89495

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

+1184
-801
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: 201 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -295,19 +295,22 @@ 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.
298301
template <typename T>
299-
class FieldResult
302+
class AwaitableScalar
300303
{
301304
public:
302305
template <typename U>
303-
FieldResult(U&& value)
306+
AwaitableScalar(U&& value)
304307
: _value { std::forward<U>(value) }
305308
{
306309
}
307310

308311
struct promise_type
309312
{
310-
FieldResult<T> get_return_object() noexcept
313+
AwaitableScalar<T> get_return_object() noexcept
311314
{
312315
return { _promise.get_future() };
313316
}
@@ -421,6 +424,108 @@ class FieldResult
421424
std::variant<T, std::future<T>, std::shared_ptr<const response::Value>> _value;
422425
};
423426

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

711-
using future_type = FieldResult<type>;
816+
using future_type = typename std::conditional_t<std::is_base_of_v<Object, U>,
817+
AwaitableObject<type>, AwaitableScalar<type>>;
712818
};
713819

714820
template <typename U>
@@ -718,7 +824,7 @@ struct ModifiedResult
718824
typename std::conditional_t<std::is_base_of_v<Object, U>, std::shared_ptr<U>, U>;
719825

720826
using future_type = typename std::conditional_t<std::is_base_of_v<Object, U>,
721-
FieldResult<std::shared_ptr<Object>>, FieldResult<type>>;
827+
AwaitableObject<std::shared_ptr<Object>>, AwaitableScalar<type>>;
722828
};
723829

724830
// Convert a single value of the specified type to JSON.
@@ -727,23 +833,17 @@ struct ModifiedResult
727833

728834
// Peel off the none modifier. If it's included, it should always be last in the list.
729835
template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
730-
static typename std::enable_if_t<TypeModifier::None == Modifier && sizeof...(Other) == 0
836+
static typename std::enable_if_t<TypeModifier::None == Modifier
731837
&& !std::is_same_v<Object, Type> && std::is_base_of_v<Object, Type>,
732838
AwaitableResolver>
733-
convert(FieldResult<typename ResultTraits<Type>::type> result, ResolverParams params)
839+
convert(AwaitableObject<typename ResultTraits<Type>::type> result, ResolverParams params)
734840
{
735841
// Call through to the Object specialization with a static_pointer_cast for subclasses of
736842
// Object.
843+
static_assert(sizeof...(Other) == 0, "None modifier should always be last");
737844
static_assert(std::is_same_v<std::shared_ptr<Type>, typename ResultTraits<Type>::type>,
738845
"this is the derived object type");
739846

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-
747847
co_await params.launch;
748848

749849
auto awaitedResult = co_await ModifiedResult<Object>::convert(
@@ -755,11 +855,13 @@ struct ModifiedResult
755855

756856
// Peel off the none modifier. If it's included, it should always be last in the list.
757857
template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
758-
static typename std::enable_if_t<TypeModifier::None == Modifier && sizeof...(Other) == 0
858+
static typename std::enable_if_t<TypeModifier::None == Modifier
759859
&& (std::is_same_v<Object, Type> || !std::is_base_of_v<Object, Type>),
760860
AwaitableResolver>
761861
convert(typename ResultTraits<Type>::future_type result, ResolverParams params)
762862
{
863+
static_assert(sizeof...(Other) == 0, "None modifier should always be last");
864+
763865
// Just call through to the partial specialization without the modifier.
764866
return convert(std::move(result), std::move(params));
765867
}
@@ -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,16 @@ 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+
ModifiedResult::validateScalar<Modifier, Other...>(*value);
911+
co_return ResolverResult { response::Value {
912+
std::shared_ptr { std::move(value) } } };
913+
}
814914
}
815915

816916
co_await params.launch;
@@ -833,11 +933,16 @@ struct ModifiedResult
833933
static typename std::enable_if_t<TypeModifier::List == Modifier, AwaitableResolver> convert(
834934
typename ResultTraits<Type, Modifier, Other...>::future_type result, ResolverParams params)
835935
{
836-
auto value = result.get_value();
837-
838-
if (value)
936+
if constexpr (!std::is_base_of_v<Object, Type>)
839937
{
840-
co_return ResolverResult { response::Value { std::shared_ptr { std::move(value) } } };
938+
auto value = result.get_value();
939+
940+
if (value)
941+
{
942+
ModifiedResult::validateScalar<Modifier, Other...>(*value);
943+
co_return ResolverResult { response::Value {
944+
std::shared_ptr { std::move(value) } } };
945+
}
841946
}
842947

843948
std::vector<AwaitableResolver> children;
@@ -925,6 +1030,47 @@ struct ModifiedResult
9251030
}
9261031

9271032
private:
1033+
// Validate a single scalar value is the expected type.
1034+
static void validateScalar(const response::Value& value);
1035+
1036+
// Peel off the none modifier. If it's included, it should always be last in the list.
1037+
template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
1038+
static void validateScalar(
1039+
typename std::enable_if_t<TypeModifier::None == Modifier, const response::Value&> value)
1040+
{
1041+
static_assert(sizeof...(Other) == 0, "None modifier should always be last");
1042+
1043+
// Just call through to the partial specialization without the modifier.
1044+
validateScalar(value);
1045+
}
1046+
1047+
// Peel off nullable modifiers.
1048+
template <TypeModifier Modifier, TypeModifier... Other>
1049+
static void validateScalar(
1050+
typename std::enable_if_t<TypeModifier::Nullable == Modifier, const response::Value&> value)
1051+
{
1052+
if (value.type() != response::Type::Null)
1053+
{
1054+
ModifiedResult::validateScalar<Other...>(value);
1055+
}
1056+
}
1057+
1058+
// Peel off list modifiers.
1059+
template <TypeModifier Modifier, TypeModifier... Other>
1060+
static void validateScalar(
1061+
typename std::enable_if_t<TypeModifier::List == Modifier, const response::Value&> value)
1062+
{
1063+
if (value.type() != response::Type::List)
1064+
{
1065+
throw schema_exception { { R"ex(not a valid List value)ex" } };
1066+
}
1067+
1068+
for (size_t i = 0; i < value.size(); ++i)
1069+
{
1070+
ModifiedResult::validateScalar<Other...>(value[i]);
1071+
}
1072+
}
1073+
9281074
using ResolverCallback =
9291075
std::function<response::Value(typename ResultTraits<Type>::type, const ResolverParams&)>;
9301076

@@ -938,6 +1084,7 @@ struct ModifiedResult
9381084

9391085
if (value)
9401086
{
1087+
ModifiedResult::validateScalar(*value);
9411088
co_return ResolverResult { response::Value { std::shared_ptr { std::move(value) } } };
9421089
}
9431090

@@ -988,25 +1135,42 @@ using ObjectResult = ModifiedResult<Object>;
9881135
// Export all of the built-in converters
9891136
template <>
9901137
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<int>::convert(
991-
FieldResult<int> result, ResolverParams params);
1138+
AwaitableScalar<int> result, ResolverParams params);
9921139
template <>
9931140
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<double>::convert(
994-
FieldResult<double> result, ResolverParams params);
1141+
AwaitableScalar<double> result, ResolverParams params);
9951142
template <>
9961143
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<std::string>::convert(
997-
FieldResult<std::string> result, ResolverParams params);
1144+
AwaitableScalar<std::string> result, ResolverParams params);
9981145
template <>
9991146
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<bool>::convert(
1000-
FieldResult<bool> result, ResolverParams params);
1147+
AwaitableScalar<bool> result, ResolverParams params);
10011148
template <>
10021149
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::IdType>::convert(
1003-
FieldResult<response::IdType> result, ResolverParams params);
1150+
AwaitableScalar<response::IdType> result, ResolverParams params);
10041151
template <>
10051152
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::Value>::convert(
1006-
FieldResult<response::Value> result, ResolverParams params);
1153+
AwaitableScalar<response::Value> result, ResolverParams params);
10071154
template <>
10081155
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<Object>::convert(
1009-
FieldResult<std::shared_ptr<Object>> result, ResolverParams params);
1156+
AwaitableObject<std::shared_ptr<Object>> result, ResolverParams params);
1157+
1158+
// Export all of the scalar value validation methods
1159+
template <>
1160+
GRAPHQLSERVICE_EXPORT void ModifiedResult<int>::validateScalar(const response::Value& value);
1161+
template <>
1162+
GRAPHQLSERVICE_EXPORT void ModifiedResult<double>::validateScalar(const response::Value& value);
1163+
template <>
1164+
GRAPHQLSERVICE_EXPORT void ModifiedResult<std::string>::validateScalar(
1165+
const response::Value& value);
1166+
template <>
1167+
GRAPHQLSERVICE_EXPORT void ModifiedResult<bool>::validateScalar(const response::Value& value);
1168+
template <>
1169+
GRAPHQLSERVICE_EXPORT void ModifiedResult<response::IdType>::validateScalar(
1170+
const response::Value& value);
1171+
template <>
1172+
GRAPHQLSERVICE_EXPORT void ModifiedResult<response::Value>::validateScalar(
1173+
const response::Value& value);
10101174
#endif // GRAPHQL_DLLEXPORTS
10111175

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

0 commit comments

Comments
 (0)