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(T result);
87 template<typename T>
88 void sendErrorResponse(int code, const QByteArray &message, T data);
89 void sendErrorResponse(int code, const QByteArray &message);
90 template<typename... Params>
91 void sendNotification(const QByteArray &method, 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 std::function<void(const QJsonRpcProtocol::Request &,
121 const QJsonRpcProtocol::ResponseHandler &)>
122 rHandler,
123 std::function<void(const QJsonRpcProtocol::Notification &)> nHandler)
124 : m_method(method), m_requestHandler(rHandler), m_notificationHandler(nHandler)
125 {
126 }
127
128 TypedHandler(const QByteArray &method,
129 std::function<void(const QJsonRpcProtocol::Request &,
130 const QJsonRpcProtocol::ResponseHandler &)>
131 rHandler)
132 : m_method(method), m_requestHandler(rHandler), m_notificationHandler()
133 {
134 }
135
136 TypedHandler(const QByteArray &method,
137 std::function<void(const QJsonRpcProtocol::Notification &)> nHandler)
138 : m_method(method), m_requestHandler(), m_notificationHandler(nHandler)
139 {
140 }
141
142 ~TypedHandler() = default;
143
144 void handleRequest(const QJsonRpcProtocol::Request &request,
145 const QJsonRpcProtocol::ResponseHandler &handler) override
146 {
147 using namespace Qt::StringLiterals;
148
149 if (m_requestHandler) {
150 m_requestHandler(request, handler);
151 return;
152 }
153 QString msg;
154 if (m_notificationHandler)
155 msg = u"Expected notification with method '%1', not request"_s;
156 else
157 msg = u"Reached null handler for method '%1'"_s;
158 msg = msg.arg(a: request.method);
159 handler(MessageHandler::error(code: int(QJsonRpcProtocol::ErrorCode::InvalidRequest), message: msg));
160 qCWarning(QTypedJson::jsonRpcLog) << msg;
161 }
162
163 QByteArray method() const { return m_method; }
164
165 void handleNotification(const QJsonRpcProtocol::Notification &notification) override
166 {
167 if (m_notificationHandler) {
168 m_notificationHandler(notification);
169 return;
170 }
171 if (m_requestHandler)
172 qCWarning(QTypedJson::jsonRpcLog) << "Expected Request but got notification for "
173 << notification.method << ", ignoring it.";
174 else
175 qCWarning(QTypedJson::jsonRpcLog)
176 << "Reached null handler for method " << notification.method;
177 }
178
179private:
180 QByteArray m_method;
181 std::function<void(const QJsonRpcProtocol::Request &,
182 const QJsonRpcProtocol::ResponseHandler &)>
183 m_requestHandler;
184 std::function<void(const QJsonRpcProtocol::Notification &)> m_notificationHandler;
185};
186
187class Q_JSONRPC_EXPORT TypedRpc : public QJsonRpcProtocol
188{
189 Q_DISABLE_COPY_MOVE(TypedRpc)
190public:
191 TypedRpc() = default;
192
193 template<typename... Params>
194 void sendRequestId(std::variant<int, QByteArray> id, const QByteArray &method,
195 const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &rHandler,
196 Params... params)
197 {
198 QJsonRpcProtocol::sendRequest(request: Request { QTypedJson::toJsonValue(params: id),
199 QString::fromUtf8(ba: method),
200 QTypedJson::toJsonValue(params...) },
201 handler: rHandler);
202 }
203
204 template<typename... Params>
205 void sendRequest(const QByteArray &method,
206 const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &handler,
207 Params... params)
208 {
209 sendRequestId(++m_lastId, method, handler, params...);
210 }
211
212 template<typename... Params>
213 void sendNotification(const QByteArray &method, Params... params)
214 {
215 QJsonRpcProtocol::sendNotification(
216 notification: Notification { QString::fromUtf8(ba: method), QTypedJson::toJsonValue(params...) });
217 }
218
219 template<typename Req, typename Resp>
220 void
221 registerRequestHandler(const QByteArray &method,
222 std::function<void(const QByteArray &, const Req &, Resp &&)> handler)
223 {
224 if (m_handlers.contains(key: method) && handler) {
225 qCWarning(QTypedJson::jsonRpcLog)
226 << "QJsonRpc double registration for method" << QString::fromUtf8(ba: method);
227 Q_ASSERT(false);
228 return;
229 }
230 TypedHandler *h;
231 if (handler)
232 h = new TypedHandler(
233 method,
234 [handler, method, this](const QJsonRpcProtocol::Request &req,
235 const QJsonRpcProtocol::ResponseHandler &rH) {
236 std::variant<int, QByteArray> id = req.id.toInt(defaultValue: 0);
237 if (req.id.isString())
238 id = req.id.toString().toUtf8();
239 TypedResponse typedResponse(id, this, rH);
240 Req tReq;
241 {
242 QTypedJson::Reader r(req.params);
243 QTypedJson::doWalk(r, tReq);
244 if (!r.errorMessages().isEmpty()) {
245 qCWarning(QTypedJson::jsonRpcLog)
246 << "Warnings decoding parameters for Request" << method
247 << idToString(v: id) << "from" << req.params << ":\n "
248 << r.errorMessages().join(sep: u"\n ");
249 r.clearErrorMessages();
250 }
251 }
252 Resp myResponse(std::move(typedResponse));
253 handler(method, tReq, std::move(myResponse));
254 });
255 else
256 h = new TypedHandler;
257 m_handlers[method] = h;
258 setMessageHandler(method: QString::fromUtf8(ba: method), handler: h);
259 }
260
261 template<typename N>
262 void registerNotificationHandler(const QByteArray &method,
263 std::function<void(const QByteArray &, const N &)> handler)
264 {
265 if (m_handlers.contains(key: method) && handler) {
266 qCWarning(QTypedJson::jsonRpcLog)
267 << "QJsonRpc double registration for method" << QString::fromUtf8(ba: method);
268 Q_ASSERT(false);
269 return;
270 }
271 TypedHandler *h;
272 if (handler)
273 h = new TypedHandler(
274 method, [handler, method](const QJsonRpcProtocol::Notification &notif) {
275 N tNotif;
276 {
277 QTypedJson::Reader r(notif.params);
278 QTypedJson::doWalk(r, tNotif);
279 if (!r.errorMessages().isEmpty()) {
280 qCWarning(QTypedJson::jsonRpcLog)
281 << "Warnings decoding parameters for Notification" << method
282 << "from" << notif.params << ":\n "
283 << r.errorMessages().join(sep: u"\n ");
284 r.clearErrorMessages();
285 }
286 }
287 handler(method, tNotif);
288 });
289 else
290 h = new TypedHandler;
291 setMessageHandler(method: QString::fromUtf8(ba: method), handler: h);
292 m_handlers[method] = h;
293 }
294
295 void sendBatch(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(T result)
310{
311 if (m_status == Status::Started) {
312 m_status = Status::SentSuccess;
313 QJsonValue jsonId = QTypedJson::toJsonValue(params: m_id);
314 QJsonValue jsonResult = QTypedJson::toJsonValue(result);
315 m_responseHandler(QJsonRpcProtocol::Response { .id: jsonId, .data: jsonResult });
316 doOnCloseActions();
317 } else {
318 qCWarning(QTypedJson::jsonRpcLog)
319 << "Ignoring response in already answered request" << idStr();
320 }
321}
322
323template<typename T>
324void TypedResponse::sendErrorResponse(int code, const QByteArray &message, T data)
325{
326 if (m_status == Status::Started) {
327 m_status = Status::SentError;
328 m_responseHandler(QJsonRpcProtocol::Response { QTypedJson::toJsonValue(params: m_id),
329 QTypedJson::toJsonValue(data), code,
330 QString::fromUtf8(ba: message) });
331 doOnCloseActions();
332 } else {
333 qCWarning(QTypedJson::jsonRpcLog)
334 << "Ignoring error response" << code << QString::fromUtf8(ba: message)
335 << "in already answered request" << idStr();
336 }
337}
338
339template<typename... Params>
340void TypedResponse::sendNotification(const QByteArray &method, Params... params)
341{
342 m_typedRpc->sendNotification(method, params...);
343}
344} // namespace QTypedJson
345QT_END_NAMESPACE
346
347#endif // QJSONTYPEDRPC_P_H
348

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