summaryrefslogtreecommitdiff
path: root/source/compiler-core/slang-json-rpc-connection.h
blob: 1436e9601830f8195f715d0f36c7841a159bf33c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#ifndef SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H
#define SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H

#include "../../source/core/slang-http.h"
#include "../../source/core/slang-process.h"

#include "slang-diagnostic-sink.h"
#include "slang-source-loc.h"
#include "slang-json-value.h"
#include "slang-json-rpc.h"

#include "slang-json-diagnostics.h"

#include "slang-test-server-protocol.h"

namespace Slang {

/* A type to handle communication via the JSON-RPC protocol.

Uses Rtti to be able to convert between native and JSON types.
Provides methods that work on the JSON-RPC protocol, these methods contain 'RPC' and can only
use the JSON-RPC protocol types. These types will hold items that can vary (like parameters)
in JSONValue parameters. Code can use regular JSON functions to access/process.

Doing conversions to native types and JSON manually can be a fairly monotonous task. To avoid this
effort Rtti and JSON<->Rtti conversions can be used. For example sendCall will send a JSON-RPC 'call' method,
with the parameters being converted from some native type. For this to work the type T must be determinable
via GetRttiType<T>, and T must only contain types that JSON<->Rtti conversion supports. 
*/
class JSONRPCConnection : public RefObject
{
public:

    enum class CallStyle
    {
        Default,        ///< The default
        Object,         ///< Params are passed as an object
        Array,          ///< Params are passed as an array
    };

        /// An init function must be called before use
        /// If a process is implementing the server it should be passed in if the process needs to shut down if the connection does
    SlangResult init(HTTPPacketConnection* connection, CallStyle callStyle = CallStyle::Default, Process* process = nullptr);

        /// Initialize using stdin/out streams for input/output. 
    SlangResult initWithStdStreams(CallStyle callStyle = CallStyle::Default, Process* process = nullptr);

        /// Disconnect. May block while server shuts down
    void disconnect();

        /// Convert value to dst. Will write response on fails
    SlangResult toNativeOrSendError(const JSONValue& value, const RttiInfo* info, void* dst, const JSONValue& id);
    template <typename T>
    SlangResult toNativeOrSendError(const JSONValue& value, T* data, const JSONValue& id) { return toNativeOrSendError(value, GetRttiInfo<T>::get(), data, id); }

        /// Convert value to dst.
        /// The 'Args' aspect here is to handle Args/Params in JSON-RPC which can be specified as an array or object style.
        /// This call will automatically handle either case.
        /// toNativeOrSendError does not assume the thing being converted is args, and so doesn't allow such a transformation.
        /// Will write error response on failure.
    SlangResult toNativeArgsOrSendError(const JSONValue& srcArgs, const RttiInfo* dstArgsRttiInfo, void* dstArgs, const JSONValue& id);
    template <typename T>
    SlangResult toNativeArgsOrSendError(const JSONValue& srcArgs, T* dstArgs, const JSONValue& id) { return toNativeArgsOrSendError(srcArgs, GetRttiInfo<T>::get(), dstArgs, id); }

    template <typename T>
    SlangResult toValidNativeOrSendError(const JSONValue& value, T* data, const JSONValue& id);

        /// Send a RPC response (ie should only be one of the JSONRPC classes)
    SlangResult sendRPC(const RttiInfo* info, const void* data);
    template <typename T>
    SlangResult sendRPC(const T* data) { return sendRPC(GetRttiInfo<T>::get(), (const void*)data);  }

        /// Send an error
    SlangResult sendError(JSONRPC::ErrorCode code, const JSONValue& id);
    SlangResult sendError(JSONRPC::ErrorCode errorCode, const UnownedStringSlice& msg, const JSONValue& id);

        /// Send a 'call'
        /// Uses the default CallStyle as set when init
    SlangResult sendCall(const UnownedStringSlice& method, const RttiInfo* argsRttiInfo, const void* args, const JSONValue& id = JSONValue());
    template <typename T>
    SlangResult sendCall(const UnownedStringSlice& method, const T* args, const JSONValue& id = JSONValue()) { return sendCall(method, GetRttiInfo<T>::get(), (const void*)args, id); }

        /// Send a 'call'
        /// Uses the call mechanism specified in callStyle. It is valid to pass as Default. 
    SlangResult sendCall(CallStyle callStyle, const UnownedStringSlice& method, const RttiInfo* argsRttiInfo, const void* args, const JSONValue& id = JSONValue());
    template <typename T>
    SlangResult sendCall(CallStyle callStyle, const UnownedStringSlice& method, const T* args, const JSONValue& id = JSONValue()) { return sendCall(callStyle, method, GetRttiInfo<T>::get(), (const void*)args, id); }

        /// Send a call, wheret there are no arguments
    SlangResult sendCall(const UnownedStringSlice& method, const JSONValue& id = JSONValue());

