summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2021-11-23 16:23:15 -0500
committerGitHub <noreply@github.com>2021-11-23 16:23:15 -0500
commit9e084ffab37c276d40931a58633041a2e10de623 (patch)
treed85aa490968cdd3fe4bbcf305b593c6b86587685 /source
parentfd46034bf2de59b8ad51743e62b26359678432f7 (diff)
JSON-RPC test server (#2026)
* #include an absolute path didn't work - because paths were taken to always be relative. * Use 'Process' to communicate with an command line tool. * Remove slang-win-stream * Tidy up windows ProcessUtil. * First version of BufferedReadStream. * Windows working IPC for steams. * Test proxy count option. * Split Process/ProcessUtil. Process is platform dependant. ProcessUtil are functions that are platform independent. * First implementation of Unix Process interface. * Unix process compiles on cygwin. * Fix typo in unix process. * Separate unix pipe stream error of invalid access, from pipe availability. * Fix in standard line extraction. * Make fd non blocking. * Fix issues with Windows Process streams. * Added UnixPipe. * Some fixes around UnixPipeStream. * Make a unix stream closed explicit. * Hack to debug linux process/stream. * Revert to old linux pipe handling. * Pass executable path for unit tests. Split out CommandLine into own source. * Small improvements in process/command line. * Check process behavior with crash. * Make stderr and stdout unbuffered for crash testing. * Only turn disable buffering in crash test. * Disable crash test on CI. * Fix crash on clang/linux. * Enable crash test. Remove _appendBuffer as can use StreamUtil functionality. * Added inital processing for http headers. * Small improvements to HttpHeader. * First pass HTTPPacketConnection working on windows. * Enable other Process communication tests. * Update comments. * WIP JSON RPC. * Add terminate to Process. Made JSONRPC a Util. * Small tidy up around HTTPPacketConnection. * Improve process termination options. * WIP for test-server. * Add diagnostics error handling to test-server. * Improved JSON support. Parsing/creating JSON-RPC messages. * WIP JSONRPC parsing. * First pass RttiInfo support. * WIP converting between JSON/native types. * Project files. * Split out RttiUtil. Made RttiInfo constuction thread safe. * WIP RTTI<->JSON. * Add diagnostics to JSON<->native conversions. * Make RttiInfo for structs globals. Avoids problem around derived types (like pointers), being able to cause an abort. * Add pointer support to RTTI. Fixed some compilation issues on linux. * Add fixed array support. * Added Rtti unit test. * Add rtti unit test. * Split out quoted/unquoted key handling. Fix bugs in JSON value/container. Added JSON native test. * Make default array allocator use malloc/free. Remove the new[] handler (doesn't work on visuals studio). * Fix for linux warning. * Remove some test code. * Fix issues on x86 win. * Fix warning on aarch64. * Fix some bugs in JSON parsing/handling. Make Rtti work copy/dtor/ctor struct types. * Testing JSON<->native with fixed array. Make makeArrayView explicit if it's just a single value. Added array type. * Fix getting arrayView. * Improve JSON diagnostic name. * First pass refactor using Rtti for JSON RPC. * First pass of test server using RTTI/JSON-RPC. * Added JSONRPCConnection. * Fix some naming issues. * First pass of test-server working. * Added unit test support for JSON-RPC test server. * Fix compilation issues on linux around template handling. * Typo fix. * Fix a bug around SourceLoc lookup with JSONContainer. * Set the console type to console for ISlangWriters. * Small improvements to test-server. * Small improvements in test-server. * Small fix.
Diffstat (limited to 'source')
-rw-r--r--source/compiler-core/slang-json-diagnostic-defs.h7
-rw-r--r--source/compiler-core/slang-json-native.h5
-rw-r--r--source/compiler-core/slang-json-rpc-connection.cpp286
-rw-r--r--source/compiler-core/slang-json-rpc-connection.h161
-rw-r--r--source/compiler-core/slang-json-rpc.cpp404
-rw-r--r--source/compiler-core/slang-json-rpc.h151
-rw-r--r--source/compiler-core/slang-json-value.cpp2
-rw-r--r--source/compiler-core/slang-json-value.h3
-rw-r--r--source/compiler-core/slang-test-server-protocol.cpp43
-rw-r--r--source/compiler-core/slang-test-server-protocol.h51
-rw-r--r--source/core/slang-http.cpp24
-rw-r--r--source/core/slang-http.h5
-rw-r--r--source/core/slang-writer.h1
13 files changed, 727 insertions, 416 deletions
diff --git a/source/compiler-core/slang-json-diagnostic-defs.h b/source/compiler-core/slang-json-diagnostic-defs.h
index 4733a3291..576f14beb 100644
--- a/source/compiler-core/slang-json-diagnostic-defs.h
+++ b/source/compiler-core/slang-json-diagnostic-defs.h
@@ -42,4 +42,11 @@ DIAGNOSTIC(20011, Error, fieldNotDefinedOnType, "field '$0' not defined on type
DIAGNOSTIC(20011, Error, fieldRequiredOnType, "field '$0' required on '$1'")
DIAGNOSTIC(20012, Error, tooManyElementsForArray, "too many elements ($0) for array array. Max allowed is $1")
+//
+// 3xxxx JSON-RPC
+//
+
+DIAGNOSTIC(30000, Error, argsAreInvalid, "Args for '%0' are invalid")
+
+
#undef DIAGNOSTIC
diff --git a/source/compiler-core/slang-json-native.h b/source/compiler-core/slang-json-native.h
index 66f20aafb..f9ec93119 100644
--- a/source/compiler-core/slang-json-native.h
+++ b/source/compiler-core/slang-json-native.h
@@ -12,6 +12,8 @@ namespace Slang {
struct JSONToNativeConverter
{
SlangResult convert(const JSONValue& value, const RttiInfo* rttiInfo, void* out);
+ template <typename T>
+ SlangResult convert(const JSONValue& value, T* in) { return convert(value, GetRttiInfo<T>::get(), (void*)in); }
JSONToNativeConverter(JSONContainer* container, DiagnosticSink* sink):
m_container(container),
@@ -32,6 +34,9 @@ struct NativeToJSONConverter
{
SlangResult convert(const RttiInfo* rttiInfo, const void* in, JSONValue& out);
+ template <typename T>
+ SlangResult convert(T* in, JSONValue& out) { return convert(GetRttiInfo<T>::get(), (const void*)in, out); }
+
NativeToJSONConverter(JSONContainer* container, DiagnosticSink* sink) :
m_container(container),
m_sink(sink)
diff --git a/source/compiler-core/slang-json-rpc-connection.cpp b/source/compiler-core/slang-json-rpc-connection.cpp
new file mode 100644
index 000000000..20283070b
--- /dev/null
+++ b/source/compiler-core/slang-json-rpc-connection.cpp
@@ -0,0 +1,286 @@
+// slang-json-rpc-connection.cpp
+#include "slang-json-rpc-connection.h"
+
+#include "../core/slang-string-util.h"
+#include "../core/slang-process-util.h"
+
+#include "slang-json-rpc.h"
+#include "slang-json-native.h"
+
+namespace Slang {
+
+SlangResult JSONRPCConnection::init(HTTPPacketConnection* connection, Process* process)
+{
+ m_connection = connection;
+ m_process = process;
+
+ m_sourceManager.initialize(nullptr, nullptr);
+ m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation);
+ m_container.setSourceManager(&m_sourceManager);
+
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::initWithStdStreams(Process* process)
+{
+ RefPtr<Stream> stdinStream, stdoutStream;
+
+ Process::getStdStream(Process::StreamType::StdIn, stdinStream);
+ Process::getStdStream(Process::StreamType::StdOut, stdoutStream);
+
+ RefPtr<BufferedReadStream> readStream(new BufferedReadStream(stdinStream));
+
+ RefPtr<HTTPPacketConnection> connection = new HTTPPacketConnection(readStream, stdoutStream);
+ return init(connection, process);
+}
+
+void JSONRPCConnection::clearBuffers()
+{
+ m_sourceManager.reset();
+ m_diagnosticSink.reset();
+ m_container.reset();
+ m_jsonRoot.reset();
+}
+
+bool JSONRPCConnection::isActive()
+{
+ return m_connection->isActive() && (m_process == nullptr || !m_process->isTerminated());
+}
+
+JSONValue JSONRPCConnection::getMessageId()
+{
+ SLANG_ASSERT(hasMessage());
+ return JSONRPCUtil::getId(&m_container, m_jsonRoot);
+}
+
+void JSONRPCConnection::disconnect()
+{
+ if (m_process)
+ {
+ if (!m_process->isTerminated())
+ {
+ if (m_connection)
+ {
+ // Send. If succeeded, wait
+ if (SLANG_SUCCEEDED(sendCall(UnownedStringSlice::fromLiteral("quit"))))
+ {
+ // Wait for termination
+ m_process->waitForTermination(m_terminationTimeOutInMs);
+ }
+ }
+
+ if (!m_process->isTerminated())
+ {
+ // Okay, just try terminating
+ m_process->waitForTermination(m_terminationTimeOutInMs);
+ }
+
+ // Okay just kill it then
+ if (!m_process->isTerminated())
+ {
+ m_process->kill(-1);
+ }
+ }
+ m_process.setNull();
+ }
+
+ m_connection.setNull();
+}
+
+SlangResult JSONRPCConnection::sendRPC(const RttiInfo* rttiInfo, const void* data)
+{
+ // Convert to JSON
+ NativeToJSONConverter converter(&m_container, &m_diagnosticSink);
+ JSONValue value;
+
+ SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, data, value));
+
+ // Convert to text
+ JSONWriter writer(JSONWriter::IndentationStyle::Allman);
+
+ m_container.traverseRecursively(value, &writer);
+ const StringBuilder& builder = writer.getBuilder();
+ return m_connection->write(builder.getBuffer(), builder.getLength());
+}
+
+SlangResult JSONRPCConnection::sendError(JSONRPC::ErrorCode code)
+{
+ return sendError(code, m_diagnosticSink.outputBuffer.getUnownedSlice());
+}
+
+SlangResult JSONRPCConnection::sendError(JSONRPC::ErrorCode errorCode, const UnownedStringSlice& msg)
+{
+ JSONRPCErrorResponse errorResponse;
+ errorResponse.error.code = Int(errorCode);
+ errorResponse.error.message = msg;
+
+ // TODO(JS):
+ // This is only appropriate if the sendError is for the current input message.
+ // We might want to add function that the client uses, which take the id as a parameter.
+
+ if (m_jsonRoot.isValid())
+ {
+ errorResponse.id = JSONRPCUtil::getId(&m_container, m_jsonRoot);
+ }
+ else
+ {
+ // If we don't have valid json, we set the id to be null per the spec
+ errorResponse.id = JSONValue::makeNull();
+ }
+
+ return sendRPC(&errorResponse);
+}
+
+SlangResult JSONRPCConnection::toNativeOrSendError(const JSONValue& value, const RttiInfo* info, void* dst)
+{
+ m_diagnosticSink.outputBuffer.Clear();
+ if (SLANG_FAILED(JSONRPCUtil::convertToNative(&m_container, value, &m_diagnosticSink, info, dst)))
+ {
+ return sendError(JSONRPC::ErrorCode::InvalidRequest);
+ }
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::sendCall(const UnownedStringSlice& method, const JSONValue& id)
+{
+ JSONRPCCall call;
+ call.id = id;
+ call.method = method;
+
+ SLANG_RETURN_ON_FAIL(sendRPC(&call));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::sendResult(const RttiInfo* rttiInfo, const void* result, const JSONValue& id)
+{
+ JSONResultResponse response;
+ response.id = id;
+
+ NativeToJSONConverter converter(&m_container, &m_diagnosticSink);
+ SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, result, response.result));
+
+ // Send the RPC
+ SLANG_RETURN_ON_FAIL(sendRPC(&response));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::sendCall(const UnownedStringSlice& method, const RttiInfo* argsRttiInfo, const void* args, const JSONValue& id)
+{
+ JSONRPCCall call;
+ call.id = id;
+ call.method = method;
+
+ // Convert the args/params
+ NativeToJSONConverter converter(&m_container, &m_diagnosticSink);
+ SLANG_RETURN_ON_FAIL(converter.convert(argsRttiInfo, args, call.params));
+
+ // Send the RPC
+ SLANG_RETURN_ON_FAIL(sendRPC(&call));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::waitForResult(Int timeOutInMs)
+{
+ SLANG_RETURN_ON_FAIL(m_connection->waitForResult(timeOutInMs));
+ return tryReadMessage();
+}
+
+SlangResult JSONRPCConnection::tryReadMessage()
+{
+ m_jsonRoot.reset();
+
+ SLANG_RETURN_ON_FAIL(m_connection->update());
+ if (!m_connection->hasContent())
+ {
+ return SLANG_OK;
+ }
+
+ auto content = m_connection->getContent();
+ UnownedStringSlice slice((const char*)content.begin(), content.getCount());
+
+ clearBuffers();
+
+ {
+ const SlangResult res = JSONRPCUtil::parseJSON(slice, &m_container, &m_diagnosticSink, m_jsonRoot);
+
+ // Consume that content/packet
+ m_connection->consumeContent();
+ if (SLANG_FAILED(res))
+ {
+ return sendError(JSONRPC::ErrorCode::ParseError);
+ }
+ }
+
+ return SLANG_OK;
+}
+
+JSONRPCMessageType JSONRPCConnection::getMessageType()
+{
+ return JSONRPCUtil::getMessageType(&m_container, m_jsonRoot);
+}
+
+SlangResult JSONRPCConnection::getMessage(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ m_diagnosticSink.outputBuffer.Clear();
+ JSONToNativeConverter converter(&m_container, &m_diagnosticSink);
+
+ // Get the RPC response
+ JSONResultResponse resultResponse;
+ SLANG_RETURN_ON_FAIL(converter.convert(m_jsonRoot, &resultResponse));
+
+ // Convert the result in the response
+ SLANG_RETURN_ON_FAIL(converter.convert(resultResponse.result, rttiInfo, out));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::getMessageOrSendError(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ const auto res = getMessage(rttiInfo, out);
+ if (SLANG_FAILED(res))
+ {
+ return sendError(JSONRPC::ErrorCode::ParseError);
+ }
+ return res;
+}
+
+SlangResult JSONRPCConnection::getRPC(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ m_diagnosticSink.outputBuffer.Clear();
+ JSONToNativeConverter converter(&m_container, &m_diagnosticSink);
+
+ // Convert the result in the response
+ SLANG_RETURN_ON_FAIL(converter.convert(m_jsonRoot, rttiInfo, out));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::getRPCOrSendError(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ const auto res = getRPC(rttiInfo, out);
+ if (SLANG_FAILED(res))
+ {
+ return sendError(JSONRPC::ErrorCode::ParseError);
+ }
+ return res;
+}
+
+} // namespcae Slang
diff --git a/source/compiler-core/slang-json-rpc-connection.h b/source/compiler-core/slang-json-rpc-connection.h
new file mode 100644
index 000000000..730635493
--- /dev/null
+++ b/source/compiler-core/slang-json-rpc-connection.h
@@ -0,0 +1,161 @@
+#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 {
+
+/* TODO(JS):
+
+It's not really clear here, how to handle id handling. If a server can receive multiple messages, and then later send responses we need
+a way to queue up ids. This is complicated by JSONValue is only valid whilst the backing JSONContainer holds it.
+
+We probably want to create some kind of Variant that can hold all of these values that can hold state independently.
+*/
+class JSONRPCConnection : public RefObject
+{
+public:
+
+ /// 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, Process* process = nullptr);
+
+ /// Initialize using stdin/out streams for input/output.
+ SlangResult initWithStdStreams(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);
+ template <typename T>
+ SlangResult toNativeOrSendError(const JSONValue& value, T* data) { return toNativeOrSendError(value, GetRttiInfo<T>::get(), data); }
+
+ template <typename T>
+ SlangResult toValidNativeOrSendError(const JSONValue& value, T* data);
+
+ /// 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);
+ SlangResult sendError(JSONRPC::ErrorCode errorCode, const UnownedStringSlice& msg);
+
+ /// Send a call
+ /// If no id is needed, id can just be invalid
+ 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); }
+ ///
+ SlangResult sendCall(const UnownedStringSlice& method, const JSONValue& id = JSONValue());
+
+ template <typename T>
+ SlangResult sendResult(const T* result, const JSONValue& id = JSONValue()) { return sendResult(GetRttiInfo<T>::get(), (const void*)result, id); }
+ SlangResult sendResult(const RttiInfo* rttiInfo, const void* result, const JSONValue& id = JSONValue());
+
+ /// 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 getMessageId();
+
+ /// Get the diagnostic sink. Can queue up errors before sending an error
+ DiagnosticSink* getSink() { return &m_diagnosticSink; }
+
+ /// Dtor
+ ~JSONRPCConnection() { disconnect(); }
+
+ /// Ctor
+ JSONRPCConnection():m_container(nullptr) {}
+
+protected:
+ RefPtr<Slang::Process> m_process; ///< Backing process (optional)
+ RefPtr<Slang::HTTPPacketConnection> m_connection;
+
+ DiagnosticSink m_diagnosticSink;
+
+ SourceManager m_sourceManager;
+ JSONContainer m_container;
+
+ JSONValue m_jsonRoot;
+
+ /// Default timeout is 10 seconds
+ Int m_timeOutInMs = 10 * 1000;
+ /// Termination timeout
+ Int m_terminationTimeOutInMs = 1 * 1000;
+};
+
+// ---------------------------------------------------------------------------
+template <typename T>
+SlangResult JSONRPCConnection::toValidNativeOrSendError(const JSONValue& value, T* data)
+{
+ const RttiInfo* rttiInfo = GetRttiInfo<T>::get();
+
+ SLANG_RETURN_ON_FAIL(toNativeOrSendError(value, rttiInfo, (void*)data));
+ 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);
+ }
+ return SLANG_OK;
+}
+
+} // namespace Slang
+
+#endif // SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H
+
diff --git a/source/compiler-core/slang-json-rpc.cpp b/source/compiler-core/slang-json-rpc.cpp
index 1ca55e9eb..660bbd4b7 100644
--- a/source/compiler-core/slang-json-rpc.cpp
+++ b/source/compiler-core/slang-json-rpc.cpp
@@ -2,23 +2,20 @@
#include "../../slang-com-helper.h"
+#include "slang-json-native.h"
+
namespace Slang {
// https://www.jsonrpc.org/specification
-// m_sourceManager.initialize(nullptr, nullptr);
-// m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation);
-static const auto g_jsonRpc = UnownedStringSlice::fromLiteral("jsonrpc");
-static const auto g_jsonRpcVersion = UnownedStringSlice::fromLiteral("2.0");
-static const auto g_method = UnownedStringSlice::fromLiteral("method");
-static const auto g_id = UnownedStringSlice::fromLiteral("id");
-static const auto g_params = UnownedStringSlice::fromLiteral("params");
-static const auto g_code = UnownedStringSlice::fromLiteral("code");
-static const auto g_error = UnownedStringSlice::fromLiteral("error");
-static const auto g_message = UnownedStringSlice::fromLiteral("message");
+/* static */ const UnownedStringSlice JSONRPC::jsonRpc = UnownedStringSlice::fromLiteral("jsonrpc");
+/* static */const UnownedStringSlice JSONRPC::jsonRpcVersion = UnownedStringSlice::fromLiteral("2.0");
+/* static */const UnownedStringSlice JSONRPC::id = UnownedStringSlice::fromLiteral("id");
+
static const auto g_result = UnownedStringSlice::fromLiteral("result");
-static const auto g_data = UnownedStringSlice::fromLiteral("data");
+static const auto g_error = UnownedStringSlice::fromLiteral("error");
+static const auto g_method = UnownedStringSlice::fromLiteral("method");
// Add the fields.
// TODO(JS): This is a little verbose, and could be improved on with something like
@@ -39,9 +36,10 @@ static const StructRttiInfo _makeJSONRPCErrorResponseRtti()
JSONRPCErrorResponse obj;
StructRttiBuilder builder(&obj, "JSONRPCErrorResponse", nullptr);
+ builder.addField("jsonrpc", &obj.jsonrpc);
builder.addField("error", &obj.error);
builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional);
- builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne));
+ builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional);
return builder.make();
}
@@ -52,9 +50,10 @@ static const StructRttiInfo _makeJSONRPCCallResponseRtti()
JSONRPCCall obj;
StructRttiBuilder builder(&obj, "JSONRPCCall", nullptr);
+ builder.addField("jsonrpc", &obj.jsonrpc);
builder.addField("method", &obj.method);
builder.addField("params", &obj.params, StructRttiInfo::Flag::Optional);
- builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne));
+ builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional);
return builder.make();
}
@@ -65,350 +64,44 @@ static const StructRttiInfo _makeJSONResultResponseResponseRtti()
JSONResultResponse obj;
StructRttiBuilder builder(&obj, "JSONResultResponse", nullptr);
+ builder.addField("jsonrpc", &obj.jsonrpc);
builder.addField("result", &obj.result);
- builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne));
+ builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional);
return builder.make();
}
/* static */const StructRttiInfo JSONResultResponse::g_rttiInfo = _makeJSONResultResponseResponseRtti();
-
-/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id)
-{
- const Index maxPairs = 4;
- JSONKeyValue pairs[maxPairs];
-
- Index i = 0;
-
- // Version number is a string
- pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion));
- pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method));
- pairs[i++] = JSONKeyValue::make(container->getKey(g_params), params);
-
- if (id >= 0)
- {
- pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id));
- }
-
- return container->createObject(pairs, i);
-}
-
-/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, Int id)
-{
- const Index maxPairs = 3;
- JSONKeyValue pairs[maxPairs];
- Index i = 0;
- // Version number is a string
- pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion));
- pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method));
-
- if (id >= 0)
- {
- pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id));
- }
-
- return container->createObject(pairs, i);
-}
-
-/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data, Int id)
-{
- // Set up the error value
- JSONValue errorValue;
- {
- const Index maxPairs = 2;
- JSONKeyValue pairs[maxPairs];
- Index i = 0;
-
- if (code != 0)
- {
- pairs[i++] = JSONKeyValue::make(container->getKey(g_code), JSONValue::makeInt(code));
- }
- if (message.getLength() > 0)
- {
- pairs[i++] = JSONKeyValue::make(container->getKey(g_message), container->createString(message));
- }
- errorValue = container->createObject(pairs, i);
- }
-
- const Index maxPairs = 4;
- JSONKeyValue pairs[maxPairs];
- Index i = 0;
-
- pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion));
- pairs[i++] = JSONKeyValue::make(container->getKey(g_error), errorValue);
-
- if (data.isValid())
- {
- pairs[i++] = JSONKeyValue::make(container->getKey(g_data), data);
- }
-
- if (id >= 0)
- {
- pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id));
- }
-
- return container->createObject(pairs, i);
-}
-
-
-/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data, Int id)
-{
- return createErrorResponse(container, Index(code), message, data, id);
-}
-
-/* static */JSONValue JSONRPCUtil::createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id)
+/* static */JSONRPCMessageType JSONRPCUtil::getMessageType(JSONContainer* container, const JSONValue& value)
{
- const Index maxPairs = 3;
- JSONKeyValue pairs[maxPairs];
- Index i = 0;
-
- pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion));
- pairs[i++] = JSONKeyValue::make(container->getKey(g_result), resultValue);
-
- if (id >= 0)
- {
- pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id));
- }
-
- return container->createObject(pairs, i);
-}
-
-/* static */ JSONRPCUtil::ResponseType JSONRPCUtil::getResponseType(JSONContainer* container, const JSONValue& response)
-{
- if (response.getKind() == JSONValue::Kind::Object)
+ if (value.getKind() == JSONValue::Kind::Object)
{
const JSONKey resultKey = container->findKey(g_result);
const JSONKey errorKey = container->findKey(g_error);
+ const JSONKey methodKey = container->findKey(g_method);
- auto pairs = container->getObject(response);
+ auto pairs = container->getObject(value);
for (const auto& pair : pairs)
{
if (pair.key == resultKey)
{
- return ResponseType::Result;
+ return JSONRPCMessageType::Result;
}
else if (pair.key == errorKey)
{
- return ResponseType::Error;
- }
- }
- }
-
- return ResponseType::Error;
-}
-
-static SlangResult _parseError(JSONContainer* container, const JSONValue& error, JSONRPCUtil::ErrorResponse& out)
-{
- if (error.getKind() != JSONValue::Kind::Object)
- {
- return SLANG_FAIL;
- }
- const auto pairs = container->getObject(error);
-
- const JSONKey messageKey = container->findKey(g_message);
- const JSONKey codeKey = container->findKey(g_code);
- const JSONKey dataKey = container->findKey(g_data);
-
- Int fieldBits = 0;
-
- for (auto const& pair : pairs)
- {
- if (pair.key == messageKey)
- {
- if (pair.value.getKind() != JSONValue::Kind::String)
- {
- return SLANG_FAIL;
- }
- out.message = container->getString(pair.value);
- fieldBits |= 0x1;
- }
- else if (pair.key == codeKey)
- {
- if (pair.value.getKind() != JSONValue::Kind::Integer)
- {
- return SLANG_FAIL;
- }
- out.code = Index(pair.value.asInteger());
- fieldBits |= 0x2;
- }
- else if (pair.key == dataKey)
- {
- out.data = pair.value;
- fieldBits |= 0x4;
- }
- else
- {
- return SLANG_FAIL;
- }
- }
-
- // Check all required fields are set
- return (fieldBits & 0x3) == 0x3 ? SLANG_OK : SLANG_FAIL;
-}
-
-/* static */SlangResult JSONRPCUtil::parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out)
-{
- if (response.getKind() != JSONValue::Kind::Object)
- {
- return SLANG_FAIL;
- }
-
- const auto pairs = container->getObject(response);
-
- const JSONKey jsonRpcKey = container->findKey(g_jsonRpc);
- const JSONKey errorKey = container->findKey(g_error);
- const JSONKey idKey = container->findKey(g_id);
-
- Int fieldBits = 0;
-
- for (auto const& pair : pairs)
- {
- if (pair.key == jsonRpcKey)
- {
- if (!container->areEqual(pair.value, g_jsonRpcVersion))
- {
- return SLANG_FAIL;
- }
- fieldBits |= 0x1;
- }
- else if (pair.key == errorKey)
- {
- // We need to decode the error
- SLANG_RETURN_ON_FAIL(_parseError(container, pair.value, out));
- fieldBits |= 0x2;
- }
- else if (pair.key == idKey)
- {
- if (pair.value.getKind() != JSONValue::Kind::Integer)
- {
- return SLANG_FAIL;
- }
- out.id = Int(pair.value.asInteger());
- fieldBits |= 0x4;
- }
- else
- {
- // Unknown key
- return SLANG_FAIL;
- }
- }
-
- // Check all the required bits are set
- return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL;
-}
-
-/* static */SlangResult JSONRPCUtil::parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out)
-{
- if (response.getKind() != JSONValue::Kind::Object)
- {
- return SLANG_FAIL;
- }
-
- const auto pairs = container->getObject(response);
-
- const JSONKey jsonRpcKey = container->findKey(g_jsonRpc);
- const JSONKey resultKey = container->findKey(g_result);
- const JSONKey idKey = container->findKey(g_id);
-
- Int fieldBits = 0;
-
- for (auto const& pair : pairs)
- {
- if (pair.key == jsonRpcKey)
- {
- if (!container->areEqual(pair.value, g_jsonRpcVersion))
- {
- return SLANG_FAIL;
- }
- fieldBits |= 0x1;
- }
- else if (pair.key == resultKey)
- {
- out.result = pair.value;
- fieldBits |= 0x2;
- }
- else if (pair.key == idKey)
- {
- if (pair.value.getKind() != JSONValue::Kind::Integer)
- {
- return SLANG_FAIL;
- }
- out.id = Int(pair.value.asInteger());
- fieldBits |= 0x4;
- }
- else
- {
- // Unknown key
- return SLANG_FAIL;
- }
- }
-
- // Check all the required bits are set
- return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL;
-}
-
-/* static */SlangResult JSONRPCUtil::parseCall(JSONContainer* container, const JSONValue& value, Call& out)
-{
- if (value.getKind() != JSONValue::Kind::Object)
- {
- return SLANG_FAIL;
- }
-
- const auto pairs = container->getObject(value);
-
- const JSONKey jsonRpcKey = container->findKey(g_jsonRpc);
- const JSONKey methodKey = container->findKey(g_method);
- const JSONKey paramsKey = container->findKey(g_params);
- const JSONKey idKey = container->findKey(g_id);
-
- Int fieldBits = 0;
-
- for (auto const& pair : pairs)
- {
- if (pair.key == jsonRpcKey)
- {
- if (!container->areEqual(pair.value, g_jsonRpcVersion))
- {
- return SLANG_FAIL;
+ return JSONRPCMessageType::Error;
}
- fieldBits |= 0x1;
- }
- else if (pair.key == methodKey)
- {
- if (pair.value.getKind() != JSONValue::Kind::String)
- {
- return SLANG_FAIL;
- }
- out.method = container->getString(pair.value);
- fieldBits |= 0x2;
- }
- else if (pair.key == idKey)
- {
- if (pair.value.getKind() != JSONValue::Kind::Integer)
+ else if (pair.key == methodKey)
{
- return SLANG_FAIL;
+ return JSONRPCMessageType::Call;
}
- out.id = Int(pair.value.asInteger());
- fieldBits |= 0x4;
- }
- else if (pair.key == paramsKey)
- {
- out.params = pair.value;
- fieldBits |= 0x8;
- }
- else
- {
- // Unknown key
- return SLANG_FAIL;
}
}
- // Check all the required bits are set
- return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL;
+ return JSONRPCMessageType::Invalid;
}
-
/* static */SlangResult JSONRPCUtil::parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue)
{
SourceManager* sourceManager = sink->getSourceManager();
@@ -430,21 +123,50 @@ static SlangResult _parseError(JSONContainer* container, const JSONValue& error,
return SLANG_OK;
}
-SlangResult JSONRPCUtil::parseJSONAndConsume(HTTPPacketConnection* connection, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue)
+/* static */SlangResult JSONRPCUtil::convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, const RttiInfo* rttiInfo, void* out)
{
- if (!connection->hasContent())
- {
- return SLANG_FAIL;
- }
+ JSONToNativeConverter converter(container, sink);
+ SLANG_RETURN_ON_FAIL(converter.convert(value, rttiInfo, out));
+ return SLANG_OK;
+}
- auto content = connection->getContent();
+/* static */SlangResult JSONRPCUtil::convertToJSON(const RttiInfo* rttiInfo, const void* in, DiagnosticSink* sink, StringBuilder& out)
+{
+ SourceManager* sourceManager = sink->getSourceManager();
+ JSONContainer container(sourceManager);
- UnownedStringSlice text((const char*)content.begin(), content.getCount());
- SlangResult res = parseJSON(text, container, sink, outValue);
+ NativeToJSONConverter converter(&container, sink);
- // Consume the content
- connection->consumeContent();
- return res;
+ JSONValue value;
+ SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, in, value));
+
+ // Convert into a string
+ JSONWriter writer(JSONWriter::IndentationStyle::Allman);
+ container.traverseRecursively(value, &writer);
+
+ out = writer.getBuilder();
+ return SLANG_OK;
}
+/* static */JSONValue JSONRPCUtil::getId(JSONContainer* container, const JSONValue& root)
+{
+ if (root.getKind() == JSONValue::Kind::Object)
+ {
+ const JSONKey key = container->findKey(JSONRPC::id);
+
+ if (key != JSONKey(0))
+ {
+ auto obj = container->getObject(root);
+ Index index = obj.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; });
+
+ if (index >= 0)
+ {
+ return obj[index].value;
+ }
+ }
+ }
+ return JSONValue();
+}
+
+
} // namespace Slang
diff --git a/source/compiler-core/slang-json-rpc.h b/source/compiler-core/slang-json-rpc.h
index e85664ceb..f32513b02 100644
--- a/source/compiler-core/slang-json-rpc.h
+++ b/source/compiler-core/slang-json-rpc.h
@@ -12,115 +12,124 @@
namespace Slang {
+/// Struct to hold values associated with JSON-RPC
+struct JSONRPC
+{
+ enum class ErrorCode
+ {
+ ParseError = -32700, ///< Invalid JSON was received by the server.
+ InvalidRequest = -32600, ///< The JSON sent is not a valid Request object.
+ MethodNotFound = -32601, ///< The method does not exist / is not available.
+ InvalidParams = -32602, ///< Invalid method parameter(s).
+ InternalError = -32603, ///< Internal JSON - RPC error.
+
+ ServerImplStart = -32000, ///< Server implementation defined error range
+ ServerImplEnd = -32099,
+ };
+
+ static bool isIdOk(const JSONValue& value)
+ {
+ auto kind = value.getKind();
+ switch (kind)
+ {
+ case JSONValue::Kind::Integer:
+ case JSONValue::Kind::Invalid:
+ case JSONValue::Kind::String:
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static const UnownedStringSlice jsonRpc;
+ static const UnownedStringSlice jsonRpcVersion;
+ static const UnownedStringSlice id;
+};
+
struct JSONRPCErrorResponse
{
struct Error
{
- Index code = 0; ///< Value from ErrorCode
- UnownedStringSlice message; ///< Error message
+ bool isValid() const { return code != 0; }
+ Int code = 0; ///< Value from ErrorCode
+ UnownedStringSlice message; ///< Error message
+
static const StructRttiInfo g_rttiInfo;
};
+ bool isValid() const { return jsonrpc == JSONRPC::jsonRpcVersion && error.isValid() && JSONRPC::isIdOk(id); }
+
+ UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion;
Error error;
- JSONValue data;
- Int id = -1; ///< Id of initiating method or -1 if not set
+ JSONValue data; ///< Optional data describing the errro
+ JSONValue id; ///< Id associated with this request
static const StructRttiInfo g_rttiInfo;
};
struct JSONRPCCall
{
+ bool isValid() const { return method.getLength() > 0 && jsonrpc == JSONRPC::jsonRpcVersion && JSONRPC::isIdOk(id); }
+
+ UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion;
UnownedStringSlice method; ///< The name of the method
JSONValue params; ///< Can be invalid/array/object
- Int id = -1; ///< Id associated with this request, or -1 if not set
+ JSONValue id; ///< Id associated with this request
static const StructRttiInfo g_rttiInfo;
};
struct JSONResultResponse
{
- JSONValue result; ///< The result value
- Int id = -1; ///< Id of initiating method or -1 if not set
+ bool isValid() const { return jsonrpc == JSONRPC::jsonRpcVersion && JSONRPC::isIdOk(id); }
+
+ UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion;
+ JSONValue result; ///< The result value
+ JSONValue id; ///< Id associated with this request
static const StructRttiInfo g_rttiInfo;
};
+enum class JSONRPCMessageType
+{
+ Invalid,
+ Result,
+ Call,
+ Error,
+ CountOf,
+};
+
/// Send and receive messages as JSON
-///
-/// Strictly speaking should support ids, as strings or ids. Currently just supports with integer ids.
-/// One way of dealing with this would be to just use JSONValue for ids, would allow invalid/string/integer and
-/// a mechanism to compare/display etc.
class JSONRPCUtil
{
public:
- enum class ErrorCode
- {
- ParseError = -32700, ///< Invalid JSON was received by the server.
- InvalidRequest = -32600, ///< The JSON sent is not a valid Request object.
- MethodNotFound = -32601, ///< The method does not exist / is not available.
- InvalidParams = -32602, ///< Invalid method parameter(s).
- InternalError = -32603, ///< Internal JSON - RPC error.
-
- ServerImplStart = -32000, ///< Server implementation defined error range
- ServerImplEnd = -32099,
- };
-
- enum class ResponseType
- {
- Invalid,
- Error,
- Result
- };
-
- struct ErrorResponse
- {
- Index code = 0; ///< Value from ErrorCode
- UnownedStringSlice message; ///< Error message
- JSONValue data;
- Int id = -1; ///< Id of initiating method or -1 if not set
- };
-
- struct ResultResponse
- {
- JSONValue result; ///< The result value
- Int id = -1; ///< Id of initiating method or -1 if not set
- };
-
- struct Call
- {
- UnownedStringSlice method; ///< The name of the method
- JSONValue params; ///< Can be invalid/array/object
- Int id = -1; ///< Id associated with this request, or -1 if not set
- };
-
- /// Parameters can be either named or via index.
- static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id = -1);
- /// Parameters can be either named or via index.
- static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, Int id = -1);
-
- /// Create an error response
- /// Code should typically be something in the ErrorCode range
- static JSONValue createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1);
- static JSONValue createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1);
- /// Create a result response
- static JSONValue createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id = -1);
-
/// Determine the response type
- static ResponseType getResponseType(JSONContainer* container, const JSONValue& response);
+ static JSONRPCMessageType getMessageType(JSONContainer* container, const JSONValue& value);
- static SlangResult parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out);
+ /// Parse slice into JSONContainer. outValue is the root of the hierarchy.
+ /// NOTE! Uses and *assumes* there is a source manager on the sink. outValue is likely only usable whilst the sourceManger is in scope
+ /// The sourceLoc can only be interpretted with the sourceLoc anyway
+ static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue);
- static SlangResult parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out);
+ /// Convert value into out
+ static SlangResult convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, const RttiInfo* rttiInfo, void* out);
+ template <typename T>
+ static SlangResult convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, T& out) { return convertToNative(container, value, sink, GetRttiInfo<T>::get(), (void*)&out); }
- static SlangResult parseCall(JSONContainer* container, const JSONValue& value, Call& out);
+ /// Convert to JSON
+ static SlangResult convertToJSON(const RttiInfo* rttiInfo, const void* in, DiagnosticSink* sink, StringBuilder& out);
- /// Parse slice into JSONContainer. outValue is the root of the hierarchy.
- static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue);
+ template <typename T>
+ static SlangResult convertToJSON(const T* in, DiagnosticSink* sink, StringBuilder& out)
+ {
+ return convertToJSON(GetRttiInfo<T>::get(), (const void*)in, sink, out);
+ }
- /// Parse content from stream, and consume the packet
- static SlangResult parseJSONAndConsume(HTTPPacketConnection* connection, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue);
+ /// Get an id directly from root (assumed id: is in root object definition).
+ static JSONValue getId(JSONContainer* container, const JSONValue& root);
};
} // namespace Slang
diff --git a/source/compiler-core/slang-json-value.cpp b/source/compiler-core/slang-json-value.cpp
index 4fad3764f..216512407 100644
--- a/source/compiler-core/slang-json-value.cpp
+++ b/source/compiler-core/slang-json-value.cpp
@@ -164,6 +164,8 @@ void JSONContainer::reset()
m_objectValues.clear();
_addRange(Range::Type::None, 0, 0);
+
+ m_currentView = nullptr;
}
/* static */bool JSONContainer::areKeysUnique(const JSONKeyValue* keyValues, Index keyValueCount)
diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h
index 119479285..b94283d81 100644
--- a/source/compiler-core/slang-json-value.h
+++ b/source/compiler-core/slang-json-value.h
@@ -100,7 +100,7 @@ struct JSONValue
/// Given a type return the associated kind
static Kind getKindForType(Type type) { return g_typeToKind[Index(type)]; }
- Type type; ///< The type of value
+ Type type = Type::Invalid; ///< The type of value
SourceLoc loc; ///< The (optional) location in source of this value.
union
@@ -112,6 +112,7 @@ struct JSONValue
JSONKey stringKey; ///< The pool key if it's a string
};
+
static const Kind g_typeToKind[Index(Type::CountOf)];
static const OtherRttiInfo g_rttiInfo;
diff --git a/source/compiler-core/slang-test-server-protocol.cpp b/source/compiler-core/slang-test-server-protocol.cpp
new file mode 100644
index 000000000..2a3bb3a3b
--- /dev/null
+++ b/source/compiler-core/slang-test-server-protocol.cpp
@@ -0,0 +1,43 @@
+#include "slang-test-server-protocol.h"
+
+namespace TestServerProtocol {
+
+static const StructRttiInfo _makeExecuteUnitTestArgsRtti()
+{
+ ExecuteUnitTestArgs obj;
+ StructRttiBuilder builder(&obj, "TestServerProtocol::ExecuteUnitTestArgs", nullptr);
+
+ builder.addField("moduleName", &obj.moduleName);
+ builder.addField("testName", &obj.testName);
+ builder.addField("enabledApis", &obj.enabledApis);
+ return builder.make();
+}
+/* static */const UnownedStringSlice ExecuteUnitTestArgs::g_methodName = UnownedStringSlice::fromLiteral("unitTest");
+/* static */const StructRttiInfo ExecuteUnitTestArgs::g_rttiInfo = _makeExecuteUnitTestArgsRtti();
+
+static const StructRttiInfo _makeExecuteToolTestArgsRtti()
+{
+ ExecuteToolTestArgs obj;
+ StructRttiBuilder builder(&obj, "TestServerProtocol::ExecuteToolTestArgs", nullptr);
+ builder.addField("toolName", &obj.toolName);
+ builder.addField("args", &obj.args);
+ return builder.make();
+}
+/* static */const StructRttiInfo ExecuteToolTestArgs::g_rttiInfo = _makeExecuteToolTestArgsRtti();
+/* static */const UnownedStringSlice ExecuteToolTestArgs::g_methodName = UnownedStringSlice::fromLiteral("tool");
+
+static const StructRttiInfo _makeExecutionResultRtti()
+{
+ ExecutionResult obj;
+ StructRttiBuilder builder(&obj, "TestServerProtocol::ExecutionResult", nullptr);
+ builder.addField("stdOut", &obj.stdOut);
+ builder.addField("stdError", &obj.stdError);
+ builder.addField("result", &obj.result);
+ builder.addField("returnCode", &obj.returnCode);
+ return builder.make();
+}
+/* static */const StructRttiInfo ExecutionResult::g_rttiInfo = _makeExecutionResultRtti();
+
+/* static */const UnownedStringSlice QuitArgs::g_methodName = UnownedStringSlice::fromLiteral("quit");
+
+} // namespace TestServerProtocol
diff --git a/source/compiler-core/slang-test-server-protocol.h b/source/compiler-core/slang-test-server-protocol.h
new file mode 100644
index 000000000..8b61c4b39
--- /dev/null
+++ b/source/compiler-core/slang-test-server-protocol.h
@@ -0,0 +1,51 @@
+#ifndef SLANG_COMPILER_CORE_TEST_PROTOCOL_H
+#define SLANG_COMPILER_CORE_TEST_PROTOCOL_H
+
+#include "../../slang.h"
+#include "../../slang-com-helper.h"
+#include "../../slang-com-ptr.h"
+
+#include "../core/slang-rtti-info.h"
+#include "slang-json-value.h"
+
+namespace TestServerProtocol {
+
+using namespace Slang;
+
+struct ExecuteUnitTestArgs
+{
+ String moduleName;
+ String testName;
+ uint32_t enabledApis;
+
+ static const UnownedStringSlice g_methodName;
+ static const StructRttiInfo g_rttiInfo;
+};
+
+struct ExecuteToolTestArgs
+{
+ String toolName; ///< The name of the tool (will be a shared library typically - like render-test). Doesn't need -tool suffix.
+ List<String> args; ///< Arguments passed to the tool during exectution
+
+ static const UnownedStringSlice g_methodName;
+ static const StructRttiInfo g_rttiInfo;
+};
+
+struct QuitArgs
+{
+ static const UnownedStringSlice g_methodName;
+};
+
+struct ExecutionResult
+{
+ String stdOut;
+ String stdError;
+ int32_t result = SLANG_OK;
+ int32_t returnCode = 0; ///< As returned if invoked as command line
+
+ static const StructRttiInfo g_rttiInfo;
+};
+
+} // namespace Slang
+
+#endif // SLANG_COMPILER_CORE_TEST_PROTOCOL_H
diff --git a/source/core/slang-http.cpp b/source/core/slang-http.cpp
index 43be26c1c..6a4bb4f92 100644
--- a/source/core/slang-http.cpp
+++ b/source/core/slang-http.cpp
@@ -287,8 +287,19 @@ SlangResult HTTPPacketConnection::update()
return m_readResult;
}
-SlangResult HTTPPacketConnection::waitForResult()
+SlangResult HTTPPacketConnection::waitForResult(Int timeOutInMs)
{
+ m_readResult = SLANG_OK;
+
+ int64_t startTick = 0;
+ int64_t timeOutInTicks = -1;
+
+ if (timeOutInMs >= 0)
+ {
+ timeOutInTicks = timeOutInMs * (Process::getClockFrequency() / 1000);
+ startTick = Process::getClockTick();
+ }
+
while (m_readState == ReadState::Header ||
m_readState == ReadState::Content)
{
@@ -296,6 +307,17 @@ SlangResult HTTPPacketConnection::waitForResult()
SLANG_RETURN_ON_FAIL(update());
+ if (m_readState == ReadState::Done)
+ {
+ break;
+ }
+
+ // We timed out
+ if (timeOutInTicks >= 0 && int64_t(Process::getClockTick()) - startTick >= timeOutInTicks)
+ {
+ break;
+ }
+
if (prevCount == m_readStream->getCount())
{
// Yield if it appears nothing was read.
diff --git a/source/core/slang-http.h b/source/core/slang-http.h
index a298e9e27..784357273 100644
--- a/source/core/slang-http.h
+++ b/source/core/slang-http.h
@@ -123,8 +123,9 @@ public:
/// Write. Will potentially block if write stream is blocking.
SlangResult write(const void* content, size_t sizeInBytes);
- /// Blocks until some result - a packet, closure, or some kind of error
- SlangResult waitForResult();
+ /// Blocks until some result - a packet, closure, or some kind of error or timeout.
+ /// TimeOut of -1 means no timeout.
+ SlangResult waitForResult(Int timeOutInMs = -1);
/// Consume the content - so can read next content
void consumeContent();
diff --git a/source/core/slang-writer.h b/source/core/slang-writer.h
index 380823b39..7c768a22f 100644
--- a/source/core/slang-writer.h
+++ b/source/core/slang-writer.h
@@ -148,6 +148,7 @@ public:
Parent(flags),
m_builder(builder)
{}
+ ~StringWriter() {}
protected:
StringBuilder* m_builder;