Skip to content

Commit 60589e1

Browse files
committed
Implement Directive validation
1 parent b12a87a commit 60589e1

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

src/Validation.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,8 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF
20222022

20232023
void ValidateExecutableVisitor::visitDirectives(introspection::DirectiveLocation location, const peg::ast_node& directives)
20242024
{
2025+
std::set<std::string> uniqueDirectives;
2026+
20252027
for (const auto& directive : directives.children)
20262028
{
20272029
std::string directiveName;
@@ -2032,6 +2034,18 @@ void ValidateExecutableVisitor::visitDirectives(introspection::DirectiveLocation
20322034
directiveName = child.string_view();
20332035
});
20342036

2037+
if (!uniqueDirectives.insert(directiveName).second)
2038+
{
2039+
// http://spec.graphql.org/June2018/#sec-Directives-Are-Unique-Per-Location
2040+
auto position = directive->begin();
2041+
std::ostringstream message;
2042+
2043+
message << "Conflicting directive name: " << directiveName;
2044+
2045+
_errors.push_back({ message.str(), { position.line, position.byte_in_line } });
2046+
continue;
2047+
}
2048+
20352049
auto itrDirective = _directives.find(directiveName);
20362050

20372051
if (itrDirective == _directives.end())
@@ -2046,6 +2060,52 @@ void ValidateExecutableVisitor::visitDirectives(introspection::DirectiveLocation
20462060
continue;
20472061
}
20482062

2063+
if (itrDirective->second.locations.find(location) == itrDirective->second.locations.end())
2064+
{
2065+
// http://spec.graphql.org/June2018/#sec-Directives-Are-In-Valid-Locations
2066+
auto position = directive->begin();
2067+
std::ostringstream message;
2068+
2069+
message << "Unexpected location for directive: " << directiveName;
2070+
2071+
switch (location)
2072+
{
2073+
case introspection::DirectiveLocation::QUERY:
2074+
message << " name: QUERY";
2075+
break;
2076+
2077+
case introspection::DirectiveLocation::MUTATION:
2078+
message << " name: MUTATION";
2079+
break;
2080+
2081+
case introspection::DirectiveLocation::SUBSCRIPTION:
2082+
message << " name: SUBSCRIPTION";
2083+
break;
2084+
2085+
case introspection::DirectiveLocation::FIELD:
2086+
message << " name: FIELD";
2087+
break;
2088+
2089+
case introspection::DirectiveLocation::FRAGMENT_DEFINITION:
2090+
message << " name: FRAGMENT_DEFINITION";
2091+
break;
2092+
2093+
case introspection::DirectiveLocation::FRAGMENT_SPREAD:
2094+
message << " name: FRAGMENT_SPREAD";
2095+
break;
2096+
2097+
case introspection::DirectiveLocation::INLINE_FRAGMENT:
2098+
message << " name: INLINE_FRAGMENT";
2099+
break;
2100+
2101+
default:
2102+
break;
2103+
}
2104+
2105+
_errors.push_back({ message.str(), { position.line, position.byte_in_line } });
2106+
continue;
2107+
}
2108+
20492109
peg::on_first_child<peg::arguments>(*directive,
20502110
[this, &directive, &directiveName, itrDirective](const peg::ast_node& child)
20512111
{

test/ValidationTests.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,3 +1272,52 @@ TEST_F(ValidationExamplesCase, CounterExample149)
12721272
ASSERT_GE(errors.size(), size_t{ 1 });
12731273
EXPECT_EQ(R"js({"message":"Conflicting input field name: name","locations":[{"line":2,"column":37}]})js", response::toJSON(std::move(errors[0]))) << "error should match";
12741274
}
1275+
1276+
TEST_F(ValidationExamplesCase, CounterExample150)
1277+
{
1278+
// http://spec.graphql.org/June2018/#example-55f3f
1279+
auto query = R"(query @skip(if: $foo) {
1280+
dog {
1281+
name
1282+
}
1283+
})"_graphql;
1284+
1285+
auto errors = service::buildErrorValues(_service->validate(query)).release<response::ListType>();
1286+
1287+
EXPECT_EQ(errors.size(), 1) << "1 unexpected location";
1288+
ASSERT_GE(errors.size(), size_t{ 1 });
1289+
EXPECT_EQ(R"js({"message":"Unexpected location for directive: skip name: QUERY","locations":[{"line":1,"column":7}]})js", response::toJSON(std::move(errors[0]))) << "error should match";
1290+
}
1291+
1292+
TEST_F(ValidationExamplesCase, CounterExample151)
1293+
{
1294+
// http://spec.graphql.org/June2018/#example-b2e6c
1295+
auto query = R"(query ($foo: Boolean = true, $bar: Boolean = false) {
1296+
dog @skip(if: $foo) @skip(if: $bar) {
1297+
name
1298+
}
1299+
})"_graphql;
1300+
1301+
auto errors = service::buildErrorValues(_service->validate(query)).release<response::ListType>();
1302+
1303+
EXPECT_EQ(errors.size(), 1) << "1 conflicting directive";
1304+
ASSERT_GE(errors.size(), size_t{ 1 });
1305+
EXPECT_EQ(R"js({"message":"Conflicting directive name: skip","locations":[{"line":2,"column":24}]})js", response::toJSON(std::move(errors[0]))) << "error should match";
1306+
}
1307+
1308+
TEST_F(ValidationExamplesCase, Example152)
1309+
{
1310+
// http://spec.graphql.org/June2018/#example-c5ee9
1311+
auto query = R"(query ($foo: Boolean = true, $bar: Boolean = false) {
1312+
dog @skip(if: $foo) {
1313+
name
1314+
}
1315+
dog @skip(if: $bar) {
1316+
nickname
1317+
}
1318+
})"_graphql;
1319+
1320+
auto errors = service::buildErrorValues(_service->validate(query)).release<response::ListType>();
1321+
1322+
ASSERT_TRUE(errors.empty());
1323+
}

0 commit comments

Comments
 (0)