Skip to content

Commit ee264a6

Browse files
authored
Merge pull request #32 from wravery/enum_args
Fix for issue #30
2 parents b8d3e59 + 2c73618 commit ee264a6

File tree

9 files changed

+133
-61
lines changed

9 files changed

+133
-61
lines changed

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ if(BUILD_TESTS OR UPDATE_SAMPLES)
138138
add_test(NAME PegtlCase
139139
COMMAND tests --gtest_filter=PegtlCase.*
140140
WORKING_DIRECTORY $<TARGET_FILE_DIR:tests>)
141+
add_test(NAME ResponseCase
142+
COMMAND tests --gtest_filter=ResponseCase.*
143+
WORKING_DIRECTORY $<TARGET_FILE_DIR:tests>)
141144
endif()
142145

143146
if(UPDATE_SAMPLES)

GraphQLResponse.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ Value::~Value()
4444
// omitted, declare it explicitly and define it in graphqlservice.
4545
}
4646

47+
Value::Value(const char* value)
48+
: _type(Type::String)
49+
, _string(new StringType(value))
50+
{
51+
}
52+
4753
Value::Value(StringType&& value)
4854
: _type(Type::String)
4955
, _string(new StringType(std::move(value)))
@@ -74,12 +80,14 @@ Value::Value(Value&& other) noexcept
7480
, _map(std::move(other._map))
7581
, _list(std::move(other._list))
7682
, _string(std::move(other._string))
83+
, _from_json(other._from_json)
7784
, _scalar(std::move(other._scalar))
7885
, _boolean(other._boolean)
7986
, _int(other._int)
8087
, _float(other._float)
8188
{
8289
const_cast<Type&>(other._type) = Type::Null;
90+
other._from_json = false;
8391
other._boolean = false;
8492
other._int = 0;
8593
other._float = 0.0;
@@ -125,6 +133,8 @@ Value& Value::operator=(Value&& rhs) noexcept
125133
_map = std::move(rhs._map);
126134
_list = std::move(rhs._list);
127135
_string = std::move(rhs._string);
136+
_from_json = rhs._from_json;
137+
rhs._from_json = false;
128138
_scalar = std::move(rhs._scalar);
129139
_boolean = rhs._boolean;
130140
rhs._boolean = false;
@@ -180,11 +190,24 @@ bool Value::operator!=(const Value& rhs) const noexcept
180190
return !(*this == rhs);
181191
}
182192

183-
Type Value::type() const
193+
Type Value::type() const noexcept
184194
{
185195
return _type;
186196
}
187197

198+
Value&& Value::from_json() noexcept
199+
{
200+
_from_json = true;
201+
202+
return std::move(*this);
203+
}
204+
205+
bool Value::maybe_enum() const noexcept
206+
{
207+
return _type == Type::EnumValue
208+
|| (_from_json && _type == Type::String);
209+
}
210+
188211
void Value::reserve(size_t count)
189212
{
190213
switch (_type)

JSONResponse.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ struct ResponseHandler
185185

186186
bool String(const Ch* str, rapidjson::SizeType /*length*/, bool /*copy*/)
187187
{
188-
setValue(Value(std::string(str)));
188+
setValue(Value(std::string(str)).from_json());
189189
return true;
190190
}
191191

SchemaGenerator.cpp

Lines changed: 40 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,7 +1623,7 @@ template <>
16231623
sourceFile << R"cpp(
16241624
};
16251625
1626-
if (value.type() != response::Type::EnumValue)
1626+
if (!value.maybe_enum())
16271627
{
16281628
throw service::schema_exception({ "not a valid )cpp" << enumType.type << R"cpp( value" });
16291629
}
@@ -1719,33 +1719,7 @@ template <>
17191719

