55
66#include " schema/ProxySchema.h"
77#include " schema/QueryObject.h"
8+ #include " schema/ResultsObject.h"
89
910#include " graphqlservice/JSONResponse.h"
1011
2122#include < boost/asio/use_awaitable.hpp>
2223#include < boost/asio/use_future.hpp>
2324
25+ #include < algorithm>
2426#include < cstdio>
2527#include < cstdlib>
2628#include < functional>
@@ -45,13 +47,111 @@ constexpr auto c_port = "8080"sv;
4547constexpr auto c_target = " /graphql" sv;
4648constexpr int c_version = 11 ; // HTTP 1.1
4749
50+ struct AsyncIoWorker : service::RequestState
51+ {
52+ AsyncIoWorker ()
53+ : worker { std::make_shared<service::await_worker_thread>() }
54+ {
55+ }
56+
57+ const service::await_async worker;
58+ };
59+
60+ class Results
61+ {
62+ public:
63+ explicit Results (response::Value&& data, std::vector<client::Error> errors) noexcept ;
64+
65+ service::AwaitableScalar<std::optional<std::string>> getData (
66+ service::FieldParams&& fieldParams) const ;
67+ service::AwaitableScalar<std::optional<std::vector<std::optional<std::string>>>> getErrors (
68+ service::FieldParams&& fieldParams) const ;
69+
70+ private:
71+ mutable response::Value m_data;
72+ mutable std::vector<client::Error> m_errors;
73+ };
74+
75+ Results::Results (response::Value&& data, std::vector<client::Error> errors) noexcept
76+ : m_data { std::move (data) }
77+ , m_errors { std::move (errors) }
78+ {
79+ }
80+
81+ service::AwaitableScalar<std::optional<std::string>> Results::getData (
82+ service::FieldParams&& fieldParams) const
83+ {
84+ auto asyncIoWorker = std::static_pointer_cast<AsyncIoWorker>(fieldParams.state );
85+ auto data = std::move (m_data);
86+
87+ // Jump to a worker thread for the resolver where we can run a separate I/O context without
88+ // blocking the I/O context in Query::getRelay. This simulates how you might fan out to
89+ // additional async I/O tasks for sub-field resolvers.
90+ co_await asyncIoWorker->worker ;
91+
92+ net::io_context ioc;
93+ auto future = net::co_spawn (
94+ ioc,
95+ [](response::Value&& data) -> net::awaitable<std::optional<std::string>> {
96+ co_return (data.type () == response::Type::Null)
97+ ? std::nullopt
98+ : std::make_optional (response::toJSON (std::move (data)));
99+ }(std::move (data)),
100+ net::use_future);
101+
102+ ioc.run ();
103+
104+ co_return future.get ();
105+ }
106+
107+ service::AwaitableScalar<std::optional<std::vector<std::optional<std::string>>>> Results::getErrors (
108+ service::FieldParams&& fieldParams) const
109+ {
110+ auto asyncIoWorker = std::static_pointer_cast<AsyncIoWorker>(fieldParams.state );
111+ auto errors = std::move (m_errors);
112+
113+ // Jump to a worker thread for the resolver where we can run a separate I/O context without
114+ // blocking the I/O context in Query::getRelay. This simulates how you might fan out to
115+ // additional async I/O tasks for sub-field resolvers.
116+ co_await asyncIoWorker->worker ;
117+
118+ net::io_context ioc;
119+ auto future = net::co_spawn (
120+ ioc,
121+ [](std::vector<client::Error> errors)
122+ -> net::awaitable<std::optional<std::vector<std::optional<std::string>>>> {
123+ if (errors.empty ())
124+ {
125+ co_return std::nullopt ;
126+ }
127+
128+ std::vector<std::optional<std::string>> results { errors.size () };
129+
130+ std::transform (errors.begin (),
131+ errors.end (),
132+ results.begin (),
133+ [](auto & error) noexcept -> std::optional<std::string> {
134+ return error.message .empty ()
135+ ? std::nullopt
136+ : std::make_optional<std::string>(std::move (error.message ));
137+ });
138+
139+ co_return std::make_optional (results);
140+ }(std::move (errors)),
141+ net::use_future);
142+
143+ ioc.run ();
144+
145+ co_return future.get ();
146+ }
147+
48148class Query
49149{
50150public:
51151 explicit Query (std::string_view host, std::string_view port, std::string_view target,
52152 int version) noexcept ;
53153
54- std::future<std::optional<std::string >> getRelay (std::string&& queryArg,
154+ std::future<std::shared_ptr<proxy::object::Results >> getRelay (std::string&& queryArg,
55155 std::optional<std::string>&& operationNameArg,
56156 std::optional<std::string>&& variablesArg) const ;
57157
@@ -73,7 +173,7 @@ Query::Query(
73173
74174// Based on:
75175// https://www.boost.org/doc/libs/1_82_0/libs/beast/example/http/client/awaitable/http_client_awaitable.cpp
76- std::future<std::optional<std::string >> Query::getRelay (std::string&& queryArg,
176+ std::future<std::shared_ptr<proxy::object::Results >> Query::getRelay (std::string&& queryArg,
77177 std::optional<std::string>&& operationNameArg, std::optional<std::string>&& variablesArg) const
78178{
79179 response::Value payload { response::Type::Map };
@@ -99,7 +199,7 @@ std::future<std::optional<std::string>> Query::getRelay(std::string&& queryArg,
99199 const char * port,
100200 const char * target,
101201 int version,
102- std::string requestBody) -> net::awaitable<std::optional<std::string >> {
202+ std::string requestBody) -> net::awaitable<std::shared_ptr<proxy::object::Results >> {
103203 // These objects perform our I/O. They use an executor with a default completion token
104204 // of use_awaitable. This makes our code easy, but will use exceptions as the default
105205 // error handling, i.e. if the connection drops, we might see an exception.
@@ -150,7 +250,10 @@ std::future<std::optional<std::string>> Query::getRelay(std::string&& queryArg,
150250 throw boost::system::system_error (ec, " shutdown" );
151251 }
152252
153- co_return std::make_optional<std::string>(std::move (res.body ()));
253+ auto [data, errors] = client::parseServiceResponse (response::parseJSON (res.body ()));
254+
255+ co_return std::make_shared<proxy::object::Results>(
256+ std::make_shared<Results>(std::move (data), std::move (errors)));
154257 }(m_host.c_str (), m_port.c_str (), m_target.c_str (), m_version, std::move (requestBody)),
155258 net::use_future);
156259
@@ -179,14 +282,25 @@ int main(int argc, char** argv)
179282 auto variables = serializeVariables (
180283 { input, ((argc > 1 ) ? std::make_optional (argv[1 ]) : std::nullopt ) });
181284 auto launch = service::await_async { std::make_shared<service::await_worker_queue>() };
285+ auto state = std::make_shared<AsyncIoWorker>();
182286 auto serviceResponse = client::parseServiceResponse (
183- service->resolve ({ query, GetOperationName (), std::move (variables), launch }).get ());
287+ service->resolve ({ query, GetOperationName (), std::move (variables), launch, state })
288+ .get ());
184289 auto result = client::query::relayQuery::parseResponse (std::move (serviceResponse.data ));
185290 auto errors = std::move (serviceResponse.errors );
186291
187- if (result.relay )
292+ if (result.relay .data )
293+ {
294+ std::cout << " Data: " << *result.relay .data << std::endl;
295+ }
296+
297+ if (result.relay .errors )
188298 {
189- std::cout << *result.relay << std::endl;
299+ for (const auto & message : *result.relay .errors )
300+ {
301+ std::cerr << " Remote Error: "
302+ << (message ? std::string_view { *message } : " <empty>" sv) << std::endl;
303+ }
190304 }
191305
192306 if (!errors.empty ())
0 commit comments