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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
#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-json-diagnostics.h"
#include "slang-json-rpc.h"
#include "slang-json-value.h"
#include "slang-source-loc.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();
SlangResult checkArrayObjectWrap(
const JSONValue& srcArgs,
const RttiInfo* dstArgsRttiInfo,
void* dstArgs,
const JSONValue& id);
/// 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());
}
HTTPPacketConnection* getUnderlyingConnection() { return m_connection.Ptr(); }
/// Dtor
~JSONRPCConnection() { disconnect(); }
/// Ctor
JSONRPCConnection();
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
RttiTypeFuncsMap m_typeMap;
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
|