@@ -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.
298304template <typename T>
299- class FieldResult
305+ class AwaitableScalar
300306{
301307public:
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
9891092template <>
9901093GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<int >::convert(
991- FieldResult <int > result, ResolverParams params);
1094+ AwaitableScalar <int > result, ResolverParams params);
9921095template <>
9931096GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<double >::convert(
994- FieldResult <double > result, ResolverParams params);
1097+ AwaitableScalar <double > result, ResolverParams params);
9951098template <>
9961099GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<std::string>::convert(
997- FieldResult <std::string> result, ResolverParams params);
1100+ AwaitableScalar <std::string> result, ResolverParams params);
9981101template <>
9991102GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<bool >::convert(
1000- FieldResult <bool > result, ResolverParams params);
1103+ AwaitableScalar <bool > result, ResolverParams params);
10011104template <>
10021105GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::IdType>::convert(
1003- FieldResult <response::IdType> result, ResolverParams params);
1106+ AwaitableScalar <response::IdType> result, ResolverParams params);
10041107template <>
10051108GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::Value>::convert(
1006- FieldResult <response::Value> result, ResolverParams params);
1109+ AwaitableScalar <response::Value> result, ResolverParams params);
10071110template <>
10081111GRAPHQLSERVICE_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
0 commit comments