@@ -297,10 +297,7 @@ struct FieldParams : SelectionSetParams
297297// by returning an async future or an awaitable coroutine.
298298//
299299// 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.
300+ // can store and return a std::shared_ptr<const response::Value> directly.
304301template <typename T>
305302class AwaitableScalar
306303{
@@ -836,13 +833,14 @@ struct ModifiedResult
836833
837834 // Peel off the none modifier. If it's included, it should always be last in the list.
838835 template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
839- static typename std::enable_if_t <TypeModifier::None == Modifier && sizeof ...(Other) == 0
836+ static typename std::enable_if_t <TypeModifier::None == Modifier
840837 && !std::is_same_v<Object, Type> && std::is_base_of_v<Object, Type>,
841838 AwaitableResolver>
842839 convert (AwaitableObject<typename ResultTraits<Type>::type> result, ResolverParams params)
843840 {
844841 // Call through to the Object specialization with a static_pointer_cast for subclasses of
845842 // Object.
843+ static_assert (sizeof ...(Other) == 0 , " None modifier should always be last" );
846844 static_assert (std::is_same_v<std::shared_ptr<Type>, typename ResultTraits<Type>::type>,
847845 " this is the derived object type" );
848846
@@ -857,11 +855,13 @@ struct ModifiedResult
857855
858856 // Peel off the none modifier. If it's included, it should always be last in the list.
859857 template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
860- static typename std::enable_if_t <TypeModifier::None == Modifier && sizeof ...(Other) == 0
858+ static typename std::enable_if_t <TypeModifier::None == Modifier
861859 && (std::is_same_v<Object, Type> || !std::is_base_of_v<Object, Type>),
862860 AwaitableResolver>
863861 convert (typename ResultTraits<Type>::future_type result, ResolverParams params)
864862 {
863+ static_assert (sizeof ...(Other) == 0 , " None modifier should always be last" );
864+
865865 // Just call through to the partial specialization without the modifier.
866866 return convert (std::move (result), std::move (params));
867867 }
@@ -907,6 +907,7 @@ struct ModifiedResult
907907
908908 if (value)
909909 {
910+ ModifiedResult::validateScalar<Modifier, Other...>(*value);
910911 co_return ResolverResult { response::Value {
911912 std::shared_ptr { std::move (value) } } };
912913 }
@@ -938,6 +939,7 @@ struct ModifiedResult
938939
939940 if (value)
940941 {
942+ ModifiedResult::validateScalar<Modifier, Other...>(*value);
941943 co_return ResolverResult { response::Value {
942944 std::shared_ptr { std::move (value) } } };
943945 }
@@ -1028,6 +1030,47 @@ struct ModifiedResult
10281030 }
10291031
10301032private:
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+
10311074 using ResolverCallback =
10321075 std::function<response::Value(typename ResultTraits<Type>::type, const ResolverParams&)>;
10331076
@@ -1041,6 +1084,7 @@ struct ModifiedResult
10411084
10421085 if (value)
10431086 {
1087+ ModifiedResult::validateScalar (*value);
10441088 co_return ResolverResult { response::Value { std::shared_ptr { std::move (value) } } };
10451089 }
10461090
@@ -1110,6 +1154,23 @@ GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<response::Value>::convert
11101154template <>
11111155GRAPHQLSERVICE_EXPORT AwaitableResolver ModifiedResult<Object>::convert(
11121156 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);
11131174#endif // GRAPHQL_DLLEXPORTS
11141175
11151176// Subscription callbacks receive the response::Value representing the result of evaluating the
0 commit comments