1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QJSONTYPEDRPC_P_H
5#define QJSONTYPEDRPC_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtJsonRpc/private/qjsonrpcprotocol_p.h>
19#include <QtJsonRpc/private/qjsonrpctransport_p.h>
20#include <QtJsonRpc/private/qtypedjson_p.h>
21#include <QtCore/qjsondocument.h>
22#include <functional>
23#include <variant>
24
25QT_BEGIN_NAMESPACE
26
27namespace QJsonRpc {
28class TypedRpc;
29
30using IdType = std::variant<int, QByteArray>;
31
32template<typename... T>
33QString idToString(const std::variant<T...> &v)
34{
35 struct ToStr
36 {
37 QString operator()(const QByteArray &v) { return QString::fromUtf8(ba: v); }
38
39 QString operator()(int v) { return QString::number(v); }
40
41 QString operator()(std::nullptr_t) { return QStringLiteral("null"); }
42 } toStr;
43 return std::visit(toStr, v);
44}
45
46// concurrent usage by multiple threads not supported, the user should take care
47class Q_JSONRPC_EXPORT TypedResponse
48{
49 Q_DISABLE_COPY(TypedResponse)
50public:
51 enum class Status { Started, SentSuccess, SentError, Invalid };
52 TypedResponse() = default;
53
54 TypedResponse(IdType id, TypedRpc *typedRpc,
55 const QJsonRpcProtocol::ResponseHandler &responseHandler,
56 Status status = Status::Started)
57 : m_status(status), m_id(id), m_typedRpc(typedRpc), m_responseHandler(responseHandler)
58 {
59 }
60 TypedResponse(TypedResponse &&o)
61 : m_status(o.m_status),
62 m_id(o.m_id),
63 m_typedRpc(o.m_typedRpc),
64 m_responseHandler(std::move(o.m_responseHandler))
65 {
66 o.m_status = Status::Invalid;
67 }
68 TypedResponse &operator=(TypedResponse &&o) noexcept
69 {
70 m_status = o.m_status;
71 m_id = o.m_id;
72 m_typedRpc = o.m_typedRpc;
73 m_responseHandler = std::move(o.m_responseHandler);
74 o.m_status = Status::Invalid;
75 return *this;
76 }
77 ~TypedResponse()
78 {
79 if (m_status == Status::Started)
80 sendErrorResponse(code: int(QJsonRpcProtocol::ErrorCode::InternalError),
81 message: QByteArray("Response destroyed before having sent a response"),
82 data: nullptr);
83 }
84
85 template<typename T>
86 void sendSuccessfullResponse(const T &result);
87 template<typename T>
88 void sendErrorResponse(int code, const QByteArray &message, const T &data);
89 void sendErrorResponse(int code, const QByteArray &message);
90 template<typename... Params>
91 void sendNotification(const QByteArray &method, const Params &...params);
92
93 IdType id() const { return m_id; }
94 QString idStr()
95 {
96 if (const int *iPtr = std::get_if<int>(ptr: &m_id))
97 return QString::number(*iPtr);
98 else if (const QByteArray *bPtr = std::get_if<QByteArray>(ptr: &m_id))
99 return QString::fromUtf8(ba: *bPtr);
100 else
101 return QString();
102 }
103 using OnCloseAction = std::function<void(Status, const IdType &, TypedRpc &)>;
104 void addOnCloseAction(const OnCloseAction &act);
105
106private:
107 void doOnCloseActions();
108 Status m_status = Status::Invalid;
109 IdType m_id;
110 TypedRpc *m_typedRpc = nullptr;
111 QJsonRpcProtocol::ResponseHandler m_responseHandler;
112 QList<OnCloseAction> m_onCloseActions;
113};
114
115class Q_JSONRPC_EXPORT TypedHandler : public QJsonRpcProtocol::MessageHandler
116{
117public:
118 TypedHandler() = default; // invalid instance
119 TypedHandler(const QByteArray &method,
120 const std::function<void(const QJsonRpcProtocol::Request &,
121 const QJsonRpcProtocol::ResponseHandler &)> &rHandler,
122 const std::function<void(const QJsonRpcProtocol::Notification &)> &nHandler)
123 : m_method(method), m_requestHandler(rHandler), m_notificationHandler(nHandler)
124 {
125 }
126
127 TypedHandler(const QByteArray &method,
128 const std::function<void(const QJsonRpcProtocol::Request &,
129 const QJsonRpcProtocol::ResponseHandler &)> &rHandler)
130 : m_method(method), m_requestHandler(rHandler), m_notificationHandler()
131 {
132 }
133
134 TypedHandler(const QByteArray &method,
135 const std::function<void(const QJsonRpcProtocol::Notification &)> &nHandler)
136 : m_method(method), m_requestHandler(), m_notificationHandler(nHandler)
137 {
138 }
139
140 ~TypedHandler() = default;
141
142 void handleRequest(const QJsonRpcProtocol::Request &request,
143 const QJsonRpcProtocol::ResponseHandler &handler) override
144 {
145 using namespace Qt::StringLiterals;
146
147 if (m_requestHandler) {
148 m_requestHandler(request, handler);
149 return;
150 }
151 QString msg;
152 if (m_notificationHandler)
153 msg = u"Expected notification with method '%1', not request"_s;
154 else
155 msg = u"Reached null handler for method '%1'"_s;
156 msg = msg.arg(a: request.method);
157 handler(MessageHandler::error(code: int(QJsonRpcProtocol::ErrorCode::InvalidRequest), message: msg));
158 qCWarning(QTypedJson::jsonRpcLog) << msg;
159 }
160
161 QByteArray method() const { return m_method; }
162
163 void handleNotification(const QJsonRpcProtocol::Notification &notification) override
164 {
165 if (m_notificationHandler) {
166 m_notificationHandler(notification);
167 return;
168 }
169 if (m_requestHandler)
170 qCWarning(QTypedJson::jsonRpcLog) << "Expected Request but got notification for "
171 << notification.method << ", ignoring it.";
172 else
173 qCWarning(QTypedJson::jsonRpcLog)
174 << "Reached null handler for method " << notification.method;
175 }
176
177private:
178 QByteArray m_method;
179 std::function<void(const QJsonRpcProtocol::Request &,
180 const QJsonRpcProtocol::ResponseHandler &)>
181 m_requestHandler;
182 std::function<void(const QJsonRpcProtocol::Notification &)> m_notificationHandler;
183};
184
185class Q_JSONRPC_EXPORT TypedRpc : public QJsonRpcProtocol
186{
187 Q_DISABLE_COPY_MOVE(TypedRpc)
188public:
189 TypedRpc() = default;
190
191 template<typename... Params>
192 void sendRequestId(const std::variant<int, QByteArray> &id, const QByteArray &method,
193 const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &rHandler,
194 const Params &...params)
195 {
196 QJsonRpcProtocol::sendRequest(request: Request { QTypedJson::toJsonValue(params: id),
197 QString::fromUtf8(ba: method),
198 QTypedJson::toJsonValue(params...) },
199 handler: rHandler);
200 }
201
202 template<typename... Params>
203 void sendRequest(const QByteArray &method,
204 const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &handler,
205 const Params &...params)
206 {
207 sendRequestId(++m_lastId, method, handler, params...);
208 }
209
210 template<typename... Params>
211 void sendNotification(const QByteArray &method, const Params &...params)
212 {
213 QJsonRpcProtocol::sendNotification(
214 notification: Notification { QString::fromUtf8(ba: method), QTypedJson::toJsonValue(params...) });
215 }
216
217 template<typename Req, typename Resp>
218 void registerRequestHandler(
219 const QByteArray &method,
220 const std::function<void(const QByteArray &, const Req &, Resp &&)> &handler)
221 {
222 if (m_handlers.contains(key: method) && handler) {
223 qCWarning(QTypedJson::jsonRpcLog)
224 << "QJsonRpc double registration for method" << QString::fromUtf8(ba: method);
225 Q_ASSERT(false);
226 return;
227 }
228 TypedHandler *h;
229 if (handler)
230 h = new TypedHandler(
231 method,
232 [handler, method, this](const QJsonRpcProtocol::Request &req,
233 const QJsonRpcProtocol::ResponseHandler &rH) {
234 std::variant<int, QByteArray> id = req.id.toInt(defaultValue: 0);
235 if (req.id.isString())
236 id = req.id.toString().toUtf8();
237 TypedResponse typedResponse(id, this, rH);
238 Req tReq;
239 {
240 QTypedJson::Reader r(req.params);
241 QTypedJson::doWalk(r, tReq);
242 if (!r.errorMessages().isEmpty()) {
243 qCWarning(QTypedJson::jsonRpcLog)
244 << "Warnings decoding parameters for Request" << method
245 << idToString(v: id) << "from" << req.params << ":\n "
246 << r.errorMessages().join(sep: u"\n ");
247 r.clearErrorMessages();
248 }
249 }
250 Resp myResponse(std::move(typedResponse));
251 handler(method, tReq, std::move(myResponse));
252 });
253 else
254 h = new TypedHandler;
255 m_handlers[method] = h;
256 setMessageHandler(method: QString::fromUtf8(ba: method), handler: h);
257 }
258
259 template<typename N>
260 void registerNotificationHandler(
261 const QByteArray &method,
262 const std::function<void(const QByteArray &, const N &)> &handler)
263 {
264 if (m_handlers.contains(key: method) && handler) {
265 qCWarning(QTypedJson::jsonRpcLog)
266 << "QJsonRpc double registration for method" << QString::fromUtf8(ba: method);
267 Q_ASSERT(false);
268 return;
269 }
270 TypedHandler *h;
271 if (handler)
272 h = new TypedHandler(
273 method, [handler, method](const QJsonRpcProtocol::Notification &notif) {
274 N tNotif;
275 {
276 QTypedJson::Reader r(notif.params);
277 QTypedJson::doWalk(r, tNotif);
278 if (!r.errorMessages().isEmpty()) {
279 qCWarning(QTypedJson::jsonRpcLog)
280 << "Warnings decoding parameters for Notification" << method
281 << "from" << notif.params << ":\n "
282 << r.errorMessages().join(sep: u"\n ");
283 r.clearErrorMessages();
284 }
285 }
286 handler(method, tNotif);
287 });
288 else
289 h = new TypedHandler;
290 setMessageHandler(method: QString::fromUtf8(ba: method), handler: h);
291 m_handlers[method] = h;
292 }
293
294 void sendBatch(
295 QJsonRpcProtocol::Batch &&batch,
296 const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &handler)
297 = delete; // disable batch support
298 void installOnCloseAction(const TypedResponse::OnCloseAction &closeAction);
299 TypedResponse::OnCloseAction onCloseAction();
300 void doOnCloseAction(TypedResponse::Status, const IdType &);
301
302private:
303 QAtomicInt m_lastId;
304 QHash<QByteArray, TypedHandler *> m_handlers;
305 TypedResponse::OnCloseAction m_onCloseAction;
306};
307
308template<typename T>
309void TypedResponse::sendSuccessfullResponse(const T &result)
310{
311 if (m_status == Status::Started) {
312 m_status = Status::SentSuccess;
313 m_responseHandler(QJsonRpcProtocol::Response {
314 QTypedJson::toJsonValue(params: m_id),
315 QTypedJson::toJsonValue(result)
316 });
317 doOnCloseActions();
318 } else {
319 qCWarning(QTypedJson::jsonRpcLog)
320 << "Ignoring response in already answered request" << idStr();
321 }
322}
323
324template<typename T>
325void TypedResponse::sendErrorResponse(int code, const QByteArray &message, const T &data)
326{
327 if (m_status == Status::Started) {
328 m_status = Status::SentError;
329 m_responseHandler(QJsonRpcProtocol::Response { QTypedJson::toJsonValue(params: m_id),
330 QTypedJson::toJsonValue(data), code,
331 QString::fromUtf8(ba: message) });
332 doOnCloseActions();
333 } else {
334 qCWarning(QTypedJson::jsonRpcLog)
335 << "Ignoring error response" << code << QString::fromUtf8(ba: message)
336 << "in already answered request" << idStr();
337 }
338}
339
340template<typename... Params>
341void TypedResponse::sendNotification(const QByteArray &method, const Params &...params)
342{
343 m_typedRpc->sendNotification(method, params...);
344}
345} // namespace QTypedJson
346QT_END_NAMESPACE
347
348#endif // QJSONTYPEDRPC_P_H
349

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtlanguageserver/src/jsonrpc/qjsontypedrpc_p.h