X Tutup
Skip to content

Commit f254b29

Browse files
committed
Switch to resolving against peg::ast and tracking whether or not it's validated
1 parent 4c2310f commit f254b29

File tree

5 files changed

+216
-159
lines changed

5 files changed

+216
-159
lines changed

include/graphqlservice/GraphQLParse.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct ast
1919
{
2020
std::shared_ptr<ast_input> input;
2121
std::shared_ptr<ast_node> root;
22+
bool validated = false;
2223
};
2324

2425
ast parseString(std::string_view input);

include/graphqlservice/GraphQLService.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,13 +799,18 @@ class Request : public std::enable_shared_from_this<Request>
799799
virtual ~Request() = default;
800800

801801
public:
802-
std::vector<schema_error> validate(const peg::ast_node& root) const;
802+
std::vector<schema_error> validate(peg::ast& query) const;
803803

804804
std::pair<std::string, const peg::ast_node*> findOperationDefinition(const peg::ast_node& root, const std::string& operationName) const;
805805

806+
[[deprecated("Use the Request::resolve overload which takes a peg::ast reference instead.")]]
806807
std::future<response::Value> resolve(const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const;
808+
[[deprecated("Use the Request::resolve overload which takes a peg::ast reference instead.")]]
807809
std::future<response::Value> resolve(std::launch launch, const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const;
808810

811+
std::future<response::Value> resolve(const std::shared_ptr<RequestState>& state, peg::ast& query, const std::string& operationName, response::Value&& variables) const;
812+
std::future<response::Value> resolve(std::launch launch, const std::shared_ptr<RequestState>& state, peg::ast& query, const std::string& operationName, response::Value&& variables) const;
813+
809814
SubscriptionKey subscribe(SubscriptionParams&& params, SubscriptionCallback&& callback);
810815
void unsubscribe(SubscriptionKey key);
811816

@@ -818,6 +823,8 @@ class Request : public std::enable_shared_from_this<Request>
818823
void deliver(std::launch launch, const SubscriptionName& name, const SubscriptionFilterCallback& apply, const std::shared_ptr<Object>& subscriptionObject) const;
819824

820825
private:
826+
std::future<response::Value> resolveValidated(std::launch launch, const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const;
827+
821828
TypeMap _operations;
822829
std::map<SubscriptionKey, std::shared_ptr<SubscriptionData>> _subscriptions;
823830
std::unordered_map<SubscriptionName, std::set<SubscriptionKey>> _listeners;

src/GraphQLService.cpp

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1816,10 +1816,6 @@ class ValidateExecutableVisitor
18161816
ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service)
18171817
: _service(service)
18181818
{
1819-
// This is taking advantage of the fact that during validation we can choose to execute
1820-
// unvalidated queries. Normally you can't have fragment cycles, so tools like GraphiQL work
1821-
// around that limitation by nesting the ofType selection set many layers deep to eventually
1822-
// read the underlying type when it's wrapped in List or NotNull types.
18231819
auto data = executeQuery(R"gql(query {
18241820
__schema {
18251821
queryType {
@@ -1993,12 +1989,18 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service)
19931989

19941990
response::Value ValidateExecutableVisitor::executeQuery(std::string_view query) const
19951991
{
1992+
auto ast = peg::parseString(query);
1993+
1994+
// This is taking advantage of the fact that during validation we can choose to execute
1995+
// unvalidated queries against the Introspection schema. This way we can use fragment
1996+
// cycles to expand an arbitrary number of wrapper types.
1997+
ast.validated = true;
1998+
19961999
response::Value data(response::Type::Map);
19972000
std::shared_ptr<RequestState> state;
1998-
auto ast = peg::parseString(query);
19992001
const std::string operationName;
20002002
response::Value variables(response::Type::Map);
2001-
auto result = _service.resolve(state, *ast.root, operationName, std::move(variables)).get();
2003+
auto result = _service.resolve(state, ast, operationName, std::move(variables)).get();
20022004
auto members = result.release<response::MapType>();
20032005
auto itrResponse = std::find_if(members.begin(), members.end(),
20042006
[](const std::pair<std::string, response::Value>& entry) noexcept
@@ -2370,10 +2372,6 @@ ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor:
23702372
{
23712373
std::ostringstream oss;
23722374

2373-
// This is taking advantage of the fact that during validation we can choose to execute
2374-
// unvalidated queries. Normally you can't have fragment cycles, so tools like GraphiQL work
2375-
// around that limitation by nesting the ofType selection set many layers deep to eventually
2376-
// read the underlying type when it's wrapped in List or NotNull types.
23772375
oss << R"gql(query {
23782376
__type(name: ")gql" << _scopedType << R"gql(") {
23792377
fields(includeDeprecated: true) {
@@ -3560,13 +3558,21 @@ Request::Request(TypeMap&& operationTypes)
35603558
{
35613559
}
35623560

3563-
std::vector<schema_error> Request::validate(const peg::ast_node& root) const
3561+
std::vector<schema_error> Request::validate(peg::ast& query) const
35643562
{
3565-
ValidateExecutableVisitor visitor(*this);
3563+
std::vector<schema_error> errors;
3564+
3565+
if (!query.validated)
3566+
{
3567+
ValidateExecutableVisitor visitor(*this);
3568+
3569+
visitor.visit(*query.root);
35663570

3567-
visitor.visit(root);
3571+
errors = visitor.getStructuredErrors();
3572+
query.validated = errors.empty();
3573+
}
35683574

3569-
return visitor.getStructuredErrors();
3575+
return errors;
35703576
}
35713577

35723578
std::pair<std::string, const peg::ast_node*> Request::findOperationDefinition(const peg::ast_node& root, const std::string& operationName) const
@@ -3667,10 +3673,40 @@ std::pair<std::string, const peg::ast_node*> Request::findOperationDefinition(co
36673673

36683674
std::future<response::Value> Request::resolve(const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const
36693675
{
3670-
return resolve(std::launch::deferred, state, root, operationName, std::move(variables));
3676+
return resolveValidated(std::launch::deferred, state, root, operationName, std::move(variables));
36713677
}
36723678

36733679
std::future<response::Value> Request::resolve(std::launch launch, const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const
3680+
{
3681+
return resolveValidated(launch, state, root, operationName, std::move(variables));
3682+
}
3683+
3684+
std::future<response::Value> Request::resolve(const std::shared_ptr<RequestState>& state, peg::ast& query, const std::string& operationName, response::Value&& variables) const
3685+
{
3686+
return resolve(std::launch::deferred, state, query, operationName, std::move(variables));
3687+
}
3688+
3689+
std::future<response::Value> Request::resolve(std::launch launch, const std::shared_ptr<RequestState>& state, peg::ast& query, const std::string& operationName, response::Value&& variables) const
3690+
{
3691+
auto errors = validate(query);
3692+
3693+
if (!errors.empty())
3694+
{
3695+
schema_exception ex { std::move(errors) };
3696+
std::promise<response::Value> promise;
3697+
response::Value document(response::Type::Map);
3698+
3699+
document.emplace_back(std::string { strData }, response::Value());
3700+
document.emplace_back(std::string { strErrors }, ex.getErrors());
3701+
promise.set_value(std::move(document));
3702+
3703+
return promise.get_future();
3704+
}
3705+
3706+
return resolveValidated(launch, state, *query.root, operationName, std::move(variables));
3707+
}
3708+
3709+
std::future<response::Value> Request::resolveValidated(std::launch launch, const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const
36743710
{
36753711
try
36763712
{
@@ -3756,6 +3792,13 @@ std::future<response::Value> Request::resolve(std::launch launch, const std::sha
37563792

37573793
SubscriptionKey Request::subscribe(SubscriptionParams&& params, SubscriptionCallback&& callback)
37583794
{
3795+
auto errors = validate(params.query);
3796+
3797+
if (!errors.empty())
3798+
{
3799+
throw schema_exception { std::move(errors) };
3800+
}
3801+
37593802
FragmentDefinitionVisitor fragmentVisitor(params.variables);
37603803

37613804
peg::for_each_child<peg::fragment_definition>(*params.query.root,

0 commit comments

Comments
 (0)
X Tutup