    template <typename T>
    SlangResult sendResult(const T* result, const JSONValue& id) { return sendResult(GetRttiInfo<T>::get(), (const void*)result, id); }
    SlangResult sendResult(const RttiInfo* rttiInfo, const void* result, const JSONValue& id);

        /// Try to read a message. Will return if message is not available.
    SlangResult tryReadMessage();

        /// Will block for message/result up to time
    SlangResult waitForResult(Int timeOutInMs = -1);

        /// If we have an JSON-RPC message m_jsonRoot the root.
    bool hasMessage() const { return m_jsonRoot.isValid(); }

        /// If there is a message returns kind of JSON RPC message
    JSONRPCMessageType getMessageType();

        /// Get JSON-RPC message (ie one of JSONRPC classes)
    template <typename T>
    SlangResult getRPC(T* out) { return getRPC(GetRttiInfo<T>::get(), (void*)out); }
    SlangResult getRPC(const RttiInfo* rttiInfo, void* out);

        /// Get JSON-RPC message (ie one of JSONRPC prefixed classes)
        /// If there is a message and there is a failure, will send an error response
    template <typename T>
    SlangResult getRPCOrSendError(T* out) { return getRPCOrSendError(GetRttiInfo<T>::get(), (void*)out); }
    SlangResult getRPCOrSendError(const RttiInfo* rttiInfo, void* out);

        /// Get message (has to be part of JSONRPCResultResponse)
    template <typename T>
    SlangResult getMessage(T* out) { return getMessage(GetRttiInfo<T>::get(), (void*)out); }
    SlangResult getMessage(const RttiInfo* rttiInfo, void* out);

        /// If there is a message and there is a failure, will send an error response
    template <typename T>
    SlangResult getMessageOrSendError(T* out) { return getMessageOrSendError(GetRttiInfo<T>::get(), (void*)out); }
    SlangResult getMessageOrSendError(const RttiInfo* rttiInfo, void* out);

        /// Clears all the internal buffers (for JSON/Source/etc).
        /// Happens automatically on tryReadMessage/readMessage
    void clearBuffers();

        /// True if this connection is active
    bool isActive();

        /// Get the id of the current message
    JSONValue getCurrentMessageId();

        /// Get the diagnostic sink. Can queue up errors before sending an error
    DiagnosticSink* getSink() { return &m_diagnosticSink;  }

        /// Get the container
    JSONContainer* getContainer() { return &m_container;  }

        /// Turn a value into a persistant value. This will also remove any sourceLoc under the assumption that it's highly likely
        /// it will become invalid in most usage scenarios. 
    PersistentJSONValue getPersistentValue(const JSONValue& value) { return PersistentJSONValue(value, &m_container, SourceLoc()); }

        /// Dtor
    ~JSONRPCConnection() { disconnect(); }

        /// Ctor
    JSONRPCConnection():m_container(nullptr) {}

protected:
    CallStyle _getCallStyle(CallStyle callStyle) const { return (callStyle == CallStyle::Default) ? m_defaultCallStyle : callStyle; }

    RefPtr<Process> m_process;                       ///< Backing process (optional)
    RefPtr<HTTPPacketConnection> m_connection;       ///< The underlying 'transport' connection, whilst HTTP currently doesn't have to be 

    DiagnosticSink m_diagnosticSink;                ///< Holds any diagnostics typically generated by parsing JSON, producing JSON from native types

    SourceManager m_sourceManager;                  ///< Holds the JSON text for current message/output. Is cleared regularly.
    JSONContainer m_container;                      ///< Holds the backing memory for jsonMemory, and used when converting input into output JSON

    JSONValue m_jsonRoot;                           ///< The root JSON value for the currently read message. 

    CallStyle m_defaultCallStyle = CallStyle::Array;    ///< The default calling style
    
    Int m_terminationTimeOutInMs = 1 * 1000;        ///< Time to wait for termination response. Default is 1 second
};

// ---------------------------------------------------------------------------
template <typename T>
SlangResult JSONRPCConnection::toValidNativeOrSendError(const JSONValue& value, T* data, const JSONValue& id)
{
    const RttiInfo* rttiInfo = GetRttiInfo<T>::get();

    SLANG_RETURN_ON_FAIL(toNativeOrSendError(value, rttiInfo, (void*)data, id));
    if (!data->isValid())
    {
        // If it has a name add validation info
        if (rttiInfo->isNamed())
        {
            const NamedRttiInfo* namedRttiInfo = static_cast<const NamedRttiInfo*>(rttiInfo);
            m_diagnosticSink.diagnose(SourceLoc(), JSONDiagnostics::argsAreInvalid, namedRttiInfo->m_name);
        }

        return sendError(JSONRPC::ErrorCode::InvalidRequest, id);
    }
    return SLANG_OK;
}

} // namespace Slang

#endif // SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H