@@ -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.
298301template <typename T>
299- class FieldResult
302+ class AwaitableScalar
300303{
301304public:
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
9271032private:
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
9891136template <>
9901137GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<int >::convert(
991- FieldResult <int > result, ResolverParams params);
1138+ AwaitableScalar <int > result, ResolverParams params);
9921139template <>
9931140GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<double >::convert(
994- FieldResult <double > result, ResolverParams params);
1141+ AwaitableScalar <double > result, ResolverParams params);
9951142template <>
9961143GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<std::string>::convert(
997- FieldResult <std::string> result, ResolverParams params);
1144+ AwaitableScalar <std::string> result, ResolverParams params);
9981145template <>
9991146GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<bool >::convert(
1000- FieldResult <bool > result, ResolverParams params);
1147+ AwaitableScalar <bool > result, ResolverParams params);
10011148template <>
10021149GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::IdType>::convert(
1003- FieldResult <response::IdType> result, ResolverParams params);
1150+ AwaitableScalar <response::IdType> result, ResolverParams params);
10041151template <>
10051152GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::Value>::convert(
1006- FieldResult <response::Value> result, ResolverParams params);
1153+ AwaitableScalar <response::Value> result, ResolverParams params);
10071154template <>
10081155GRAPHQLSERVICE_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