5
5
6
6
#include " schema/ProxySchema.h"
7
7
#include " schema/QueryObject.h"
8
+ #include " schema/ResultsObject.h"
8
9
9
10
#include " graphqlservice/JSONResponse.h"
10
11
21
22
#include < boost/asio/use_awaitable.hpp>
22
23
#include < boost/asio/use_future.hpp>
23
24
25
+ #include < algorithm>
24
26
#include < cstdio>
25
27
#include < cstdlib>
26
28
#include < functional>
@@ -45,13 +47,111 @@ constexpr auto c_port = "8080"sv;
45
47
constexpr auto c_target = " /graphql" sv;
46
48
constexpr int c_version = 11 ; // HTTP 1.1
47
49
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
+
48
148
class Query
49
149
{
50
150
public:
51
151
explicit Query (std::string_view host, std::string_view port, std::string_view target,
52
152
int version) noexcept ;
53
153
54
- std::future<std::optional<std::string >> getRelay (std::string&& queryArg,
154
+ std::future<std::shared_ptr<proxy::object::Results >> getRelay (std::string&& queryArg,
55
155
std::optional<std::string>&& operationNameArg,
56
156
std::optional<std::string>&& variablesArg) const ;
57
157
@@ -73,7 +173,7 @@ Query::Query(
73
173
74
174
// Based on:
75
175
// 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,
77
177
std::optional<std::string>&& operationNameArg, std::optional<std::string>&& variablesArg) const
78
178
{
79
179
response::Value payload { response::Type::Map };
@@ -99,7 +199,7 @@ std::future<std::optional<std::string>> Query::getRelay(std::string&& queryArg,
99
199
const char * port,
100
200
const char * target,
101
201
int version,
102
- std::string requestBody) -> net::awaitable<std::optional<std::string >> {
202
+ std::string requestBody) -> net::awaitable<std::shared_ptr<proxy::object::Results >> {
103
203
// These objects perform our I/O. They use an executor with a default completion token
104
204
// of use_awaitable. This makes our code easy, but will use exceptions as the default
105
205
// 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,
150
250
throw boost::system::system_error (ec, " shutdown" );
151
251
}
152
252
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)));
154
257
}(m_host.c_str (), m_port.c_str (), m_target.c_str (), m_version, std::move (requestBody)),
155
258
net::use_future);
156
259
@@ -179,14 +282,25 @@ int main(int argc, char** argv)
179
282
auto variables = serializeVariables (
180
283
{ input, ((argc > 1 ) ? std::make_optional (argv[1 ]) : std::nullopt) });
181
284
auto launch = service::await_async { std::make_shared<service::await_worker_queue>() };
285
+ auto state = std::make_shared<AsyncIoWorker>();
182
286
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 ());
184
289
auto result = client::query::relayQuery::parseResponse (std::move (serviceResponse.data ));
185
290
auto errors = std::move (serviceResponse.errors );
186
291
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 )
188
298
{
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
+ }
190
304
}
191
305
192
306
if (!errors.empty ())
0 commit comments