17201720
for (const auto& inputField : inputType.fields)
17211721
{
1722-
std::string fieldName(inputField.name);
1723-
1724-
fieldName[0] = std::toupper(fieldName[0]);
1725-
if (inputField.defaultValue.type() == response::Type::Null)
1726-
{
1727-
sourceFile << R"cpp( auto value)cpp" << fieldName
1728-
<< R"cpp( = )cpp" << getArgumentAccessType(inputField)
1729-
<< R"cpp(::require)cpp" << getTypeModifiers(inputField.modifiers)
1730-
<< R"cpp((")cpp" << inputField.name
1731-
<< R"cpp(", value);
1732-
)cpp";
1733-
}
1734-
else
1735-
{
1736-
sourceFile << R"cpp( auto pair)cpp" << fieldName
1737-
<< R"cpp( = )cpp" << getArgumentAccessType(inputField)
1738-
<< R"cpp(::find)cpp" << getTypeModifiers(inputField.modifiers)
1739-
<< R"cpp((")cpp" << inputField.name
1740-
<< R"cpp(", value);
1741-
auto value)cpp" << fieldName << R"cpp( = (pair)cpp" << fieldName << R"cpp(.second
1742-
? std::move(pair)cpp" << fieldName << R"cpp(.first)
1743-
: )cpp" << getArgumentAccessType(inputField)
1744-
<< R"cpp(::require)cpp" << getTypeModifiers(inputField.modifiers)
1745-
<< R"cpp((")cpp" << inputField.name
1746-
<< R"cpp(", defaultValue));
1747-
)cpp";
1748-
}
1722+
sourceFile << getArgumentDeclaration(inputField, "value", "value", "defaultValue");
17491723
}
17501724

17511725
if (!inputType.fields.empty())
@@ -1942,33 +1916,7 @@ std::future<response::Value> )cpp" << objectType.type
19421916

19431917
for (const auto& argument : outputField.arguments)
19441918
{
1945-
std::string argumentName(argument.name);
1946-
1947-
argumentName[0] = std::toupper(argumentName[0]);
1948-
if (argument.defaultValue.type() == response::Type::Null)
1949-
{
1950-
sourceFile << R"cpp( auto arg)cpp" << argumentName
1951-
<< R"cpp( = )cpp" << getArgumentAccessType(argument)
1952-
<< R"cpp(::require)cpp" << getTypeModifiers(argument.modifiers)
1953-
<< R"cpp((")cpp" << argument.name
1954-
<< R"cpp(", params.arguments);
1955-
)cpp";
1956-
}
1957-
else
1958-
{
1959-
sourceFile << R"cpp( auto pair)cpp" << argumentName
1960-
<< R"cpp( = )cpp" << getArgumentAccessType(argument)
1961-
<< R"cpp(::find)cpp" << getTypeModifiers(argument.modifiers)
1962-
<< R"cpp((")cpp" << argument.name
1963-
<< R"cpp(", params.arguments);
1964-
auto arg)cpp" << argumentName << R"cpp( = (pair)cpp" << argumentName << R"cpp(.second
1965-
? std::move(pair)cpp" << argumentName << R"cpp(.first)
1966-
: )cpp" << getArgumentAccessType(argument)
1967-
<< R"cpp(::require)cpp" << getTypeModifiers(argument.modifiers)
1968-
<< R"cpp((")cpp" << argument.name
1969-
<< R"cpp(", defaultArguments));
1970-
)cpp";
1971-
}
1919+
sourceFile << getArgumentDeclaration(argument, "arg", "params.arguments", "defaultArguments");
19721920
}
19731921
}
19741922

@@ -2733,6 +2681,43 @@ std::string Generator::getArgumentDefaultValue(size_t level, const response::Val
27332681
return argumentDefaultValue.str();
27342682
}
27352683

