// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once #include "basemessage.h" #include "jsonkeys.h" #include "lsptypes.h" #include #include #include #include #include #include #include #include #include #include namespace LanguageServerProtocol { class JsonRpcMessage; class LANGUAGESERVERPROTOCOL_EXPORT MessageId : public std::variant { public: MessageId() : variant(QString()) {} explicit MessageId(int id) : variant(id) {} explicit MessageId(const QString &id) : variant(id) {} explicit MessageId(const QJsonValue &value) { if (value.isDouble()) emplace(value.toInt()); else emplace(value.toString()); } operator QJsonValue() const { if (auto id = std::get_if(this)) return *id; if (auto id = std::get_if(this)) return *id; return QJsonValue(); } bool isValid() const { if (std::holds_alternative(*this)) return true; const QString *id = std::get_if(this); QTC_ASSERT(id, return false); return !id->isEmpty(); } QString toString() const { if (auto id = std::get_if(this)) return *id; if (auto id = std::get_if(this)) return QString::number(*id); return {}; } private: friend size_t qHash(const MessageId &id) { if (const int *iid = std::get_if(&id)) return QT_PREPEND_NAMESPACE(qHash(*iid)); if (const QString *sid = std::get_if(&id)) return QT_PREPEND_NAMESPACE(qHash(*sid)); return QT_PREPEND_NAMESPACE(qHash(0)); } friend QDebug operator<<(QDebug stream, const MessageId &id) { if (const int *iid = std::get_if(&id)) stream << *iid; else stream << *std::get_if(&id); return stream; } }; struct ResponseHandler { MessageId id; using Callback = std::function; Callback callback; }; class LANGUAGESERVERPROTOCOL_EXPORT JsonRpcMessage { public: JsonRpcMessage(); explicit JsonRpcMessage(const BaseMessage &message); explicit JsonRpcMessage(const QJsonObject &jsonObject); explicit JsonRpcMessage(QJsonObject &&jsonObject); virtual ~JsonRpcMessage() = default; static QByteArray jsonRpcMimeType(); QByteArray toRawData() const; virtual bool isValid(QString *errorMessage) const; const QJsonObject &toJsonObject() const; const QString parseError() { return m_parseError; } virtual std::optional responseHandler() const { return std::nullopt; } BaseMessage toBaseMessage() const { return BaseMessage(jsonRpcMimeType(), toRawData()); } protected: QJsonObject m_jsonObject; private: QString m_parseError; }; template class Notification : public JsonRpcMessage { public: Notification(const QString &methodName, const Params ¶ms) { setMethod(methodName); setParams(params); } explicit Notification(const QJsonObject &jsonObject) : JsonRpcMessage(jsonObject) {} explicit Notification(QJsonObject &&jsonObject) : JsonRpcMessage(std::move(jsonObject)) {} QString method() const { return fromJsonValue(m_jsonObject.value(methodKey)); } void setMethod(const QString &method) { m_jsonObject.insert(methodKey, method); } using Parameters = Params; std::optional params() const { const QJsonValue ¶ms = m_jsonObject.value(paramsKey); return params.isUndefined() ? std::nullopt : std::make_optional(Params(params)); } void setParams(const Params ¶ms) { m_jsonObject.insert(paramsKey, QJsonValue(params)); } void clearParams() { m_jsonObject.remove(paramsKey); } bool isValid(QString *errorMessage) const override { return JsonRpcMessage::isValid(errorMessage) && m_jsonObject.value(methodKey).isString() && parametersAreValid(errorMessage); } virtual bool parametersAreValid(QString *errorMessage) const { if (auto parameter = params()) return parameter->isValid(); if (errorMessage) *errorMessage = QCoreApplication::translate("QtC::LanguageServerProtocol", "No parameters in \"%1\".").arg(method()); return false; } }; template <> class Notification : public JsonRpcMessage { public: Notification() : Notification(QString()) {} explicit Notification(const QString &methodName, const std::nullptr_t &/*params*/ = nullptr) { setMethod(methodName); setParams(nullptr); } using JsonRpcMessage::JsonRpcMessage; QString method() const { return fromJsonValue(m_jsonObject.value(methodKey)); } void setMethod(const QString &method) { m_jsonObject.insert(methodKey, method); } std::optional params() const { return nullptr; } void setParams(const std::nullptr_t &/*params*/) { m_jsonObject.insert(paramsKey, QJsonValue::Null); } void clearParams() { m_jsonObject.remove(paramsKey); } bool isValid(QString *errorMessage) const override { return JsonRpcMessage::isValid(errorMessage) && m_jsonObject.value(methodKey).isString() && parametersAreValid(errorMessage); } virtual bool parametersAreValid(QString * /*errorMessage*/) const { return true; } }; template class ResponseError : public JsonObject { public: using JsonObject::JsonObject; int code() const { return typedValue(codeKey); } void setCode(int code) { insert(codeKey, code); } QString message() const { return typedValue(messageKey); } void setMessage(const QString &message) { insert(messageKey, message); } std::optional data() const { return optionalValue(dataKey); } void setData(const Error &data) { insert(dataKey, data); } void clearData() { remove(dataKey); } bool isValid() const override { return contains(codeKey) && contains(messageKey); } QString toString() const { return errorCodesToString(code()) + ": " + message(); } // predefined error codes enum ErrorCodes { // Defined by JSON RPC ParseError = -32700, InvalidRequest = -32600, MethodNotFound = -32601, InvalidParams = -32602, InternalError = -32603, serverErrorStart = -32099, serverErrorEnd = -32000, ServerNotInitialized = -32002, UnknownErrorCode = -32001, // Defined by the protocol. RequestCancelled = -32800 }; #define CASE_ERRORCODES(x) case ErrorCodes::x: return QLatin1String(#x) static QString errorCodesToString(int code) { switch (code) { CASE_ERRORCODES(ParseError); CASE_ERRORCODES(InvalidRequest); CASE_ERRORCODES(MethodNotFound); CASE_ERRORCODES(InvalidParams); CASE_ERRORCODES(InternalError); CASE_ERRORCODES(serverErrorStart); CASE_ERRORCODES(serverErrorEnd); CASE_ERRORCODES(ServerNotInitialized); CASE_ERRORCODES(RequestCancelled); default: return QCoreApplication::translate("QtC::LanguageClient", "Error %1").arg(code); } } #undef CASE_ERRORCODES }; template class Response : public JsonRpcMessage { public: explicit Response(const MessageId &id) { setId(id); } using JsonRpcMessage::JsonRpcMessage; MessageId id() const { return MessageId(m_jsonObject.value(idKey)); } void setId(MessageId id) { this->m_jsonObject.insert(idKey, id); } std::optional result() const { const QJsonValue &result = m_jsonObject.value(resultKey); if (result.isUndefined()) return std::nullopt; return std::make_optional(Result(result)); } void setResult(const Result &result) { m_jsonObject.insert(resultKey, QJsonValue(result)); } void clearResult() { m_jsonObject.remove(resultKey); } using Error = ResponseError; std::optional error() const { const QJsonValue &val = m_jsonObject.value(errorKey); return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue(val)); } void setError(const Error &error) { QTC_CHECK(error.isValid()); m_jsonObject.insert(errorKey, QJsonValue(error)); } void clearError() { m_jsonObject.remove(errorKey); } bool isValid(QString *errorMessage) const override { return JsonRpcMessage::isValid(errorMessage) && id().isValid(); } }; template class Response : public JsonRpcMessage { public: explicit Response(const MessageId &id) { setId(id); } using JsonRpcMessage::JsonRpcMessage; MessageId id() const { return MessageId(m_jsonObject.value(idKey)); } void setId(MessageId id) { this->m_jsonObject.insert(idKey, id); } std::optional result() const { return m_jsonObject.value(resultKey).isNull() ? std::make_optional(nullptr) : std::nullopt; } void setResult(const std::nullptr_t &) { m_jsonObject.insert(resultKey, QJsonValue::Null); } void clearResult() { m_jsonObject.remove(resultKey); } using Error = ResponseError; std::optional error() const { const QJsonValue &val = m_jsonObject.value(errorKey); return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue(val)); } void setError(const Error &error) { m_jsonObject.insert(errorKey, QJsonValue(error)); } void clearError() { m_jsonObject.remove(errorKey); } bool isValid(QString *errorMessage) const override { return JsonRpcMessage::isValid(errorMessage) && id().isValid(); } }; void LANGUAGESERVERPROTOCOL_EXPORT logElapsedTime(const QString &method, const QElapsedTimer &t); template class Request : public Notification { public: Request(const QString &methodName, const Params ¶ms) : Notification(methodName, params) { setId(MessageId(QUuid::createUuid().toString())); } explicit Request(const QJsonObject &jsonObject) : Notification(jsonObject) { } explicit Request(QJsonObject &&jsonObject) : Notification(std::move(jsonObject)) { } MessageId id() const { return MessageId(JsonRpcMessage::m_jsonObject.value(idKey)); } void setId(const MessageId &id) { JsonRpcMessage::m_jsonObject.insert(idKey, id); } using Response = LanguageServerProtocol::Response; using ResponseCallback = std::function; void setResponseCallback(const ResponseCallback &callback) { m_callBack = callback; } std::optional responseHandler() const final { QElapsedTimer timer; timer.start(); auto callback = [callback = m_callBack, method = this->method(), t = std::move(timer)] (const JsonRpcMessage &message) { if (!callback) return; logElapsedTime(method, t); callback(Response(message.toJsonObject())); }; return std::make_optional(ResponseHandler{id(), callback}); } bool isValid(QString *errorMessage) const override { if (!Notification::isValid(errorMessage)) return false; if (id().isValid()) return true; if (errorMessage) *errorMessage = QCoreApplication::translate("QtC::LanguageServerProtocol", "No ID set in \"%1\".").arg(this->method()); return false; } private: ResponseCallback m_callBack; }; class LANGUAGESERVERPROTOCOL_EXPORT CancelParameter : public JsonObject { public: explicit CancelParameter(const MessageId &id) { setId(id); } CancelParameter() = default; using JsonObject::JsonObject; MessageId id() const { return typedValue(idKey); } void setId(const MessageId &id) { insert(idKey, id); } bool isValid() const override { return contains(idKey); } }; class LANGUAGESERVERPROTOCOL_EXPORT CancelRequest : public Notification { public: explicit CancelRequest(const CancelParameter ¶ms); using Notification::Notification; constexpr static const char methodName[] = "$/cancelRequest"; }; } // namespace LanguageServerProtocol template inline QDebug operator<<(QDebug stream, const LanguageServerProtocol::ResponseError &error) { stream.nospace() << error.toString(); return stream; } Q_DECLARE_METATYPE(LanguageServerProtocol::JsonRpcMessage)