@@ -295,19 +295,22 @@ struct FieldParams : SelectionSetParams
295
295
// Field accessors may return either a result of T, an awaitable of T, or a std::future<T>, so at
296
296
// runtime the implementer may choose to return by value or defer/parallelize expensive operations
297
297
// 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.
298
301
template <typename T>
299
- class FieldResult
302
+ class AwaitableScalar
300
303
{
301
304
public:
302
305
template <typename U>
303
- FieldResult (U&& value)
306
+ AwaitableScalar (U&& value)
304
307
: _value { std::forward<U>(value) }
305
308
{
306
309
}
307
310
308
311
struct promise_type
309
312
{
310
- FieldResult <T> get_return_object () noexcept
313
+ AwaitableScalar <T> get_return_object () noexcept
311
314
{
312
315
return { _promise.get_future () };
313
316
}
@@ -421,6 +424,108 @@ class FieldResult
421
424
std::variant<T, std::future<T>, std::shared_ptr<const response::Value>> _value;
422
425
};
423
426
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
+
424
529
// Fragments are referenced by name and have a single type condition (except for inline
425
530
// fragments, where the type condition is common but optional). They contain a set of fields
426
531
// (with optional aliases and sub-selections) and potentially references to other fragments.
@@ -708,7 +813,8 @@ struct ModifiedResult
708
813
std::vector<typename ResultTraits<U, Other...>::type>,
709
814
typename std::conditional_t <std::is_base_of_v<Object, U>, std::shared_ptr<U>, U>>>;
710
815
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>>;
712
818
};
713
819
714
820
template <typename U>
@@ -718,7 +824,7 @@ struct ModifiedResult
718
824
typename std::conditional_t <std::is_base_of_v<Object, U>, std::shared_ptr<U>, U>;
719
825
720
826
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>>;
722
828
};
723
829
724
830
// Convert a single value of the specified type to JSON.
@@ -727,23 +833,17 @@ struct ModifiedResult
727
833
728
834
// Peel off the none modifier. If it's included, it should always be last in the list.
729
835
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
731
837
&& !std::is_same_v<Object, Type> && std::is_base_of_v<Object, Type>,
732
838
AwaitableResolver>
733
- convert (FieldResult <typename ResultTraits<Type>::type> result, ResolverParams params)
839
+ convert (AwaitableObject <typename ResultTraits<Type>::type> result, ResolverParams params)
734
840
{
735
841
// Call through to the Object specialization with a static_pointer_cast for subclasses of
736
842
// Object.
843
+ static_assert (sizeof ...(Other) == 0 , " None modifier should always be last" );
737
844
static_assert (std::is_same_v<std::shared_ptr<Type>, typename ResultTraits<Type>::type>,
738
845
" this is the derived object type" );
739
846
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
-
747
847
co_await params.launch ;
748
848
749
849
auto awaitedResult = co_await ModifiedResult<Object>::convert (
@@ -755,11 +855,13 @@ struct ModifiedResult
755
855
756
856
// Peel off the none modifier. If it's included, it should always be last in the list.
757
857
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
759
859
&& (std::is_same_v<Object, Type> || !std::is_base_of_v<Object, Type>),
760
860
AwaitableResolver>
761
861
convert (typename ResultTraits<Type>::future_type result, ResolverParams params)
762
862
{
863
+ static_assert (sizeof ...(Other) == 0 , " None modifier should always be last" );
864
+
763
865
// Just call through to the partial specialization without the modifier.
764
866
return convert (std::move (result), std::move (params));
765
867
}
@@ -772,13 +874,6 @@ struct ModifiedResult
772
874
convert (
773
875
typename ResultTraits<Type, Modifier, Other...>::future_type result, ResolverParams params)
774
876
{
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
-
782
877
co_await params.launch ;
783
878
784
879
auto awaitedResult = co_await std::move (result);
@@ -806,11 +901,16 @@ struct ModifiedResult
806
901
typename ResultTraits<Type, Modifier, Other...>::type>,
807
902
" this is the optional version" );
808
903
809
- auto value = result.get_value ();
810
-
811
- if (value)
904
+ if constexpr (!std::is_base_of_v<Object, Type>)
812
905
{
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
+ }
814
914
}
815
915
816
916
co_await params.launch ;
@@ -833,11 +933,16 @@ struct ModifiedResult
833
933
static typename std::enable_if_t <TypeModifier::List == Modifier, AwaitableResolver> convert (
834
934
typename ResultTraits<Type, Modifier, Other...>::future_type result, ResolverParams params)
835
935
{
836
- auto value = result.get_value ();
837
-
838
- if (value)
936
+ if constexpr (!std::is_base_of_v<Object, Type>)
839
937
{
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
+ }
841
946
}
842
947
843
948
std::vector<AwaitableResolver> children;
@@ -925,6 +1030,47 @@ struct ModifiedResult
925
1030
}
926
1031
927
1032
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
+
928
1074
using ResolverCallback =
929
1075
std::function<response::Value(typename ResultTraits<Type>::type, const ResolverParams&)>;
930
1076
@@ -938,6 +1084,7 @@ struct ModifiedResult
938
1084
939
1085
if (value)
940
1086
{
1087
+ ModifiedResult::validateScalar (*value);
941
1088
co_return ResolverResult { response::Value { std::shared_ptr { std::move (value) } } };
942
1089
}
943
1090
@@ -988,25 +1135,42 @@ using ObjectResult = ModifiedResult<Object>;
988
1135
// Export all of the built-in converters
989
1136
template <>
990
1137
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<int >::convert(
991
- FieldResult <int > result, ResolverParams params);
1138
+ AwaitableScalar <int > result, ResolverParams params);
992
1139
template <>
993
1140
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<double >::convert(
994
- FieldResult <double > result, ResolverParams params);
1141
+ AwaitableScalar <double > result, ResolverParams params);
995
1142
template <>
996
1143
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<std::string>::convert(
997
- FieldResult <std::string> result, ResolverParams params);
1144
+ AwaitableScalar <std::string> result, ResolverParams params);
998
1145
template <>
999
1146
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<bool >::convert(
1000
- FieldResult <bool > result, ResolverParams params);
1147
+ AwaitableScalar <bool > result, ResolverParams params);
1001
1148
template <>
1002
1149
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::IdType>::convert(
1003
- FieldResult <response::IdType> result, ResolverParams params);
1150
+ AwaitableScalar <response::IdType> result, ResolverParams params);
1004
1151
template <>
1005
1152
GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::Value>::convert(
1006
- FieldResult <response::Value> result, ResolverParams params);
1153
+ AwaitableScalar <response::Value> result, ResolverParams params);
1007
1154
template <>
1008
1155
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);
1010
1174
#endif // GRAPHQL_DLLEXPORTS
1011
1175
1012
1176
// Subscription callbacks receive the response::Value representing the result of evaluating the
0 commit comments