Skip to content

Commit 92e36af

Browse files
committed
Validate custom response::Value in ModifiedResult
1 parent a33724a commit 92e36af

File tree

10 files changed

+288
-18
lines changed

10 files changed

+288
-18
lines changed

include/graphqlservice/GraphQLService.h

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
304301
template <typename T>
305302
class 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

10301032
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+
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
11101154
template <>
11111155
GRAPHQLSERVICE_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

include/graphqlservice/introspection/IntrospectionSchema.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,17 @@ template <>
108108
GRAPHQLINTROSPECTION_EXPORT AwaitableResolver ModifiedResult<introspection::TypeKind>::convert(
109109
AwaitableScalar<introspection::TypeKind> result, ResolverParams params);
110110
template <>
111+
GRAPHQLINTROSPECTION_EXPORT void ModifiedResult<introspection::TypeKind>::validateScalar(
112+
const response::Value& value);
113+
template <>
111114
GRAPHQLINTROSPECTION_EXPORT introspection::DirectiveLocation ModifiedArgument<introspection::DirectiveLocation>::convert(
112115
const response::Value& value);
113116
template <>
114117
GRAPHQLINTROSPECTION_EXPORT AwaitableResolver ModifiedResult<introspection::DirectiveLocation>::convert(
115118
AwaitableScalar<introspection::DirectiveLocation> result, ResolverParams params);
119+
template <>
120+
GRAPHQLINTROSPECTION_EXPORT void ModifiedResult<introspection::DirectiveLocation>::validateScalar(
121+
const response::Value& value);
116122
#endif // GRAPHQL_DLLEXPORTS
117123

118124
} // namespace service

samples/learn/schema/StarWarsSchema.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ service::AwaitableResolver ModifiedResult<learn::Episode>::convert(service::Awai
6262
});
6363
}
6464

65+
template <>
66+
void ModifiedResult<learn::Episode>::validateScalar(const response::Value& value)
67+
{
68+
if (!value.maybe_enum())
69+
{
70+
throw service::schema_exception { { R"ex(not a valid Episode value)ex" } };
71+
}
72+
73+
const auto itr = std::find(s_namesEpisode.cbegin(), s_namesEpisode.cend(), value.get<std::string>());
74+
75+
if (itr == s_namesEpisode.cend())
76+
{
77+
throw service::schema_exception { { R"ex(not a valid Episode value)ex" } };
78+
}
79+
}
80+
6581
template <>
6682
learn::ReviewInput ModifiedArgument<learn::ReviewInput>::convert(const response::Value& value)
6783
{

samples/today/nointrospection/TodaySchema.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ service::AwaitableResolver ModifiedResult<today::TaskState>::convert(service::Aw
6464
});
6565
}
6666

67+
template <>
68+
void ModifiedResult<today::TaskState>::validateScalar(const response::Value& value)
69+
{
70+
if (!value.maybe_enum())
71+
{
72+
throw service::schema_exception { { R"ex(not a valid TaskState value)ex" } };
73+
}
74+
75+
const auto itr = std::find(s_namesTaskState.cbegin(), s_namesTaskState.cend(), value.get<std::string>());
76+
77+
if (itr == s_namesTaskState.cend())
78+
{
79+
throw service::schema_exception { { R"ex(not a valid TaskState value)ex" } };
80+
}
81+
}
82+
6783
template <>
6884
today::CompleteTaskInput ModifiedArgument<today::CompleteTaskInput>::convert(const response::Value& value)
6985
{

samples/today/schema/TodaySchema.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ service::AwaitableResolver ModifiedResult<today::TaskState>::convert(service::Aw
6464
});
6565
}
6666

67+
template <>
68+
void ModifiedResult<today::TaskState>::validateScalar(const response::Value& value)
69+
{
70+
if (!value.maybe_enum())
71+
{
72+
throw service::schema_exception { { R"ex(not a valid TaskState value)ex" } };
73+
}
74+
75+
const auto itr = std::find(s_namesTaskState.cbegin(), s_namesTaskState.cend(), value.get<std::string>());
76+
77+
if (itr == s_namesTaskState.cend())
78+
{
79+
throw service::schema_exception { { R"ex(not a valid TaskState value)ex" } };
80+
}
81+
}
82+
6783
template <>
6884
today::CompleteTaskInput ModifiedArgument<today::CompleteTaskInput>::convert(const response::Value& value)
6985
{

samples/validation/schema/ValidationSchema.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ service::AwaitableResolver ModifiedResult<validation::DogCommand>::convert(servi
6363
});
6464
}
6565