2684+
std::string Generator::getArgumentDeclaration(const InputField& argument, const char* prefixToken, const char* argumentsToken, const char* defaultToken) const noexcept
2685+
{
2686+
std::ostringstream argumentDeclaration;
2687+
std::string argumentName(argument.name);
2688+
2689+
argumentName[0] = std::toupper(argumentName[0]);
2690+
if (argument.defaultValue.type() == response::Type::Null)
2691+
{
2692+
argumentDeclaration << R"cpp( auto )cpp" << prefixToken << argumentName
2693+
<< R"cpp( = )cpp" << getArgumentAccessType(argument)
2694+
<< R"cpp(::require)cpp" << getTypeModifiers(argument.modifiers)
2695+
<< R"cpp((")cpp" << argument.name
2696+
<< R"cpp(", )cpp" << argumentsToken
2697+
<< R"cpp();
2698+
)cpp";
2699+
}
2700+
else
2701+
{
2702+
argumentDeclaration << R"cpp( auto pair)cpp" << argumentName
2703+
<< R"cpp( = )cpp" << getArgumentAccessType(argument)
2704+
<< R"cpp(::find)cpp" << getTypeModifiers(argument.modifiers)
2705+
<< R"cpp((")cpp" << argument.name
2706+
<< R"cpp(", )cpp" << argumentsToken
2707+
<< R"cpp();
2708+
auto )cpp" << prefixToken << argumentName << R"cpp( = (pair)cpp" << argumentName << R"cpp(.second
2709+
? std::move(pair)cpp" << argumentName << R"cpp(.first)
2710+
: )cpp" << getArgumentAccessType(argument)
2711+
<< R"cpp(::require)cpp" << getTypeModifiers(argument.modifiers)
2712+
<< R"cpp((")cpp" << argument.name
2713+
<< R"cpp(", )cpp" << defaultToken
2714+
<< R"cpp());
2715+
)cpp";
2716+
}
2717+
2718+
return argumentDeclaration.str();
2719+
}
2720+
27362721
std::string Generator::getArgumentAccessType(const InputField& argument) const noexcept
27372722
{
27382723
std::ostringstream argumentType;

include/SchemaGenerator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ class Generator
281281

282282
bool outputSource() const noexcept;
283283
std::string getArgumentDefaultValue(size_t level, const response::Value& defaultValue) const noexcept;
284+
std::string getArgumentDeclaration(const InputField& argument, const char* prefixToken, const char* argumentsToken, const char* defaultToken) const noexcept;
284285
std::string getArgumentAccessType(const InputField& argument) const noexcept;
285286
std::string getResultAccessType(const OutputField& result) const noexcept;
286287
std::string getTypeModifiers(const TypeModifierStack& modifiers) const noexcept;

include/graphqlservice/GraphQLResponse.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct Value
4444
Value(Type type = Type::Null);
4545
~Value();
4646

47+
explicit Value(const char* value);
4748
explicit Value(StringType&& value);
4849
explicit Value(BooleanType value);
4950
explicit Value(IntType value);
@@ -60,7 +61,12 @@ struct Value
6061
bool operator!=(const Value& rhs) const noexcept;
6162

6263
// Check the Type
63-
Type type() const;
64+
Type type() const noexcept;
65+
66+
// JSON doesn't distinguish between Type::String and Type::EnumValue, so if this value comes
67+
// from JSON and it's a string we need to track the fact that it can be interpreted as either.
68+
Value&& from_json() noexcept;
69+
bool maybe_enum() const noexcept;
6470

6571
// Valid for Type::Map or Type::List
6672
void reserve(size_t count);
@@ -101,6 +107,7 @@ struct Value
101107

102108
// Type::String or Type::EnumValue
103109
std::unique_ptr<StringType> _string;
110+
bool _from_json = false;
104111

105112
// Type::Boolean
106113
BooleanType _boolean = false;

samples/IntrospectionSchema.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ introspection::__TypeKind ModifiedArgument<introspection::__TypeKind>::convert(c
2727
{ "NON_NULL", introspection::__TypeKind::NON_NULL }
2828
};
2929

30-
if (value.type() != response::Type::EnumValue)
30+
if (!value.maybe_enum())
3131
{
3232
throw service::schema_exception({ "not a valid __TypeKind value" });
3333
}
@@ -89,7 +89,7 @@ introspection::__DirectiveLocation ModifiedArgument<introspection::__DirectiveLo
8989
{ "INPUT_FIELD_DEFINITION", introspection::__DirectiveLocation::INPUT_FIELD_DEFINITION }
9090
};
9191

92-
if (value.type() != response::Type::EnumValue)
92+
if (!value.maybe_enum())
9393
{
9494
throw service::schema_exception({ "not a valid __DirectiveLocation value" });
9595
}

samples/TodaySchema.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ today::TaskState ModifiedArgument<today::TaskState>::convert(const response::Val
2525
{ "Unassigned", today::TaskState::Unassigned }
2626
};
2727

28-
if (value.type() != response::Type::EnumValue)
28+
if (!value.maybe_enum())
2929
{
3030
throw service::schema_exception({ "not a valid TaskState value" });
3131
}

tests.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,50 @@ TEST(ArgumentsCase, TaskStateEnum)
12421242
EXPECT_EQ(today::TaskState::Started, actual) << "should parse the enum";
12431243
}
12441244

1245+
TEST(ArgumentsCase, TaskStateEnumFromString)
1246+
{
1247+
response::Value response(response::Type::Map);
1248+
response::Value status("Started");
1249+
response.emplace_back("status", std::move(status));
1250+
today::TaskState actual = static_cast<today::TaskState>(-1);
1251+
bool caughtException = false;
1252+
std::string exceptionWhat;
1253+
1254+
try
1255+
{
1256+
actual = service::ModifiedArgument<today::TaskState>::require("status", response);
1257+
}
1258+
catch (const service::schema_exception& ex)
1259+
{
1260+
caughtException = true;
1261+
exceptionWhat = response::toJSON(response::Value(ex.getErrors()));
1262+
}
1263+
1264+
EXPECT_NE(today::TaskState::Started, actual) << "should not parse the enum from a known string value";
1265+
ASSERT_TRUE(caughtException);
1266+
EXPECT_EQ(R"js([{"message":"Invalid argument: status message: not a valid TaskState value"}])js", exceptionWhat) << "exception should match";
1267+
}
1268+
1269+
TEST(ArgumentsCase, TaskStateEnumFromJSONString)
1270+
{
1271+
response::Value response(response::Type::Map);
1272+
response::Value status("Started");
1273+
response.emplace_back("status", status.from_json());
1274+
today::TaskState actual = static_cast<today::TaskState>(-1);
1275+
1276+
1277+
try
1278+
{
1279+
actual = service::ModifiedArgument<today::TaskState>::require("status", response);
1280+
}
1281+
catch (const service::schema_exception& ex)
1282+
{
1283+
FAIL() << response::toJSON(response::Value(ex.getErrors()));
1284+
}
1285+
1286+
EXPECT_EQ(today::TaskState::Started, actual) << "should parse the enum";
1287+
}
1288+
12451289
TEST(PegtlCase, ParseKitchenSinkQuery)
12461290
{
12471291
memory_input<> input(R"gql(
@@ -1624,3 +1668,12 @@ TEST(PegtlCase, AnalyzeGrammar)
16241668
{
16251669
ASSERT_EQ(0, analyze<document>(true)) << "there shuldn't be any infinite loops in the PEG version of the grammar";
16261670
}
1671+
1672+
TEST(ResponseCase, ValueConstructorFromStringLiteral)
1673+
{
1674+
auto expected = "Test String";
1675+
auto actual = response::Value(expected);
1676+
1677+
ASSERT_TRUE(response::Type::String == actual.type());
1678+
ASSERT_EQ(expected, actual.release<response::StringType>());
1679+
}

0 commit comments

Comments
 (0)