66+
template <>
67+
void ModifiedResult<validation::DogCommand>::validateScalar(const response::Value& value)
68+
{
69+
if (!value.maybe_enum())
70+
{
71+
throw service::schema_exception { { R"ex(not a valid DogCommand value)ex" } };
72+
}
73+
74+
const auto itr = std::find(s_namesDogCommand.cbegin(), s_namesDogCommand.cend(), value.get<std::string>());
75+
76+
if (itr == s_namesDogCommand.cend())
77+
{
78+
throw service::schema_exception { { R"ex(not a valid DogCommand value)ex" } };
79+
}
80+
}
81+
6682
static const std::array<std::string_view, 1> s_namesCatCommand = {
6783
R"gql(JUMP)gql"sv
6884
};
@@ -99,6 +115,22 @@ service::AwaitableResolver ModifiedResult<validation::CatCommand>::convert(servi
99115
});
100116
}
101117

118+
template <>
119+
void ModifiedResult<validation::CatCommand>::validateScalar(const response::Value& value)
120+
{
121+
if (!value.maybe_enum())
122+
{
123+
throw service::schema_exception { { R"ex(not a valid CatCommand value)ex" } };
124+
}
125+
126+
const auto itr = std::find(s_namesCatCommand.cbegin(), s_namesCatCommand.cend(), value.get<std::string>());
127+
128+
if (itr == s_namesCatCommand.cend())
129+
{
130+
throw service::schema_exception { { R"ex(not a valid CatCommand value)ex" } };
131+
}
132+
}
133+
102134
template <>
103135
validation::ComplexInput ModifiedArgument<validation::ComplexInput>::convert(const response::Value& value)
104136
{

src/GraphQLService.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,66 @@ AwaitableResolver ModifiedResult<Object>::convert(
716716
co_return std::move(document);
717717
}
718718

719+
template <>
720+
void ModifiedResult<int>::validateScalar(const response::Value& value)
721+
{
722+
if (value.type() != response::Type::Int)
723+
{
724+
throw schema_exception { { R"ex(not a valid Int value)ex" } };
725+
}
726+
}
727+
728+
template <>
729+
void ModifiedResult<double>::validateScalar(const response::Value& value)
730+
{
731+
if (value.type() != response::Type::Float)
732+
{
733+
throw schema_exception { { R"ex(not a valid Float value)ex" } };
734+
}
735+
}
736+
737+
template <>
738+
void ModifiedResult<std::string>::validateScalar(const response::Value& value)
739+
{
740+
if (value.type() != response::Type::String)
741+
{
742+
throw schema_exception { { R"ex(not a valid String value)ex" } };
743+
}
744+
}
745+
746+
template <>
747+
void ModifiedResult<bool>::validateScalar(const response::Value& value)
748+
{
749+
if (value.type() != response::Type::Boolean)
750+
{
751+
throw schema_exception { { R"ex(not a valid Boolean value)ex" } };
752+
}
753+
}
754+
755+
template <>
756+
void ModifiedResult<response::IdType>::validateScalar(const response::Value& value)
757+
{
758+
if (value.type() != response::Type::String)
759+
{
760+
throw schema_exception { { R"ex(not a valid String value)ex" } };
761+
}
762+
763+
try
764+
{
765+
const auto result = value.get<response::IdType>();
766+
}
767+
catch (const std::logic_error& ex)
768+
{
769+
throw schema_exception { { ex.what() } };
770+
}
771+
}
772+
773+
template <>
774+
void ModifiedResult<response::Value>::validateScalar(const response::Value&)
775+
{
776+
// Any response::Value is valid for a custom scalar type.
777+
}
778+
719779
// SelectionVisitor visits the AST and resolves a field or fragment, unless it's skipped by
720780
// a directive or type condition.
721781
class SelectionVisitor

0 commit comments

Comments
 (0)