From d8a934c08014b6140dde45600dc383557fda35cf Mon Sep 17 00:00:00 2001 From: amasson Date: Sat, 12 Oct 2024 12:55:37 +0200 Subject: [PATCH 01/16] Network object pool --- Engine/CMakeLists.txt | 1 + Engine/{Networking => Network}/CMakeLists.txt | 3 +- Engine/Network/include/Network.hpp | 5 + Engine/Network/include/Network/ObjectPool.hpp | 68 ++++++++++++ Engine/Network/src/Network/ObjectPool.cpp | 3 + Engine/Network/test/test_ObjectPool.cpp | 101 ++++++++++++++++++ Engine/Networking/include/Networking.hpp | 1 - Engine/Networking/src/library.cpp | 2 - 8 files changed, 180 insertions(+), 4 deletions(-) rename Engine/{Networking => Network}/CMakeLists.txt (63%) create mode 100644 Engine/Network/include/Network.hpp create mode 100644 Engine/Network/include/Network/ObjectPool.hpp create mode 100644 Engine/Network/src/Network/ObjectPool.cpp create mode 100644 Engine/Network/test/test_ObjectPool.cpp delete mode 100644 Engine/Networking/include/Networking.hpp delete mode 100644 Engine/Networking/src/library.cpp diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index 3992a1b..1f27def 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(Logging) # Layer 2 add_subdirectory(Core) # Layer 3 +add_subdirectory(Network) add_subdirectory(Scene) # Layer 4 add_subdirectory(Physics) diff --git a/Engine/Networking/CMakeLists.txt b/Engine/Network/CMakeLists.txt similarity index 63% rename from Engine/Networking/CMakeLists.txt rename to Engine/Network/CMakeLists.txt index 8d8505c..9ef8502 100644 --- a/Engine/Networking/CMakeLists.txt +++ b/Engine/Network/CMakeLists.txt @@ -1,5 +1,6 @@ setup_module( - NAME networking + NAME network TARGET_DEPS logging utils core + ENABLE_TESTS FATAL_ERROR ) diff --git a/Engine/Network/include/Network.hpp b/Engine/Network/include/Network.hpp new file mode 100644 index 0000000..80cd13a --- /dev/null +++ b/Engine/Network/include/Network.hpp @@ -0,0 +1,5 @@ +// Copyright 2024 Stone-Engine + +#pragma once + +#include "Network/ObjectPool.hpp" diff --git a/Engine/Network/include/Network/ObjectPool.hpp b/Engine/Network/include/Network/ObjectPool.hpp new file mode 100644 index 0000000..2347dec --- /dev/null +++ b/Engine/Network/include/Network/ObjectPool.hpp @@ -0,0 +1,68 @@ +// Copyright 2024 Stone-Engine + +#pragma once + +#include +#include + +namespace Stone::Network { + +template +class ObjectPool { +public: + using Id = unsigned int; + + ObjectPool() = default; + ObjectPool(const ObjectPool &) = delete; + + virtual ~ObjectPool() = default; + + Id add(const std::shared_ptr &object) { + Id id = _makeUsableId(); + _objects[id] = object; + return id; + } + + void set(Id id, const std::shared_ptr &object) { + if (id >= _objects.size()) { + _objects.resize(id + 1); + } + _objects[id] = object; + _incrementLastUsableId(); + } + + std::shared_ptr get(Id id) const { + return _objects[id].lock(); + } + + const std::weak_ptr &weak_get(Id id) const { + return _objects[id]; + } + + void refreshId() { + _lastUsableId = 0; + _incrementLastUsableId(); + } + +private: + Id _makeUsableId() { + Id id = _lastUsableId++; + _incrementLastUsableId(); + return id; + } + + void _incrementLastUsableId() { + while (_lastUsableId < _objects.size() && !_objects[_lastUsableId].expired()) { + ++_lastUsableId; + } + if (_lastUsableId >= _objects.size()) { + _objects.resize(_lastUsableId + 1); + } + } + + Id _lastUsableId = 0; // The last id that is usable for the next object + + std::vector> _objects; +}; + +} // namespace Stone::Network diff --git a/Engine/Network/src/Network/ObjectPool.cpp b/Engine/Network/src/Network/ObjectPool.cpp new file mode 100644 index 0000000..a81c4fd --- /dev/null +++ b/Engine/Network/src/Network/ObjectPool.cpp @@ -0,0 +1,3 @@ +// Copyright 2024 Stone-Engine + +#include "Network/ObjectPool.hpp" diff --git a/Engine/Network/test/test_ObjectPool.cpp b/Engine/Network/test/test_ObjectPool.cpp new file mode 100644 index 0000000..b90f108 --- /dev/null +++ b/Engine/Network/test/test_ObjectPool.cpp @@ -0,0 +1,101 @@ +#include "Network/ObjectPool.hpp" + +#include + +using namespace Stone::Network; + +using PoolId = Stone::Network::ObjectPool::Id; + +TEST(ObjectPool, AddObject) { + ObjectPool pool; + auto object = std::make_shared(42); + PoolId id = pool.add(object); + auto result = pool.get(id); + ASSERT_EQ(*result, 42); +} + +TEST(ObjectPool, AddMultipleObjects) { + ObjectPool pool; + auto object1 = std::make_shared(42); + auto object2 = std::make_shared(43); + PoolId id1 = pool.add(object1); + PoolId id2 = pool.add(object2); + auto result1 = pool.get(id1); + auto result2 = pool.get(id2); + ASSERT_EQ(*result1, 42); + ASSERT_EQ(*result2, 43); +} + +TEST(ObjectPool, SetObject) { + ObjectPool pool; + auto object = std::make_shared(42); + PoolId id = pool.add(object); + auto result = pool.get(id); + ASSERT_EQ(*result, 42); + + auto newObject = std::make_shared(43); + pool.set(id, newObject); + auto newResult = pool.get(id); + ASSERT_EQ(*newResult, 43); +} + +TEST(ObjectPool, SetMultipleObjects) { + ObjectPool pool; + auto object1 = std::make_shared(42); + auto object2 = std::make_shared(43); + PoolId id1 = pool.add(object1); + PoolId id2 = pool.add(object2); + auto result1 = pool.get(id1); + auto result2 = pool.get(id2); + ASSERT_EQ(*result1, 42); + ASSERT_EQ(*result2, 43); + + auto newObject1 = std::make_shared(44); + auto newObject2 = std::make_shared(45); + pool.set(id1, newObject1); + pool.set(id2, newObject2); + auto newResult1 = pool.get(id1); + auto newResult2 = pool.get(id2); + ASSERT_EQ(*newResult1, 44); + ASSERT_EQ(*newResult2, 45); +} + +TEST(ObjectPool, RefreshId) { + ObjectPool pool; + + auto object1 = std::make_shared(42); + auto object2 = std::make_shared(43); + PoolId id1 = pool.add(object1); + PoolId id2 = pool.add(object2); + ASSERT_EQ(id1, 0); + ASSERT_EQ(id2, 1); + + { + auto result1 = pool.get(id1); + auto result2 = pool.get(id2); + ASSERT_EQ(*result1, 42); + ASSERT_EQ(*result2, 43); + } + + object1.reset(); + object2.reset(); + ASSERT_EQ(pool.get(id1), nullptr); + ASSERT_EQ(pool.get(id2), nullptr); + + auto object3 = std::make_shared(44); + PoolId id3 = pool.add(object3); + ASSERT_EQ(id3, 2); + + { + auto result3 = pool.get(id3); + ASSERT_EQ(*result3, 44); + } + + pool.refreshId(); + auto newObject1 = std::make_shared(44); + auto newObject2 = std::make_shared(45); + PoolId newId1 = pool.add(newObject1); + PoolId newId2 = pool.add(newObject2); + ASSERT_EQ(newId1, 0); + ASSERT_EQ(newId2, 1); +} diff --git a/Engine/Networking/include/Networking.hpp b/Engine/Networking/include/Networking.hpp deleted file mode 100644 index 6f70f09..0000000 --- a/Engine/Networking/include/Networking.hpp +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/Engine/Networking/src/library.cpp b/Engine/Networking/src/library.cpp deleted file mode 100644 index 8cfbf65..0000000 --- a/Engine/Networking/src/library.cpp +++ /dev/null @@ -1,2 +0,0 @@ -void lib() { -} From fd816886b8b88236c10be78db4b27f1ea6e62204 Mon Sep 17 00:00:00 2001 From: amasson Date: Thu, 17 Oct 2024 20:26:28 +0200 Subject: [PATCH 02/16] [wip] network objects --- .../include/Network/CommandExecutor.hpp | 24 +++++++++++++ .../Network/include/Network/NetworkObject.hpp | 36 +++++++++++++++++++ .../include/Network/NetworkSession.hpp | 26 ++++++++++++++ Engine/Network/include/Network/ObjectPool.hpp | 11 +++--- Engine/Network/src/Network/NetworkObject.cpp | 29 +++++++++++++++ Engine/Network/test/test_ObjectPool.cpp | 10 +++--- 6 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 Engine/Network/include/Network/CommandExecutor.hpp create mode 100644 Engine/Network/include/Network/NetworkObject.hpp create mode 100644 Engine/Network/include/Network/NetworkSession.hpp create mode 100644 Engine/Network/src/Network/NetworkObject.cpp diff --git a/Engine/Network/include/Network/CommandExecutor.hpp b/Engine/Network/include/Network/CommandExecutor.hpp new file mode 100644 index 0000000..5c95823 --- /dev/null +++ b/Engine/Network/include/Network/CommandExecutor.hpp @@ -0,0 +1,24 @@ +// Copyright 2024 Stone-Engine + +#pragma once + +#include + +namespace Stone::Network { + +template +class CommandExecutor { +public: + using Command = std::function &)>; + + CommandExecutor() = default; + CommandExecutor(const CommandExecutor &) = delete; + + virtual ~CommandExecutor() = default; + + void execute(const Command &command, const std::shared_ptr &object) { + command(object); + } +}; + +} // namespace Stone::Network diff --git a/Engine/Network/include/Network/NetworkObject.hpp b/Engine/Network/include/Network/NetworkObject.hpp new file mode 100644 index 0000000..e8c69b2 --- /dev/null +++ b/Engine/Network/include/Network/NetworkObject.hpp @@ -0,0 +1,36 @@ +// Copyright 2024 Stone-Engine + +#include "Core/Object.hpp" +#include "Network/ObjectPool.hpp" +#include "Utils/SigSlot.hpp" + +namespace Stone::Network { + +class NetworkObject : public Core::Object { + STONE_OBJECT(NetworkObject) + +public: + NetworkObject() = default; + NetworkObject(const NetworkObject &other) = default; + + ~NetworkObject() override = default; + + std::ostream &writeToStream(std::ostream &stream, bool closing_bracer) const override; + + ObjectPool::Id getPoolId() const; + + Signal onReceivingData; + Signal onSendingData; + +protected: + virtual void receiveData(const std::uint8_t *data, std::size_t size); + + virtual void sendData(const std::uint8_t *data, std::size_t &size) const; + +private: + ObjectPool::Id _poolId = 0; + + friend class NetworkSession; +}; + +} // namespace Stone::Network diff --git a/Engine/Network/include/Network/NetworkSession.hpp b/Engine/Network/include/Network/NetworkSession.hpp new file mode 100644 index 0000000..eb50067 --- /dev/null +++ b/Engine/Network/include/Network/NetworkSession.hpp @@ -0,0 +1,26 @@ +// Copyright 2024 Stone-Engine + +#pragma once + +#include "Network/NetworkObject.hpp" +#include "Network/ObjectPool.hpp" + +namespace Stone::Network { + +class NetworkSession { +public: + NetworkSession() = default; + + ObjectPool &getObjectPool() { + return _objectPool; + } + +private: + // TODO: Add CommunicationDevice class, which will be used to send and receive data + + // TODO: Add CommandExecutor class, which will be used to execute commands + + ObjectPool _objectPool; +}; + +} // namespace Stone::Network diff --git a/Engine/Network/include/Network/ObjectPool.hpp b/Engine/Network/include/Network/ObjectPool.hpp index 2347dec..14f305c 100644 --- a/Engine/Network/include/Network/ObjectPool.hpp +++ b/Engine/Network/include/Network/ObjectPool.hpp @@ -12,7 +12,10 @@ class ObjectPool { public: using Id = unsigned int; - ObjectPool() = default; + ObjectPool() : _lastUsableId(1), _objects() { + _incrementLastUsableId(); + }; + ObjectPool(const ObjectPool &) = delete; virtual ~ObjectPool() = default; @@ -40,7 +43,7 @@ class ObjectPool { } void refreshId() { - _lastUsableId = 0; + _lastUsableId = 1; _incrementLastUsableId(); } @@ -60,9 +63,9 @@ class ObjectPool { } } - Id _lastUsableId = 0; // The last id that is usable for the next object + Id _lastUsableId; // The last id that is directly usable for the next object - std::vector> _objects; + std::vector> _objects; // The objects in the pool }; } // namespace Stone::Network diff --git a/Engine/Network/src/Network/NetworkObject.cpp b/Engine/Network/src/Network/NetworkObject.cpp new file mode 100644 index 0000000..94151e2 --- /dev/null +++ b/Engine/Network/src/Network/NetworkObject.cpp @@ -0,0 +1,29 @@ +// Copyright 2024 Stone-Engine + +#include "Network/NetworkObject.hpp" + +namespace Stone::Network { + +std::ostream &NetworkObject::writeToStream(std::ostream &stream, bool closing_bracer) const { + Core::Object::writeToStream(stream, false); + stream << ",poolId:" << _poolId; + if (closing_bracer) { + stream << "}"; + } + return stream; +} + +ObjectPool::Id NetworkObject::getPoolId() const { + return _poolId; +} + +void NetworkObject::receiveData(const std::uint8_t *data, std::size_t size) { + onReceivingData.broadcast(*this, data, size); +} + +void NetworkObject::sendData(const std::uint8_t *data, std::size_t &size) const { + onSendingData.broadcast(*this, data, size); +} + + +} // namespace Stone::Network diff --git a/Engine/Network/test/test_ObjectPool.cpp b/Engine/Network/test/test_ObjectPool.cpp index b90f108..a59bffa 100644 --- a/Engine/Network/test/test_ObjectPool.cpp +++ b/Engine/Network/test/test_ObjectPool.cpp @@ -67,8 +67,8 @@ TEST(ObjectPool, RefreshId) { auto object2 = std::make_shared(43); PoolId id1 = pool.add(object1); PoolId id2 = pool.add(object2); - ASSERT_EQ(id1, 0); - ASSERT_EQ(id2, 1); + ASSERT_EQ(id1, 1); + ASSERT_EQ(id2, 2); { auto result1 = pool.get(id1); @@ -84,7 +84,7 @@ TEST(ObjectPool, RefreshId) { auto object3 = std::make_shared(44); PoolId id3 = pool.add(object3); - ASSERT_EQ(id3, 2); + ASSERT_EQ(id3, 3); { auto result3 = pool.get(id3); @@ -96,6 +96,6 @@ TEST(ObjectPool, RefreshId) { auto newObject2 = std::make_shared(45); PoolId newId1 = pool.add(newObject1); PoolId newId2 = pool.add(newObject2); - ASSERT_EQ(newId1, 0); - ASSERT_EQ(newId2, 1); + ASSERT_EQ(newId1, 1); + ASSERT_EQ(newId2, 2); } From 053dfc704c1dd29744c4bfdfe0844851f88e65a2 Mon Sep 17 00:00:00 2001 From: amasson Date: Sat, 5 Apr 2025 01:20:31 +0200 Subject: [PATCH 03/16] Utils SigSlot signal check if bound --- Engine/Utils/include/Utils/SigSlot.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Engine/Utils/include/Utils/SigSlot.hpp b/Engine/Utils/include/Utils/SigSlot.hpp index f31770b..baf2980 100644 --- a/Engine/Utils/include/Utils/SigSlot.hpp +++ b/Engine/Utils/include/Utils/SigSlot.hpp @@ -189,6 +189,15 @@ struct Signal { } } + /** + * @brief Checks if a slot is bound to this Signal. + * + * @return True if a slot is bound to this Signal, false otherwise. + */ + bool isBound() const { + return !_slots.empty(); + } + private: std::set *> _slots; ///< The set of slots bound to this Signal. }; From bfb1cd124be35f5c2249da7e549b6b13c546a084 Mon Sep 17 00:00:00 2001 From: amasson Date: Sat, 5 Apr 2025 15:39:36 +0200 Subject: [PATCH 04/16] JsonRpcDispatcher --- .../Network/Dispatcher/JsonRpcDispatcher.hpp | 61 +++++++ .../Network/Dispatcher/JsonRpcDispatcher.cpp | 154 ++++++++++++++++++ .../Network/test/test_JsonRpcDispatcher.cpp | 83 ++++++++++ 3 files changed, 298 insertions(+) create mode 100644 Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp create mode 100644 Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp create mode 100644 Engine/Network/test/test_JsonRpcDispatcher.cpp diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp new file mode 100644 index 0000000..ca8e6ff --- /dev/null +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -0,0 +1,61 @@ +// Copyright 2024 Stone-Engine + +#pragma once + +#include "Utils/Json.hpp" +#include "Utils/SigSlot.hpp" + +#include + +#define JSONRPC_ID "id" +#define JSONRPC_METHOD "method" +#define JSONRPC_PARAMS "params" +#define JSONRPC_RESULT "result" +#define JSONRPC_ERROR "error" + +namespace Stone::Network { + +class JsonRpcDispatcher { +public: + using Id = int; + using Method = std::string; + using Params = Json::Value; + using Result = Json::Value; + using Error = std::exception; + + using SuccessCallback = std::function; + using FailureCallback = std::function; + + using SyncRequestHandler = std::function; + using AsyncRequestHandler = std::function &)>; + using RequestHandler = std::function; + + using NotificationSignal = Signal; + + JsonRpcDispatcher() = default; + JsonRpcDispatcher(const JsonRpcDispatcher &other) = default; + + virtual ~JsonRpcDispatcher() = default; + + bool registerRequestHandler(const Method &method, const SyncRequestHandler &syncHandler); + bool registerRequestHandler(const Method &method, const AsyncRequestHandler &asyncHandler); + bool registerRequestHandler(const Method &method, const RequestHandler &handler); + + bool hasRequestHandler(const Method &method) const; + + NotificationSignal &getNotificationSignal(const Method &method); + + bool handleString(const std::string &message, std::ostream &output); + bool handleStream(std::istream &stream, std::ostream &ouutput); + bool handleJsonArray(const Json::Array &message, std::ostream &output); + bool handleJsonObject(const Json::Object &message, std::ostream &output); + + bool handleRequest(Id id, const Method &method, const Params ¶ms, std::ostream &output); + bool handleNotification(const Method &method, const Params ¶ms); + +private: + std::unordered_map _requestHandlers; + std::unordered_map> _notificationSignals; +}; + +} // namespace Stone::Network diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp new file mode 100644 index 0000000..5535581 --- /dev/null +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -0,0 +1,154 @@ +// Copyright 2024 Stone-Engine + +#include "Network/Dispatcher/JsonRpcDispatcher.hpp" + + +namespace Stone::Network { + +bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const SyncRequestHandler &syncHandler) { + return registerRequestHandler( + method, [syncHandler](const Params ¶ms, const SuccessCallback &success, const FailureCallback &failure) { + try { + success(syncHandler(params)); + } catch (const Error &err) { + failure(err); + } + }); +} + +bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const AsyncRequestHandler &asyncHandler) { + return registerRequestHandler( + method, [asyncHandler](const Params ¶ms, const SuccessCallback &success, const FailureCallback &failure) { + std::promise promise; + std::future future = promise.get_future(); + asyncHandler(params, promise); + future.wait(); + try { + success(future.get()); + } catch (const Error &err) { + failure(err); + } + }); +} + +bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const RequestHandler &handler) { + auto it = _requestHandlers.find(method); + if (it != _requestHandlers.end()) { + return false; // Handler already registered + } + _requestHandlers[method] = handler; + return true; +} + +bool JsonRpcDispatcher::hasRequestHandler(const Method &method) const { + return _requestHandlers.find(method) != _requestHandlers.end(); +} + +Signal &JsonRpcDispatcher::getNotificationSignal(const Method &method) { + auto it = _notificationSignals.find(method); + if (it != _notificationSignals.end()) { + return *it->second; + } + _notificationSignals.emplace(method, std::make_unique()); + return *_notificationSignals[method]; +} + +bool JsonRpcDispatcher::handleString(const std::string &message, std::ostream &output) { + std::stringstream stream(message); + return handleStream(stream, output); +} + +bool JsonRpcDispatcher::handleStream(std::istream &stream, std::ostream &output) { + Json::Value jsonValue; + try { + Json::parseStream(stream, jsonValue); + } catch (const std::exception &e) { + output << "Failed to parse JSON: " << e.what() << std::endl; + return false; + } + if (jsonValue.is()) { + handleJsonObject(jsonValue.get(), output); + return true; + } else if (jsonValue.is()) { + handleJsonArray(jsonValue.get(), output); + return true; + } + output << "Invalid JSON-RPC message format" << std::endl; + return false; +} + +bool JsonRpcDispatcher::handleJsonArray(const Json::Array &message, std::ostream &output) { + for (const auto &item : message) { + if (item.is()) { + handleJsonObject(item.get(), output); + } else { + output << "Invalid JSON-RPC message format" << std::endl; + return false; + } + } + return true; +} + +bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostream &output) { + const auto &idPtr = message.find(JSONRPC_ID); + const bool hasId = idPtr != message.end() && idPtr->second.is(); + + const auto &methodPtr = message.find(JSONRPC_METHOD); + const bool hasMethod = methodPtr != message.end() && methodPtr->second.is(); + + if (hasMethod) { + const auto ¶ms = message.find(JSONRPC_PARAMS); + + if (hasId) + return handleRequest(idPtr->second.get(), methodPtr->second.get(), + params == message.end() ? Json::null() : params->second, output); + else + return handleNotification(methodPtr->second.get(), + params == message.end() ? Json::null() : params->second); + } else { + // TODO: Handle request results (or errors) + } + return false; +} + +bool JsonRpcDispatcher::handleRequest(Id id, const Method &method, const Params ¶ms, std::ostream &output) { + auto it = _requestHandlers.find(method); + if (it != _requestHandlers.end()) { + it->second( + params, + [&output, id](const Result &result) { + Json::Value response = Json::object(); + Json::Object &responseObject(response.get()); + responseObject[JSONRPC_ID] = Json::number(id); + responseObject[JSONRPC_RESULT] = result; + output << response.serialize() << std::endl; + }, + [&output, id](const Error &error) { + Json::Value response = Json::object(); + Json::Object &responseObject(response.get()); + responseObject[JSONRPC_ID] = Json::number(id); + responseObject[JSONRPC_ERROR] = Json::string(error.what()); + output << response.serialize() << std::endl; + }); + return true; + } + return false; +} + +bool JsonRpcDispatcher::handleNotification(const Method &method, const Params ¶ms) { + bool used = false; + auto it = _notificationSignals.find(method); + if (it != _notificationSignals.end()) { + (*it->second)(params); + used = true; + } + auto handlerIt = _requestHandlers.find(method); + if (handlerIt != _requestHandlers.end()) { + handlerIt->second(params, [](const Result &) {}, [](const Error &) {}); + used = true; + } + return used; +} + + +} // namespace Stone::Network diff --git a/Engine/Network/test/test_JsonRpcDispatcher.cpp b/Engine/Network/test/test_JsonRpcDispatcher.cpp new file mode 100644 index 0000000..60f2883 --- /dev/null +++ b/Engine/Network/test/test_JsonRpcDispatcher.cpp @@ -0,0 +1,83 @@ +#include "Network/Dispatcher/JsonRpcDispatcher.hpp" + +#include + +using namespace Stone::Network; + +TEST(JsonRpcDispatcher, HandleRequestThatIgnoreParams) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + Json::Value outJson; + + int zeValue = 0; + + dispatcher.registerRequestHandler("incValue", [&zeValue](const Json::Value ¶ms) { + (void)params; + zeValue++; + return Json::null(); + }); + + EXPECT_EQ(zeValue, 0); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue"})", out)); + EXPECT_EQ(zeValue, 1); + EXPECT_STREQ(out.str().c_str(), ""); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": null})", out)); + EXPECT_EQ(zeValue, 2); + EXPECT_STREQ(out.str().c_str(), ""); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": {}})", out)); + EXPECT_EQ(zeValue, 3); + EXPECT_STREQ(out.str().c_str(), ""); + + EXPECT_TRUE(dispatcher.handleString(R"({"id": 0, "method": "incValue", "params": {}})", out)); + EXPECT_EQ(zeValue, 4); + EXPECT_STRNE(out.str().c_str(), ""); + + out = std::stringstream(); + EXPECT_STREQ(out.str().c_str(), ""); + + EXPECT_TRUE(dispatcher.handleString(R"({"id": 1, "method": "incValue", "params": {}})", out)); + EXPECT_EQ(zeValue, 5); + EXPECT_STRNE(out.str().c_str(), ""); +} + +TEST(JsonRpcDispatcher, HandleRequestUsingNumberParam) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + Json::Value outJson; + + int zeValue = 0; + + dispatcher.registerRequestHandler("incValue", [&zeValue](const Json::Value ¶ms) { + if (!params.is()) + throw std::runtime_error("invalid params type"); + zeValue += params.get(); + return Json::number(zeValue); + }); + + EXPECT_EQ(zeValue, 0); + + EXPECT_TRUE(dispatcher.handleString(R"({"id": 0, "method": "incValue"})", out)); + ASSERT_EQ(zeValue, 0); + EXPECT_STRNE(out.str().c_str(), ""); + ASSERT_NO_THROW(out >> outJson); + ASSERT_TRUE(outJson.is()); + EXPECT_STREQ(outJson.get()["error"].get().c_str(), "invalid params type"); + out = std::stringstream(); + EXPECT_STREQ(out.str().c_str(), ""); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": 12})", out)); + EXPECT_EQ(zeValue, 12); + EXPECT_STREQ(out.str().c_str(), ""); + + EXPECT_TRUE(dispatcher.handleString(R"({"id": 1, "method": "incValue", "params": 6})", out)); + EXPECT_EQ(zeValue, 18); + EXPECT_STRNE(out.str().c_str(), ""); + ASSERT_NO_THROW(out >> outJson); + ASSERT_TRUE(outJson.is()); + EXPECT_EQ(outJson.get()["id"].get(), 1); + EXPECT_EQ(outJson.get()["result"].get(), 18); + EXPECT_EQ(outJson.get().find("error"), outJson.get().end()); +} From 7891946b733aa0fdcd87bba284babb1b61e01299 Mon Sep 17 00:00:00 2001 From: amasson Date: Sat, 5 Apr 2025 15:54:18 +0200 Subject: [PATCH 05/16] network object writeToJson --- Engine/Network/include/Network/NetworkObject.hpp | 2 +- Engine/Network/src/Network/NetworkObject.cpp | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Engine/Network/include/Network/NetworkObject.hpp b/Engine/Network/include/Network/NetworkObject.hpp index e8c69b2..25eb5a5 100644 --- a/Engine/Network/include/Network/NetworkObject.hpp +++ b/Engine/Network/include/Network/NetworkObject.hpp @@ -15,7 +15,7 @@ class NetworkObject : public Core::Object { ~NetworkObject() override = default; - std::ostream &writeToStream(std::ostream &stream, bool closing_bracer) const override; + void writeToJson(Json::Object &json) const override; ObjectPool::Id getPoolId() const; diff --git a/Engine/Network/src/Network/NetworkObject.cpp b/Engine/Network/src/Network/NetworkObject.cpp index 94151e2..a08d3dc 100644 --- a/Engine/Network/src/Network/NetworkObject.cpp +++ b/Engine/Network/src/Network/NetworkObject.cpp @@ -4,13 +4,11 @@ namespace Stone::Network { -std::ostream &NetworkObject::writeToStream(std::ostream &stream, bool closing_bracer) const { - Core::Object::writeToStream(stream, false); - stream << ",poolId:" << _poolId; - if (closing_bracer) { - stream << "}"; - } - return stream; + +void NetworkObject::writeToJson(Json::Object &json) const { + Core::Object::writeToJson(json); + + json["poolId"] = Json::number(_poolId); } ObjectPool::Id NetworkObject::getPoolId() const { From 37660470698b88cb0fb39cabcb7768ab40ad39a7 Mon Sep 17 00:00:00 2001 From: amasson Date: Sun, 6 Apr 2025 00:32:07 +0200 Subject: [PATCH 06/16] JsonRpcDispatcher Notification handlers tests --- .../Network/Dispatcher/JsonRpcDispatcher.hpp | 4 +- .../Network/Dispatcher/JsonRpcDispatcher.cpp | 14 ++--- .../Network/test/test_JsonRpcDispatcher.cpp | 52 +++++++++++++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp index ca8e6ff..fa53ae9 100644 --- a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -23,7 +23,7 @@ class JsonRpcDispatcher { using Result = Json::Value; using Error = std::exception; - using SuccessCallback = std::function; + using SuccessCallback = std::function; using FailureCallback = std::function; using SyncRequestHandler = std::function; @@ -46,7 +46,7 @@ class JsonRpcDispatcher { NotificationSignal &getNotificationSignal(const Method &method); bool handleString(const std::string &message, std::ostream &output); - bool handleStream(std::istream &stream, std::ostream &ouutput); + bool handleStream(std::istream &stream, std::ostream &output); bool handleJsonArray(const Json::Array &message, std::ostream &output); bool handleJsonObject(const Json::Object &message, std::ostream &output); diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index 5535581..a57aafb 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -2,6 +2,7 @@ #include "Network/Dispatcher/JsonRpcDispatcher.hpp" +#include namespace Stone::Network { @@ -44,13 +45,12 @@ bool JsonRpcDispatcher::hasRequestHandler(const Method &method) const { return _requestHandlers.find(method) != _requestHandlers.end(); } -Signal &JsonRpcDispatcher::getNotificationSignal(const Method &method) { - auto it = _notificationSignals.find(method); - if (it != _notificationSignals.end()) { +JsonRpcDispatcher::NotificationSignal &JsonRpcDispatcher::getNotificationSignal(const Method &method) { + if (auto it = _notificationSignals.find(method); it != _notificationSignals.end()) { return *it->second; } - _notificationSignals.emplace(method, std::make_unique()); - return *_notificationSignals[method]; + auto it = _notificationSignals.emplace(method, std::make_unique()); + return *it.first->second; } bool JsonRpcDispatcher::handleString(const std::string &message, std::ostream &output) { @@ -121,14 +121,14 @@ bool JsonRpcDispatcher::handleRequest(Id id, const Method &method, const Params Json::Object &responseObject(response.get()); responseObject[JSONRPC_ID] = Json::number(id); responseObject[JSONRPC_RESULT] = result; - output << response.serialize() << std::endl; + output << response; }, [&output, id](const Error &error) { Json::Value response = Json::object(); Json::Object &responseObject(response.get()); responseObject[JSONRPC_ID] = Json::number(id); responseObject[JSONRPC_ERROR] = Json::string(error.what()); - output << response.serialize() << std::endl; + output << response; }); return true; } diff --git a/Engine/Network/test/test_JsonRpcDispatcher.cpp b/Engine/Network/test/test_JsonRpcDispatcher.cpp index 60f2883..7b1ac6e 100644 --- a/Engine/Network/test/test_JsonRpcDispatcher.cpp +++ b/Engine/Network/test/test_JsonRpcDispatcher.cpp @@ -81,3 +81,55 @@ TEST(JsonRpcDispatcher, HandleRequestUsingNumberParam) { EXPECT_EQ(outJson.get()["result"].get(), 18); EXPECT_EQ(outJson.get().find("error"), outJson.get().end()); } + + +TEST(JsonRpcDispatcher, HandleNotificationThatIgnoreParams) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + + int zeValue = 0; + auto incValue = [&zeValue](const Json::Value ¶ms) { + (void)params; + zeValue++; + }; + Stone::Slot incSlot(incValue); + + dispatcher.getNotificationSignal("incValue").bind(incSlot); + + EXPECT_EQ(zeValue, 0); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue"})", out)); + EXPECT_EQ(zeValue, 1); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": null})", out)); + EXPECT_EQ(zeValue, 2); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": {}})", out)); + EXPECT_EQ(zeValue, 3); +} + +TEST(JsonRpcDispatcher, HandleNotificationUsingParams) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + + int zeValue = 0; + auto incValue = [&zeValue](const Json::Value ¶ms) { + if (!params.is()) + return; + zeValue += params.get(); + }; + Stone::Slot incSlot(incValue); + + dispatcher.getNotificationSignal("incValue").bind(incSlot); + + EXPECT_EQ(zeValue, 0); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue"})", out)); + EXPECT_EQ(zeValue, 0); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": 2})", out)); + EXPECT_EQ(zeValue, 2); + + EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": [2]})", out)); + EXPECT_EQ(zeValue, 2); +} From 630484919d4c19a63eb445830e7bde09bcdfe845 Mon Sep 17 00:00:00 2001 From: amasson Date: Sun, 6 Apr 2025 16:17:58 +0200 Subject: [PATCH 07/16] JsonRpcDispatcher send request and receive responses --- .../Network/Dispatcher/JsonRpcDispatcher.hpp | 12 +- .../Network/Dispatcher/JsonRpcDispatcher.cpp | 65 ++++++++- .../Network/test/test_JsonRpcDispatcher.cpp | 129 ++++++++++++++++++ Engine/Utils/include/Utils/Json.hpp | 8 ++ 4 files changed, 211 insertions(+), 3 deletions(-) diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp index fa53ae9..e7d713e 100644 --- a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -21,7 +21,7 @@ class JsonRpcDispatcher { using Method = std::string; using Params = Json::Value; using Result = Json::Value; - using Error = std::exception; + using Error = std::string; using SuccessCallback = std::function; using FailureCallback = std::function; @@ -32,6 +32,8 @@ class JsonRpcDispatcher { using NotificationSignal = Signal; + using ResponseCallbacks = std::pair; + JsonRpcDispatcher() = default; JsonRpcDispatcher(const JsonRpcDispatcher &other) = default; @@ -45,6 +47,9 @@ class JsonRpcDispatcher { NotificationSignal &getNotificationSignal(const Method &method); + bool sendRequest(std::ostream &output, const Method &method, const Params ¶ms, + const ResponseCallbacks &callbacks); + bool handleString(const std::string &message, std::ostream &output); bool handleStream(std::istream &stream, std::ostream &output); bool handleJsonArray(const Json::Array &message, std::ostream &output); @@ -52,10 +57,15 @@ class JsonRpcDispatcher { bool handleRequest(Id id, const Method &method, const Params ¶ms, std::ostream &output); bool handleNotification(const Method &method, const Params ¶ms); + bool handleResponseSuccess(Id id, const Result &result); + bool handleResponseError(Id id, const Error &error); private: std::unordered_map _requestHandlers; std::unordered_map> _notificationSignals; + + Id _nextId = 0; + std::unordered_map _pendingRequests; }; } // namespace Stone::Network diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index a57aafb..2da1e62 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -4,6 +4,8 @@ #include +#define MAX_REQUEST_LOOP 1000000000 + namespace Stone::Network { bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const SyncRequestHandler &syncHandler) { @@ -13,6 +15,10 @@ bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const SyncR success(syncHandler(params)); } catch (const Error &err) { failure(err); + } catch (const std::exception &e) { + failure(e.what()); + } catch (...) { + failure("Unknown error occurred"); } }); } @@ -28,6 +34,10 @@ bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const Async success(future.get()); } catch (const Error &err) { failure(err); + } catch (const std::exception &e) { + failure(e.what()); + } catch (...) { + failure("Unknown error occurred"); } }); } @@ -53,6 +63,26 @@ JsonRpcDispatcher::NotificationSignal &JsonRpcDispatcher::getNotificationSignal( return *it.first->second; } +bool JsonRpcDispatcher::sendRequest(std::ostream &output, const Method &method, const Params ¶ms, + const ResponseCallbacks &callbacks) { + _nextId++; + if (_nextId >= MAX_REQUEST_LOOP) + _nextId = 1; + Json::Value request = Json::object(); + Json::Object &requestObject(request.get()); + requestObject[JSONRPC_ID] = Json::number(_nextId); + requestObject[JSONRPC_METHOD] = Json::string(method); + if (!params.isNull()) + requestObject[JSONRPC_PARAMS] = params; + _pendingRequests[_nextId] = callbacks; + output << request; + if (output.fail() || output.bad()) { + _pendingRequests.erase(_nextId); + return false; + } + return true; +} + bool JsonRpcDispatcher::handleString(const std::string &message, std::ostream &output) { std::stringstream stream(message); return handleStream(stream, output); @@ -106,7 +136,19 @@ bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostre return handleNotification(methodPtr->second.get(), params == message.end() ? Json::null() : params->second); } else { - // TODO: Handle request results (or errors) + if (hasId) { + const auto &resultPtr = message.find(JSONRPC_RESULT); + const bool hasResult = resultPtr != message.end() && !resultPtr->second.isNull(); + + const auto &errorPtr = message.find(JSONRPC_ERROR); + const bool hasError = errorPtr != message.end() && errorPtr->second.is(); + + if (hasResult && !hasError) { + return handleResponseSuccess(idPtr->second.get(), resultPtr->second); + } else if (!hasResult && hasError) { + return handleResponseError(idPtr->second.get(), errorPtr->second.get()); + } + } } return false; } @@ -127,7 +169,7 @@ bool JsonRpcDispatcher::handleRequest(Id id, const Method &method, const Params Json::Value response = Json::object(); Json::Object &responseObject(response.get()); responseObject[JSONRPC_ID] = Json::number(id); - responseObject[JSONRPC_ERROR] = Json::string(error.what()); + responseObject[JSONRPC_ERROR] = Json::string(error); output << response; }); return true; @@ -150,5 +192,24 @@ bool JsonRpcDispatcher::handleNotification(const Method &method, const Params &p return used; } +bool JsonRpcDispatcher::handleResponseSuccess(Id id, const Result &result) { + auto it = _pendingRequests.find(id); + if (it != _pendingRequests.end()) { + it->second.first(result); + _pendingRequests.erase(it); + return true; + } + return false; +} + +bool JsonRpcDispatcher::handleResponseError(Id id, const Error &error) { + auto it = _pendingRequests.find(id); + if (it != _pendingRequests.end()) { + it->second.second(error); + _pendingRequests.erase(it); + return true; + } + return false; +} } // namespace Stone::Network diff --git a/Engine/Network/test/test_JsonRpcDispatcher.cpp b/Engine/Network/test/test_JsonRpcDispatcher.cpp index 7b1ac6e..de70e72 100644 --- a/Engine/Network/test/test_JsonRpcDispatcher.cpp +++ b/Engine/Network/test/test_JsonRpcDispatcher.cpp @@ -133,3 +133,132 @@ TEST(JsonRpcDispatcher, HandleNotificationUsingParams) { EXPECT_TRUE(dispatcher.handleString(R"({"method": "incValue", "params": [2]})", out)); EXPECT_EQ(zeValue, 2); } + +TEST(JsonRpcDispatcher, SendRequestWithParams) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + Json::Value outJson; + + dispatcher.sendRequest(out, "setValue", Json::number(12), + {[](const Json::Value &result) { (void)result; }, + [](const std::string &error) { + (void)error; + }}); + + ASSERT_NO_THROW(out >> outJson); + ASSERT_TRUE(outJson.is()); + ASSERT_NE(outJson.get().find("id"), outJson.get().end()); + ASSERT_NE(outJson.get().find("method"), outJson.get().end()); + ASSERT_NE(outJson.get().find("params"), outJson.get().end()); + + EXPECT_EQ(outJson.get()["method"], Json::string("setValue")); + EXPECT_EQ(outJson.get()["params"], Json::number(12)); + + int firstId = outJson.get()["id"].get(); + + dispatcher.sendRequest(out, "getValue", Json::null(), + {[](const Json::Value &result) { (void)result; }, + [](const std::string &error) { + (void)error; + }}); + + ASSERT_NO_THROW(out >> outJson); + ASSERT_TRUE(outJson.is()); + ASSERT_NE(outJson.get().find("id"), outJson.get().end()); + ASSERT_NE(outJson.get().find("method"), outJson.get().end()); + + EXPECT_EQ(outJson.get()["method"], Json::string("getValue")); + + int secondId = outJson.get()["id"].get(); + EXPECT_NE(firstId, secondId); +} + +TEST(JsonRpcDispatcher, SendRequestWithParamsAndReceiveResponse) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + Json::Value outJson; + + int zeValue = 0; + std::string receivedError = ""; + + { + EXPECT_TRUE(dispatcher.sendRequest( // + out, "getValue", Json::null(), + {[&zeValue](const Json::Value &result) { + if (result.is()) { + zeValue = result.get(); + } + }, + [&receivedError](const std::string &error) { + receivedError = error; + }})); + + ASSERT_NO_THROW(out >> outJson); + ASSERT_TRUE(outJson.is()); + ASSERT_NE(outJson.get().find("id"), outJson.get().end()); + ASSERT_NE(outJson.get().find("method"), outJson.get().end()); + + ASSERT_TRUE(outJson.get()["id"].is()); + EXPECT_EQ(outJson.get()["method"], Json::string("getValue")); + + int requestId = outJson.get()["id"].get(); + + EXPECT_EQ(zeValue, 0); + + auto response = Json::Object({ + { "id", Json::number(requestId)}, + {"result", Json::number(12)}, + }); + EXPECT_TRUE(dispatcher.handleJsonObject(response, out)); + + EXPECT_EQ(zeValue, 12); + EXPECT_EQ(receivedError, ""); + + zeValue = 0; + + auto secondResponse = Json::Object({ + { "id", Json::number(requestId)}, + {"result", Json::number(17)}, + }); + + EXPECT_FALSE(dispatcher.handleJsonObject(secondResponse, out)); + + EXPECT_NE(zeValue, 17); + } + + { + zeValue = 10; + + EXPECT_TRUE(dispatcher.sendRequest( // + out, "getValue", Json::null(), + {[&zeValue](const Json::Value &result) { + if (result.is()) { + zeValue = result.get(); + } + }, + [&zeValue, &receivedError](const std::string &error) { + // + zeValue = 0; + receivedError = error; + }})); + + ASSERT_NO_THROW(out >> outJson); + ASSERT_TRUE(outJson.is()); + ASSERT_NE(outJson.get().find("id"), outJson.get().end()); + ASSERT_NE(outJson.get().find("method"), outJson.get().end()); + + ASSERT_TRUE(outJson.get()["id"].is()); + EXPECT_EQ(outJson.get()["method"], Json::string("getValue")); + + int requestId = outJson.get()["id"].get(); + + auto response = Json::Object({ + { "id", Json::number(requestId)}, + {"error", Json::string("pas envie cette fois")}, + }); + EXPECT_TRUE(dispatcher.handleJsonObject(response, out)); + + EXPECT_EQ(zeValue, 0); + EXPECT_EQ(receivedError, "pas envie cette fois"); + } +} diff --git a/Engine/Utils/include/Utils/Json.hpp b/Engine/Utils/include/Utils/Json.hpp index f1b094d..c1cce84 100644 --- a/Engine/Utils/include/Utils/Json.hpp +++ b/Engine/Utils/include/Utils/Json.hpp @@ -46,6 +46,14 @@ struct Value { void serialize(std::ostream &stream) const; std::string serialize() const; + + bool operator==(const Value &other) const { + return value == other.value; + } + + bool operator!=(const Value &other) const { + return !(*this == other); + } }; void parseStream(std::istream &input, Value &out); From 438c2f1a3adb26c7ab4d431b4cd04fb9cc1e0a28 Mon Sep 17 00:00:00 2001 From: amasson Date: Mon, 7 Apr 2025 09:44:15 +0200 Subject: [PATCH 08/16] JsonRpcDispatcher type casting warnings --- .../src/Network/Dispatcher/JsonRpcDispatcher.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index 2da1e62..74a52e4 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -69,7 +69,7 @@ bool JsonRpcDispatcher::sendRequest(std::ostream &output, const Method &method, if (_nextId >= MAX_REQUEST_LOOP) _nextId = 1; Json::Value request = Json::object(); - Json::Object &requestObject(request.get()); + auto &requestObject(request.get()); requestObject[JSONRPC_ID] = Json::number(_nextId); requestObject[JSONRPC_METHOD] = Json::string(method); if (!params.isNull()) @@ -122,6 +122,7 @@ bool JsonRpcDispatcher::handleJsonArray(const Json::Array &message, std::ostream bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostream &output) { const auto &idPtr = message.find(JSONRPC_ID); const bool hasId = idPtr != message.end() && idPtr->second.is(); + const Id id = hasId ? static_cast(idPtr->second.get()) : 0; const auto &methodPtr = message.find(JSONRPC_METHOD); const bool hasMethod = methodPtr != message.end() && methodPtr->second.is(); @@ -130,7 +131,7 @@ bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostre const auto ¶ms = message.find(JSONRPC_PARAMS); if (hasId) - return handleRequest(idPtr->second.get(), methodPtr->second.get(), + return handleRequest(id, methodPtr->second.get(), params == message.end() ? Json::null() : params->second, output); else return handleNotification(methodPtr->second.get(), @@ -144,9 +145,9 @@ bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostre const bool hasError = errorPtr != message.end() && errorPtr->second.is(); if (hasResult && !hasError) { - return handleResponseSuccess(idPtr->second.get(), resultPtr->second); + return handleResponseSuccess(id, resultPtr->second); } else if (!hasResult && hasError) { - return handleResponseError(idPtr->second.get(), errorPtr->second.get()); + return handleResponseError(id, errorPtr->second.get()); } } } @@ -160,14 +161,14 @@ bool JsonRpcDispatcher::handleRequest(Id id, const Method &method, const Params params, [&output, id](const Result &result) { Json::Value response = Json::object(); - Json::Object &responseObject(response.get()); + auto &responseObject(response.get()); responseObject[JSONRPC_ID] = Json::number(id); responseObject[JSONRPC_RESULT] = result; output << response; }, [&output, id](const Error &error) { Json::Value response = Json::object(); - Json::Object &responseObject(response.get()); + auto &responseObject(response.get()); responseObject[JSONRPC_ID] = Json::number(id); responseObject[JSONRPC_ERROR] = Json::string(error); output << response; From 64f9d9e362711e48f0ad7097c2cb59e5fb4dbaca Mon Sep 17 00:00:00 2001 From: Arthur Masson Date: Mon, 7 Apr 2025 11:19:11 +0200 Subject: [PATCH 09/16] Add todos in JsonRpcDispatcher.hpp --- .../include/Network/Dispatcher/JsonRpcDispatcher.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp index e7d713e..e3edf75 100644 --- a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -47,9 +47,12 @@ class JsonRpcDispatcher { NotificationSignal &getNotificationSignal(const Method &method); + // TODO: Add timeout in send request bool sendRequest(std::ostream &output, const Method &method, const Params ¶ms, const ResponseCallbacks &callbacks); + // TODO: Add sendNotification method + bool handleString(const std::string &message, std::ostream &output); bool handleStream(std::istream &stream, std::ostream &output); bool handleJsonArray(const Json::Array &message, std::ostream &output); @@ -60,12 +63,16 @@ class JsonRpcDispatcher { bool handleResponseSuccess(Id id, const Result &result); bool handleResponseError(Id id, const Error &error); + // TODO: Add the purge method that will clear out unused notifications signals and timedout requests + private: std::unordered_map _requestHandlers; std::unordered_map> _notificationSignals; + // TODO: define a tuple that contains the response callbacks and the timeout timestamp information Id _nextId = 0; std::unordered_map _pendingRequests; + }; } // namespace Stone::Network From 7787c3b0ec06d06f75a5120b5cc576453c8548e1 Mon Sep 17 00:00:00 2001 From: amasson Date: Mon, 7 Apr 2025 13:40:29 +0200 Subject: [PATCH 10/16] JsonRpcDispatcher request timeout --- .../Network/Dispatcher/JsonRpcDispatcher.hpp | 14 ++++++---- .../Network/Dispatcher/JsonRpcDispatcher.cpp | 27 +++++++++++++++--- .../Network/test/test_JsonRpcDispatcher.cpp | 28 +++++++++++++++++++ 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp index e3edf75..7fc862a 100644 --- a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -47,9 +47,8 @@ class JsonRpcDispatcher { NotificationSignal &getNotificationSignal(const Method &method); - // TODO: Add timeout in send request bool sendRequest(std::ostream &output, const Method &method, const Params ¶ms, - const ResponseCallbacks &callbacks); + const ResponseCallbacks &callbacks, float timeout = 10.0f); // TODO: Add sendNotification method @@ -63,16 +62,19 @@ class JsonRpcDispatcher { bool handleResponseSuccess(Id id, const Result &result); bool handleResponseError(Id id, const Error &error); - // TODO: Add the purge method that will clear out unused notifications signals and timedout requests + void cleanup(); + void cleanupTimedOutPendingRequests(); private: std::unordered_map _requestHandlers; std::unordered_map> _notificationSignals; - // TODO: define a tuple that contains the response callbacks and the timeout timestamp information + struct PendingResponse { + ResponseCallbacks callbacks; + std::chrono::steady_clock::time_point expiration; + }; + std::unordered_map _pendingRequests; Id _nextId = 0; - std::unordered_map _pendingRequests; - }; } // namespace Stone::Network diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index 74a52e4..5d48920 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -64,7 +64,7 @@ JsonRpcDispatcher::NotificationSignal &JsonRpcDispatcher::getNotificationSignal( } bool JsonRpcDispatcher::sendRequest(std::ostream &output, const Method &method, const Params ¶ms, - const ResponseCallbacks &callbacks) { + const ResponseCallbacks &callbacks, float timeout) { _nextId++; if (_nextId >= MAX_REQUEST_LOOP) _nextId = 1; @@ -74,7 +74,10 @@ bool JsonRpcDispatcher::sendRequest(std::ostream &output, const Method &method, requestObject[JSONRPC_METHOD] = Json::string(method); if (!params.isNull()) requestObject[JSONRPC_PARAMS] = params; - _pendingRequests[_nextId] = callbacks; + _pendingRequests[_nextId] = { + callbacks, + std::chrono::steady_clock::now() + std::chrono::milliseconds(static_cast(timeout * 1000)) // + }; output << request; if (output.fail() || output.bad()) { _pendingRequests.erase(_nextId); @@ -196,7 +199,7 @@ bool JsonRpcDispatcher::handleNotification(const Method &method, const Params &p bool JsonRpcDispatcher::handleResponseSuccess(Id id, const Result &result) { auto it = _pendingRequests.find(id); if (it != _pendingRequests.end()) { - it->second.first(result); + it->second.callbacks.first(result); _pendingRequests.erase(it); return true; } @@ -206,11 +209,27 @@ bool JsonRpcDispatcher::handleResponseSuccess(Id id, const Result &result) { bool JsonRpcDispatcher::handleResponseError(Id id, const Error &error) { auto it = _pendingRequests.find(id); if (it != _pendingRequests.end()) { - it->second.second(error); + it->second.callbacks.second(error); _pendingRequests.erase(it); return true; } return false; } +void JsonRpcDispatcher::cleanup() { + cleanupTimedOutPendingRequests(); +} + +void JsonRpcDispatcher::cleanupTimedOutPendingRequests() { + for (auto it = _pendingRequests.begin(); it != _pendingRequests.end();) { + if (it->second.expiration < std::chrono::steady_clock::now()) { + it->second.callbacks.second("request timed out"); + it = _pendingRequests.erase(it); + } else { + ++it; + } + } +} + + } // namespace Stone::Network diff --git a/Engine/Network/test/test_JsonRpcDispatcher.cpp b/Engine/Network/test/test_JsonRpcDispatcher.cpp index de70e72..d4250cc 100644 --- a/Engine/Network/test/test_JsonRpcDispatcher.cpp +++ b/Engine/Network/test/test_JsonRpcDispatcher.cpp @@ -262,3 +262,31 @@ TEST(JsonRpcDispatcher, SendRequestWithParamsAndReceiveResponse) { EXPECT_EQ(receivedError, "pas envie cette fois"); } } + +TEST(JsonRpcDispatcher, HandleRequestWithTimeout) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + + bool errorReceived = false; + + dispatcher.sendRequest( // + out, "getValue", Json::null(), + {[](const Json::Value &result) { (void)result; }, + [&errorReceived](const std::string &error) { + (void)error; + errorReceived = true; + }}, + 0.005f); + // Timeout after 5ms + + dispatcher.cleanupTimedOutPendingRequests(); + EXPECT_FALSE(errorReceived); + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + dispatcher.cleanupTimedOutPendingRequests(); + EXPECT_FALSE(errorReceived); + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + dispatcher.cleanupTimedOutPendingRequests(); + EXPECT_TRUE(errorReceived); +} From 44a082e5f4f020da12873b358b4e6d9b8853c2fe Mon Sep 17 00:00:00 2001 From: amasson Date: Mon, 7 Apr 2025 13:54:56 +0200 Subject: [PATCH 11/16] JsonRpcDispatcher cleanup unused notifications --- .../Network/Dispatcher/JsonRpcDispatcher.hpp | 3 +++ .../Network/Dispatcher/JsonRpcDispatcher.cpp | 15 +++++++++++++++ Engine/Network/test/test_JsonRpcDispatcher.cpp | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp index 7fc862a..c8ca2cc 100644 --- a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -47,6 +47,8 @@ class JsonRpcDispatcher { NotificationSignal &getNotificationSignal(const Method &method); + bool hasNotificationSignal(const Method &method) const; + bool sendRequest(std::ostream &output, const Method &method, const Params ¶ms, const ResponseCallbacks &callbacks, float timeout = 10.0f); @@ -64,6 +66,7 @@ class JsonRpcDispatcher { void cleanup(); void cleanupTimedOutPendingRequests(); + void cleanupEmptyNotificationSignals(); private: std::unordered_map _requestHandlers; diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index 5d48920..c42c9bf 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -63,6 +63,10 @@ JsonRpcDispatcher::NotificationSignal &JsonRpcDispatcher::getNotificationSignal( return *it.first->second; } +bool JsonRpcDispatcher::hasNotificationSignal(const Method &method) const { + return _notificationSignals.find(method) != _notificationSignals.end(); +} + bool JsonRpcDispatcher::sendRequest(std::ostream &output, const Method &method, const Params ¶ms, const ResponseCallbacks &callbacks, float timeout) { _nextId++; @@ -218,6 +222,7 @@ bool JsonRpcDispatcher::handleResponseError(Id id, const Error &error) { void JsonRpcDispatcher::cleanup() { cleanupTimedOutPendingRequests(); + cleanupEmptyNotificationSignals(); } void JsonRpcDispatcher::cleanupTimedOutPendingRequests() { @@ -231,5 +236,15 @@ void JsonRpcDispatcher::cleanupTimedOutPendingRequests() { } } +void JsonRpcDispatcher::cleanupEmptyNotificationSignals() { + for (auto it = _notificationSignals.begin(); it != _notificationSignals.end();) { + if (!it->second->isBound()) { + it = _notificationSignals.erase(it); + } else { + ++it; + } + } +} + } // namespace Stone::Network diff --git a/Engine/Network/test/test_JsonRpcDispatcher.cpp b/Engine/Network/test/test_JsonRpcDispatcher.cpp index d4250cc..2db9086 100644 --- a/Engine/Network/test/test_JsonRpcDispatcher.cpp +++ b/Engine/Network/test/test_JsonRpcDispatcher.cpp @@ -134,6 +134,23 @@ TEST(JsonRpcDispatcher, HandleNotificationUsingParams) { EXPECT_EQ(zeValue, 2); } +TEST(JsonRpcDispatcher, CleanupNotificationSignal) { + JsonRpcDispatcher dispatcher; + + { + Stone::Slot incSlot([](const Json::Value &) {}); + + dispatcher.getNotificationSignal("incValue").bind(incSlot); + + EXPECT_TRUE(dispatcher.hasNotificationSignal("incValue")); + dispatcher.cleanupEmptyNotificationSignals(); + EXPECT_TRUE(dispatcher.hasNotificationSignal("incValue")); + } + + dispatcher.cleanupEmptyNotificationSignals(); + EXPECT_FALSE(dispatcher.hasNotificationSignal("incValue")); +} + TEST(JsonRpcDispatcher, SendRequestWithParams) { JsonRpcDispatcher dispatcher; std::stringstream out; From 5bd25ee8193376250532a3c576ae354325d2a4bb Mon Sep 17 00:00:00 2001 From: amasson Date: Tue, 8 Apr 2025 09:08:22 +0200 Subject: [PATCH 12/16] Json direct index/key getter from value --- Engine/Utils/include/Utils/Json.hpp | 12 +++---- Engine/Utils/src/Utils/Json.cpp | 24 ++++++++++++++ Engine/Utils/test/test_Json.cpp | 49 +++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/Engine/Utils/include/Utils/Json.hpp b/Engine/Utils/include/Utils/Json.hpp index c1cce84..c0b6ee6 100644 --- a/Engine/Utils/include/Utils/Json.hpp +++ b/Engine/Utils/include/Utils/Json.hpp @@ -47,13 +47,13 @@ struct Value { void serialize(std::ostream &stream) const; std::string serialize() const; - bool operator==(const Value &other) const { - return value == other.value; - } + bool operator==(const Value &other) const; + bool operator!=(const Value &other) const; - bool operator!=(const Value &other) const { - return !(*this == other); - } + Value &operator[](int index); + const Value &operator[](int index) const; + Value &operator[](const std::string &key); + const Value &operator[](const std::string &key) const; }; void parseStream(std::istream &input, Value &out); diff --git a/Engine/Utils/src/Utils/Json.cpp b/Engine/Utils/src/Utils/Json.cpp index b9d2573..9a1972a 100644 --- a/Engine/Utils/src/Utils/Json.cpp +++ b/Engine/Utils/src/Utils/Json.cpp @@ -18,6 +18,30 @@ std::string Value::serialize() const { return ss.str(); } +bool Value::operator==(const Value &other) const { + return value == other.value; +} + +bool Value::operator!=(const Value &other) const { + return !(*this == other); +} + +Value &Value::operator[](int index) { + return get()[index]; +} + +const Value &Value::operator[](int index) const { + return get()[index]; +} + +Value &Value::operator[](const std::string &key) { + return get()[key]; +} + +const Value &Value::operator[](const std::string &key) const { + return get().find(key)->second; +} + void parseStream(std::istream &input, Value &out) { Parser parser(input); parser.parse(out); diff --git a/Engine/Utils/test/test_Json.cpp b/Engine/Utils/test/test_Json.cpp index 6dc98dc..c4e9fdd 100644 --- a/Engine/Utils/test/test_Json.cpp +++ b/Engine/Utils/test/test_Json.cpp @@ -2,6 +2,55 @@ #include +TEST(Json, GetContent) { + std::string jsonString = R"({"name": "John", "age": 30, "isStudent": false})"; + + Json::Value json = Json::object({ + { "name", Json::string("John")}, + { "age", Json::number(30)}, + {"isStudent", Json::boolean(false)}, + { "scores", Json::array({Json::number(85.5), Json::number(92.0), Json::number(78.5)})}, + { "address", Json::object({{"city", Json::string("New York")}, {"zip", Json::string("10001")}})}, + }); + + ASSERT_TRUE(json.is()); + + Json::Object &obj = json.get(); + ASSERT_TRUE(obj["name"].is()); + EXPECT_EQ(obj["name"].get(), "John"); + + ASSERT_TRUE(obj["age"].is()); + EXPECT_EQ(obj["age"].get(), 30); + + ASSERT_TRUE(obj["isStudent"].is()); + EXPECT_EQ(obj["isStudent"].get(), false); + + ASSERT_TRUE(obj["scores"].is()); + auto scores = obj["scores"].get(); + ASSERT_EQ(scores.size(), 3); + ASSERT_TRUE(scores[0].is()); + ASSERT_EQ(scores[0].get(), 85.5); + ASSERT_TRUE(scores[1].is()); + ASSERT_EQ(scores[1].get(), 92.0); + ASSERT_TRUE(scores[2].is()); + ASSERT_EQ(scores[2].get(), 78.5); + + ASSERT_TRUE(obj["address"].is()); + auto address = obj["address"].get(); + ASSERT_TRUE(address["city"].is()); + ASSERT_EQ(address["city"].get(), "New York"); + ASSERT_TRUE(address["zip"].is()); + ASSERT_EQ(address["zip"].get(), "10001"); + + const Json::Value constJson = json; + ASSERT_TRUE(constJson.is()); + ASSERT_TRUE(constJson["name"].is()); + EXPECT_EQ(constJson["name"].get(), "John"); + + EXPECT_EQ(constJson["address"]["city"].get(), "New York"); + ASSERT_EQ(constJson["scores"][1].get(), 92.0); +} + TEST(Json, ParseEmptyObject) { std::string jsonString = "{}"; From e8775e6d5c11f1fb2043f949d8c0b70a8f8defab Mon Sep 17 00:00:00 2001 From: amasson Date: Tue, 8 Apr 2025 09:09:19 +0200 Subject: [PATCH 13/16] JsonRpcDispatcher send notifications --- .../Network/Dispatcher/JsonRpcDispatcher.hpp | 2 +- .../src/Network/Dispatcher/JsonRpcDispatcher.cpp | 10 +++++++++- Engine/Network/test/test_JsonRpcDispatcher.cpp | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp index c8ca2cc..bd0fbe8 100644 --- a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -52,7 +52,7 @@ class JsonRpcDispatcher { bool sendRequest(std::ostream &output, const Method &method, const Params ¶ms, const ResponseCallbacks &callbacks, float timeout = 10.0f); - // TODO: Add sendNotification method + bool sendNotification(std::ostream &output, const Method &method, const Params ¶ms); bool handleString(const std::string &message, std::ostream &output); bool handleStream(std::istream &stream, std::ostream &output); diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index c42c9bf..36bf6a8 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -84,12 +84,20 @@ bool JsonRpcDispatcher::sendRequest(std::ostream &output, const Method &method, }; output << request; if (output.fail() || output.bad()) { - _pendingRequests.erase(_nextId); + handleResponseError(_nextId, "failed to send request"); return false; } return true; } +bool JsonRpcDispatcher::sendNotification(std::ostream &output, const Method &method, const Params ¶ms) { + output << Json::Object({ + {JSONRPC_METHOD, Json::string(method)}, + {JSONRPC_PARAMS, params}, + }); + return !(output.fail() || output.bad()); +} + bool JsonRpcDispatcher::handleString(const std::string &message, std::ostream &output) { std::stringstream stream(message); return handleStream(stream, output); diff --git a/Engine/Network/test/test_JsonRpcDispatcher.cpp b/Engine/Network/test/test_JsonRpcDispatcher.cpp index 2db9086..3a09c5c 100644 --- a/Engine/Network/test/test_JsonRpcDispatcher.cpp +++ b/Engine/Network/test/test_JsonRpcDispatcher.cpp @@ -307,3 +307,19 @@ TEST(JsonRpcDispatcher, HandleRequestWithTimeout) { dispatcher.cleanupTimedOutPendingRequests(); EXPECT_TRUE(errorReceived); } + +TEST(JsonRpcDispatcher, SendNotification) { + JsonRpcDispatcher dispatcher; + std::stringstream out; + Json::Value outJson; + + EXPECT_TRUE(dispatcher.sendNotification(out, "sayHello", Json::array({Json::number(12), Json::string("world")}))); + + EXPECT_NO_THROW(out >> outJson); + ASSERT_TRUE(outJson.is()); + ASSERT_EQ(outJson["method"], Json::string("sayHello")); + ASSERT_TRUE(outJson["params"].is()); + ASSERT_EQ(outJson["params"].get().size(), 2); + EXPECT_EQ(outJson["params"][0], Json::number(12)); + EXPECT_EQ(outJson["params"][1], Json::string("world")); +} From f24ee7666ba93a30c00ce0d8608a25e1fc8823d4 Mon Sep 17 00:00:00 2001 From: amasson Date: Tue, 8 Apr 2025 19:01:34 +0200 Subject: [PATCH 14/16] JsonRpcDispatcher define errors --- .../Network/Dispatcher/JsonRpcDispatcher.hpp | 2 + .../Network/Dispatcher/JsonRpcDispatcher.cpp | 76 ++++++++++++------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp index bd0fbe8..acfc864 100644 --- a/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp +++ b/Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp @@ -78,6 +78,8 @@ class JsonRpcDispatcher { }; std::unordered_map _pendingRequests; Id _nextId = 0; + + bool _sendErrorMessage = false; }; } // namespace Stone::Network diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index 36bf6a8..199b0a1 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -5,6 +5,10 @@ #include #define MAX_REQUEST_LOOP 1000000000 +#define ERROR_HANDLER_THROWN "Unknown error occurred" +#define ERROR_FAILED_TO_SEND "Failed to send request" +#define ERROR_INVALID_JSON "Failed to parse JSON" +#define ERROR_INVALID_MESSAGE "Invalid JSON-RPC message format" namespace Stone::Network { @@ -18,7 +22,7 @@ bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const SyncR } catch (const std::exception &e) { failure(e.what()); } catch (...) { - failure("Unknown error occurred"); + failure(ERROR_HANDLER_THROWN); } }); } @@ -37,7 +41,7 @@ bool JsonRpcDispatcher::registerRequestHandler(const Method &method, const Async } catch (const std::exception &e) { failure(e.what()); } catch (...) { - failure("Unknown error occurred"); + failure(ERROR_HANDLER_THROWN); } }); } @@ -72,26 +76,26 @@ bool JsonRpcDispatcher::sendRequest(std::ostream &output, const Method &method, _nextId++; if (_nextId >= MAX_REQUEST_LOOP) _nextId = 1; - Json::Value request = Json::object(); - auto &requestObject(request.get()); - requestObject[JSONRPC_ID] = Json::number(_nextId); - requestObject[JSONRPC_METHOD] = Json::string(method); + Json::Value request = Json::object({ + { JSONRPC_ID, Json::number(_nextId)}, + {JSONRPC_METHOD, Json::string(method)}, + }); if (!params.isNull()) - requestObject[JSONRPC_PARAMS] = params; + request[JSONRPC_PARAMS] = params; _pendingRequests[_nextId] = { callbacks, std::chrono::steady_clock::now() + std::chrono::milliseconds(static_cast(timeout * 1000)) // }; output << request; if (output.fail() || output.bad()) { - handleResponseError(_nextId, "failed to send request"); + handleResponseError(_nextId, ERROR_FAILED_TO_SEND); return false; } return true; } bool JsonRpcDispatcher::sendNotification(std::ostream &output, const Method &method, const Params ¶ms) { - output << Json::Object({ + output << Json::object({ {JSONRPC_METHOD, Json::string(method)}, {JSONRPC_PARAMS, params}, }); @@ -108,17 +112,27 @@ bool JsonRpcDispatcher::handleStream(std::istream &stream, std::ostream &output) try { Json::parseStream(stream, jsonValue); } catch (const std::exception &e) { - output << "Failed to parse JSON: " << e.what() << std::endl; + if (_sendErrorMessage) { + output << Json::object({ + { JSONRPC_ERROR, ERROR_INVALID_JSON}, + {JSONRPC_PARAMS, Json::string(e.what())}, + }); + } return false; } if (jsonValue.is()) { - handleJsonObject(jsonValue.get(), output); - return true; + return handleJsonObject(jsonValue.get(), output); } else if (jsonValue.is()) { - handleJsonArray(jsonValue.get(), output); - return true; + return handleJsonArray(jsonValue.get(), output); } - output << "Invalid JSON-RPC message format" << std::endl; + + if (_sendErrorMessage) { + output << Json::object({ + { JSONRPC_ERROR, ERROR_INVALID_MESSAGE}, + {JSONRPC_PARAMS, jsonValue}, + }); + } + return false; } @@ -127,8 +141,12 @@ bool JsonRpcDispatcher::handleJsonArray(const Json::Array &message, std::ostream if (item.is()) { handleJsonObject(item.get(), output); } else { - output << "Invalid JSON-RPC message format" << std::endl; - return false; + if (_sendErrorMessage) { + output << Json::object({ + { JSONRPC_ERROR, ERROR_INVALID_MESSAGE}, + {JSONRPC_PARAMS, item}, + }); + } } } return true; @@ -164,6 +182,12 @@ bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostre } else if (!hasResult && hasError) { return handleResponseError(id, errorPtr->second.get()); } + if (_sendErrorMessage) { + output << Json::object({ + { JSONRPC_ERROR, ERROR_INVALID_MESSAGE}, + {JSONRPC_PARAMS, message}, + }); + } } } return false; @@ -175,18 +199,16 @@ bool JsonRpcDispatcher::handleRequest(Id id, const Method &method, const Params it->second( params, [&output, id](const Result &result) { - Json::Value response = Json::object(); - auto &responseObject(response.get()); - responseObject[JSONRPC_ID] = Json::number(id); - responseObject[JSONRPC_RESULT] = result; - output << response; + output << Json::object({ + { JSONRPC_ID, Json::number(id)}, + {JSONRPC_RESULT, result}, + }); }, [&output, id](const Error &error) { - Json::Value response = Json::object(); - auto &responseObject(response.get()); - responseObject[JSONRPC_ID] = Json::number(id); - responseObject[JSONRPC_ERROR] = Json::string(error); - output << response; + output << Json::object({ + { JSONRPC_ID, Json::number(id)}, + {JSONRPC_ERROR, error}, + }); }); return true; } From b35359a299b58875fca7d71cd5cd6abfb4e75ca7 Mon Sep 17 00:00:00 2001 From: amasson Date: Tue, 8 Apr 2025 23:51:19 +0200 Subject: [PATCH 15/16] Json using primitive types --- Engine/Utils/include/Utils/Json.hpp | 24 +++-- Engine/Utils/src/Utils/Json.cpp | 14 +-- Engine/Utils/test/test_Json.cpp | 152 ++++++++++++++-------------- 3 files changed, 97 insertions(+), 93 deletions(-) diff --git a/Engine/Utils/include/Utils/Json.hpp b/Engine/Utils/include/Utils/Json.hpp index c0b6ee6..d6b4c7b 100644 --- a/Engine/Utils/include/Utils/Json.hpp +++ b/Engine/Utils/include/Utils/Json.hpp @@ -12,16 +12,20 @@ struct Value; using Object = std::unordered_map; using Array = std::vector; +using String = std::string; +using Number = double; +using Boolean = bool; +using Null = std::nullptr_t; struct Value { - std::variant value; + std::variant value; Value() : value(nullptr) { } - template , T>>> + template , T>>> Value(T &&val) : value(std::forward(val)) { } @@ -62,9 +66,9 @@ void parseFile(const std::string &path, Value &out); Value object(const Object &obj = {}); Value array(const Array &arr = {}); -Value string(const std::string &str = ""); -Value number(double num = 0.0); -Value boolean(bool b = false); +Value string(const String &str = ""); +Value number(Number num = 0.0); +Value boolean(Boolean b = false); Value null(); @@ -134,10 +138,10 @@ class Serializer { void operator()(const Object &obj); void operator()(const Array &arr); - void operator()(const std::string &str); - void operator()(double num); - void operator()(bool b); - void operator()(std::nullptr_t); + void operator()(const String &str); + void operator()(Number num); + void operator()(Boolean b); + void operator()(Null); private: std::ostream &_stream; diff --git a/Engine/Utils/src/Utils/Json.cpp b/Engine/Utils/src/Utils/Json.cpp index 9a1972a..0f4b91a 100644 --- a/Engine/Utils/src/Utils/Json.cpp +++ b/Engine/Utils/src/Utils/Json.cpp @@ -68,15 +68,15 @@ Value array(const Array &arr) { return {arr}; } -Value string(const std::string &str) { +Value string(const String &str) { return {str}; } -Value number(double num) { +Value number(Number num) { return {num}; } -Value boolean(bool b) { +Value boolean(Boolean b) { return {b}; } @@ -302,19 +302,19 @@ static std::string _escape_string(const std::string &str) { return result; } -void Serializer::operator()(const std::string &str) { +void Serializer::operator()(const String &str) { _stream << "\"" << _escape_string(str) << "\""; } -void Serializer::operator()(double num) { +void Serializer::operator()(Number num) { _stream << num; } -void Serializer::operator()(bool b) { +void Serializer::operator()(Boolean b) { _stream << (b ? "true" : "false"); } -void Serializer::operator()(std::nullptr_t) { +void Serializer::operator()(Null) { _stream << "null"; } diff --git a/Engine/Utils/test/test_Json.cpp b/Engine/Utils/test/test_Json.cpp index c4e9fdd..df3b679 100644 --- a/Engine/Utils/test/test_Json.cpp +++ b/Engine/Utils/test/test_Json.cpp @@ -16,39 +16,39 @@ TEST(Json, GetContent) { ASSERT_TRUE(json.is()); Json::Object &obj = json.get(); - ASSERT_TRUE(obj["name"].is()); - EXPECT_EQ(obj["name"].get(), "John"); + ASSERT_TRUE(obj["name"].is()); + EXPECT_EQ(obj["name"].get(), "John"); - ASSERT_TRUE(obj["age"].is()); - EXPECT_EQ(obj["age"].get(), 30); + ASSERT_TRUE(obj["age"].is()); + EXPECT_EQ(obj["age"].get(), 30); - ASSERT_TRUE(obj["isStudent"].is()); - EXPECT_EQ(obj["isStudent"].get(), false); + ASSERT_TRUE(obj["isStudent"].is()); + EXPECT_EQ(obj["isStudent"].get(), false); ASSERT_TRUE(obj["scores"].is()); auto scores = obj["scores"].get(); ASSERT_EQ(scores.size(), 3); - ASSERT_TRUE(scores[0].is()); - ASSERT_EQ(scores[0].get(), 85.5); - ASSERT_TRUE(scores[1].is()); - ASSERT_EQ(scores[1].get(), 92.0); - ASSERT_TRUE(scores[2].is()); - ASSERT_EQ(scores[2].get(), 78.5); + ASSERT_TRUE(scores[0].is()); + ASSERT_EQ(scores[0].get(), 85.5); + ASSERT_TRUE(scores[1].is()); + ASSERT_EQ(scores[1].get(), 92.0); + ASSERT_TRUE(scores[2].is()); + ASSERT_EQ(scores[2].get(), 78.5); ASSERT_TRUE(obj["address"].is()); auto address = obj["address"].get(); - ASSERT_TRUE(address["city"].is()); - ASSERT_EQ(address["city"].get(), "New York"); - ASSERT_TRUE(address["zip"].is()); - ASSERT_EQ(address["zip"].get(), "10001"); + ASSERT_TRUE(address["city"].is()); + ASSERT_EQ(address["city"].get(), "New York"); + ASSERT_TRUE(address["zip"].is()); + ASSERT_EQ(address["zip"].get(), "10001"); const Json::Value constJson = json; ASSERT_TRUE(constJson.is()); - ASSERT_TRUE(constJson["name"].is()); - EXPECT_EQ(constJson["name"].get(), "John"); + ASSERT_TRUE(constJson["name"].is()); + EXPECT_EQ(constJson["name"].get(), "John"); - EXPECT_EQ(constJson["address"]["city"].get(), "New York"); - ASSERT_EQ(constJson["scores"][1].get(), 92.0); + EXPECT_EQ(constJson["address"]["city"].get(), "New York"); + ASSERT_EQ(constJson["scores"][1].get(), 92.0); } TEST(Json, ParseEmptyObject) { @@ -70,14 +70,14 @@ TEST(Json, ParseSimpleObject) { ASSERT_TRUE(json.is()); Json::Object obj = json.get(); - ASSERT_TRUE(obj["name"].is()); - ASSERT_EQ(obj["name"].get(), "John"); + ASSERT_TRUE(obj["name"].is()); + ASSERT_EQ(obj["name"].get(), "John"); - ASSERT_TRUE(obj["age"].is()); - ASSERT_EQ(obj["age"].get(), 30); + ASSERT_TRUE(obj["age"].is()); + ASSERT_EQ(obj["age"].get(), 30); - ASSERT_TRUE(obj["isStudent"].is()); - ASSERT_EQ(obj["isStudent"].get(), false); + ASSERT_TRUE(obj["isStudent"].is()); + ASSERT_EQ(obj["isStudent"].get(), false); } TEST(Json, ParseArray) { @@ -91,14 +91,14 @@ TEST(Json, ParseArray) { Json::Array arr = json.get(); ASSERT_EQ(arr.size(), 4); - ASSERT_TRUE(arr[0].is()); - ASSERT_EQ(arr[0].get(), 1); + ASSERT_TRUE(arr[0].is()); + ASSERT_EQ(arr[0].get(), 1); - ASSERT_TRUE(arr[1].is()); - ASSERT_EQ(arr[1].get(), "two"); + ASSERT_TRUE(arr[1].is()); + ASSERT_EQ(arr[1].get(), "two"); - ASSERT_TRUE(arr[2].is()); - ASSERT_EQ(arr[2].get(), true); + ASSERT_TRUE(arr[2].is()); + ASSERT_EQ(arr[2].get(), true); ASSERT_TRUE(arr[3].isNull()); } @@ -115,11 +115,11 @@ TEST(Json, ParseNestedObject) { ASSERT_TRUE(obj["person"].is()); Json::Object personObj = obj["person"].get(); - ASSERT_TRUE(personObj["name"].is()); - ASSERT_EQ(personObj["name"].get(), "John"); + ASSERT_TRUE(personObj["name"].is()); + ASSERT_EQ(personObj["name"].get(), "John"); - ASSERT_TRUE(personObj["age"].is()); - ASSERT_EQ(personObj["age"].get(), 30); + ASSERT_TRUE(personObj["age"].is()); + ASSERT_EQ(personObj["age"].get(), 30); } TEST(Json, MalformedJsonThrowsException) { @@ -179,14 +179,14 @@ TEST(JsonSerializer, SerializeSimpleObject) { auto obj = json.get(); - ASSERT_TRUE(obj["name"].is()); - ASSERT_EQ(obj["name"].get(), "John"); + ASSERT_TRUE(obj["name"].is()); + ASSERT_EQ(obj["name"].get(), "John"); - ASSERT_TRUE(obj["age"].is()); - ASSERT_EQ(obj["age"].get(), 30); + ASSERT_TRUE(obj["age"].is()); + ASSERT_EQ(obj["age"].get(), 30); - ASSERT_TRUE(obj["isStudent"].is()); - ASSERT_EQ(obj["isStudent"].get(), false); + ASSERT_TRUE(obj["isStudent"].is()); + ASSERT_EQ(obj["isStudent"].get(), false); } TEST(JsonSerializer, SerializeArray) { @@ -207,14 +207,14 @@ TEST(JsonSerializer, SerializeArray) { auto obj = json.get(); ASSERT_EQ(obj.size(), 4); - ASSERT_TRUE(obj[0].is()); - ASSERT_EQ(obj[0].get(), 1); + ASSERT_TRUE(obj[0].is()); + ASSERT_EQ(obj[0].get(), 1); - ASSERT_TRUE(obj[1].is()); - ASSERT_EQ(obj[1].get(), "two"); + ASSERT_TRUE(obj[1].is()); + ASSERT_EQ(obj[1].get(), "two"); - ASSERT_TRUE(obj[2].is()); - ASSERT_EQ(obj[2].get(), true); + ASSERT_TRUE(obj[2].is()); + ASSERT_EQ(obj[2].get(), true); ASSERT_TRUE(obj[3].isNull()); } @@ -242,11 +242,11 @@ TEST(JsonSerializer, SerializeNestedObject) { auto personObj = obj["person"].get(); - ASSERT_TRUE(personObj["name"].is()); - ASSERT_EQ(personObj["name"].get(), "John"); + ASSERT_TRUE(personObj["name"].is()); + ASSERT_EQ(personObj["name"].get(), "John"); - ASSERT_TRUE(personObj["age"].is()); - ASSERT_EQ(personObj["age"].get(), 30); + ASSERT_TRUE(personObj["age"].is()); + ASSERT_EQ(personObj["age"].get(), 30); } TEST(JsonSerializer, SerializeComplexObject) { @@ -275,26 +275,26 @@ TEST(JsonSerializer, SerializeComplexObject) { ASSERT_TRUE(json.is()); auto obj = json.get(); - ASSERT_TRUE(obj["name"].is()); - ASSERT_EQ(obj["name"].get(), "John"); + ASSERT_TRUE(obj["name"].is()); + ASSERT_EQ(obj["name"].get(), "John"); - ASSERT_TRUE(obj["age"].is()); - ASSERT_EQ(obj["age"].get(), 30); + ASSERT_TRUE(obj["age"].is()); + ASSERT_EQ(obj["age"].get(), 30); - ASSERT_TRUE(obj["isStudent"].is()); - ASSERT_EQ(obj["isStudent"].get(), false); + ASSERT_TRUE(obj["isStudent"].is()); + ASSERT_EQ(obj["isStudent"].get(), false); ASSERT_TRUE(obj["scores"].is()); auto scores = obj["scores"].get(); ASSERT_EQ(scores.size(), 3); - ASSERT_EQ(scores[0].get(), 85.5); - ASSERT_EQ(scores[1].get(), 92); - ASSERT_EQ(scores[2].get(), 78.5); + ASSERT_EQ(scores[0].get(), 85.5); + ASSERT_EQ(scores[1].get(), 92); + ASSERT_EQ(scores[2].get(), 78.5); ASSERT_TRUE(obj["address"].is()); auto address = obj["address"].get(); - ASSERT_EQ(address["city"].get(), "New York"); - ASSERT_EQ(address["zip"].get(), "10001"); + ASSERT_EQ(address["city"].get(), "New York"); + ASSERT_EQ(address["zip"].get(), "10001"); } TEST(JsonSerializer, SerializeStringWithSpecialCharacters) { @@ -317,14 +317,14 @@ TEST(JsonSerializer, SerializeStringWithSpecialCharacters) { ASSERT_TRUE(json.is()); auto obj = json.get(); - ASSERT_TRUE(obj["name"].is()); - ASSERT_EQ(obj["name"].get(), "John \"Doe\""); + ASSERT_TRUE(obj["name"].is()); + ASSERT_EQ(obj["name"].get(), "John \"Doe\""); - ASSERT_TRUE(obj["city"].is()); - ASSERT_EQ(obj["city"].get(), "New\nYork"); + ASSERT_TRUE(obj["city"].is()); + ASSERT_EQ(obj["city"].get(), "New\nYork"); - ASSERT_TRUE(obj["zip"].is()); - ASSERT_EQ(obj["zip"].get(), "1000\b1"); + ASSERT_TRUE(obj["zip"].is()); + ASSERT_EQ(obj["zip"].get(), "1000\b1"); } TEST(JsonLexer, NextToken) { @@ -431,8 +431,8 @@ TEST(JsonParser, ParseJsonFromStream) { EXPECT_TRUE(json.is()); Json::Object &obj = json.get(); - EXPECT_TRUE(obj["name"].is()); - EXPECT_EQ(obj["name"].get(), "John"); + EXPECT_TRUE(obj["name"].is()); + EXPECT_EQ(obj["name"].get(), "John"); } { @@ -455,9 +455,9 @@ TEST(JsonParser, ParseJsonFromStream) { Json::Array &arr = json.get(); ASSERT_EQ(arr.size(), 3); - ASSERT_EQ(arr[0].get(), 1); - ASSERT_EQ(arr[1].get(), 2); - ASSERT_EQ(arr[2].get(), 3); + ASSERT_EQ(arr[0].get(), 1); + ASSERT_EQ(arr[1].get(), 2); + ASSERT_EQ(arr[2].get(), 3); } { @@ -467,7 +467,7 @@ TEST(JsonParser, ParseJsonFromStream) { EXPECT_TRUE(json.is()); Json::Object obj = json.get(); - EXPECT_TRUE(obj["name"].is()); - EXPECT_EQ(obj["name"].get(), "Eric"); + EXPECT_TRUE(obj["name"].is()); + EXPECT_EQ(obj["name"].get(), "Eric"); } } From 45036dc453a29bd81a2ae73a7bd5917517c1e836 Mon Sep 17 00:00:00 2001 From: amasson Date: Wed, 9 Apr 2025 00:08:49 +0200 Subject: [PATCH 16/16] JsonRpcDispatcher use json named types --- .../Network/Dispatcher/JsonRpcDispatcher.cpp | 14 ++++---- .../Network/test/test_JsonRpcDispatcher.cpp | 34 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp index 199b0a1..1e8c427 100644 --- a/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp +++ b/Engine/Network/src/Network/Dispatcher/JsonRpcDispatcher.cpp @@ -154,20 +154,20 @@ bool JsonRpcDispatcher::handleJsonArray(const Json::Array &message, std::ostream bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostream &output) { const auto &idPtr = message.find(JSONRPC_ID); - const bool hasId = idPtr != message.end() && idPtr->second.is(); - const Id id = hasId ? static_cast(idPtr->second.get()) : 0; + const bool hasId = idPtr != message.end() && idPtr->second.is(); + const Id id = hasId ? static_cast(idPtr->second.get()) : 0; const auto &methodPtr = message.find(JSONRPC_METHOD); - const bool hasMethod = methodPtr != message.end() && methodPtr->second.is(); + const bool hasMethod = methodPtr != message.end() && methodPtr->second.is(); if (hasMethod) { const auto ¶ms = message.find(JSONRPC_PARAMS); if (hasId) - return handleRequest(id, methodPtr->second.get(), + return handleRequest(id, methodPtr->second.get(), params == message.end() ? Json::null() : params->second, output); else - return handleNotification(methodPtr->second.get(), + return handleNotification(methodPtr->second.get(), params == message.end() ? Json::null() : params->second); } else { if (hasId) { @@ -175,12 +175,12 @@ bool JsonRpcDispatcher::handleJsonObject(const Json::Object &message, std::ostre const bool hasResult = resultPtr != message.end() && !resultPtr->second.isNull(); const auto &errorPtr = message.find(JSONRPC_ERROR); - const bool hasError = errorPtr != message.end() && errorPtr->second.is(); + const bool hasError = errorPtr != message.end() && errorPtr->second.is(); if (hasResult && !hasError) { return handleResponseSuccess(id, resultPtr->second); } else if (!hasResult && hasError) { - return handleResponseError(id, errorPtr->second.get()); + return handleResponseError(id, errorPtr->second.get()); } if (_sendErrorMessage) { output << Json::object({ diff --git a/Engine/Network/test/test_JsonRpcDispatcher.cpp b/Engine/Network/test/test_JsonRpcDispatcher.cpp index 3a09c5c..5c0df9b 100644 --- a/Engine/Network/test/test_JsonRpcDispatcher.cpp +++ b/Engine/Network/test/test_JsonRpcDispatcher.cpp @@ -51,9 +51,9 @@ TEST(JsonRpcDispatcher, HandleRequestUsingNumberParam) { int zeValue = 0; dispatcher.registerRequestHandler("incValue", [&zeValue](const Json::Value ¶ms) { - if (!params.is()) + if (!params.is()) throw std::runtime_error("invalid params type"); - zeValue += params.get(); + zeValue += params.get(); return Json::number(zeValue); }); @@ -64,7 +64,7 @@ TEST(JsonRpcDispatcher, HandleRequestUsingNumberParam) { EXPECT_STRNE(out.str().c_str(), ""); ASSERT_NO_THROW(out >> outJson); ASSERT_TRUE(outJson.is()); - EXPECT_STREQ(outJson.get()["error"].get().c_str(), "invalid params type"); + EXPECT_STREQ(outJson.get()["error"].get().c_str(), "invalid params type"); out = std::stringstream(); EXPECT_STREQ(out.str().c_str(), ""); @@ -77,8 +77,8 @@ TEST(JsonRpcDispatcher, HandleRequestUsingNumberParam) { EXPECT_STRNE(out.str().c_str(), ""); ASSERT_NO_THROW(out >> outJson); ASSERT_TRUE(outJson.is()); - EXPECT_EQ(outJson.get()["id"].get(), 1); - EXPECT_EQ(outJson.get()["result"].get(), 18); + EXPECT_EQ(outJson.get()["id"].get(), 1); + EXPECT_EQ(outJson.get()["result"].get(), 18); EXPECT_EQ(outJson.get().find("error"), outJson.get().end()); } @@ -114,9 +114,9 @@ TEST(JsonRpcDispatcher, HandleNotificationUsingParams) { int zeValue = 0; auto incValue = [&zeValue](const Json::Value ¶ms) { - if (!params.is()) + if (!params.is()) return; - zeValue += params.get(); + zeValue += params.get(); }; Stone::Slot incSlot(incValue); @@ -171,7 +171,7 @@ TEST(JsonRpcDispatcher, SendRequestWithParams) { EXPECT_EQ(outJson.get()["method"], Json::string("setValue")); EXPECT_EQ(outJson.get()["params"], Json::number(12)); - int firstId = outJson.get()["id"].get(); + int firstId = outJson.get()["id"].get(); dispatcher.sendRequest(out, "getValue", Json::null(), {[](const Json::Value &result) { (void)result; }, @@ -186,7 +186,7 @@ TEST(JsonRpcDispatcher, SendRequestWithParams) { EXPECT_EQ(outJson.get()["method"], Json::string("getValue")); - int secondId = outJson.get()["id"].get(); + int secondId = outJson.get()["id"].get(); EXPECT_NE(firstId, secondId); } @@ -202,8 +202,8 @@ TEST(JsonRpcDispatcher, SendRequestWithParamsAndReceiveResponse) { EXPECT_TRUE(dispatcher.sendRequest( // out, "getValue", Json::null(), {[&zeValue](const Json::Value &result) { - if (result.is()) { - zeValue = result.get(); + if (result.is()) { + zeValue = result.get(); } }, [&receivedError](const std::string &error) { @@ -215,10 +215,10 @@ TEST(JsonRpcDispatcher, SendRequestWithParamsAndReceiveResponse) { ASSERT_NE(outJson.get().find("id"), outJson.get().end()); ASSERT_NE(outJson.get().find("method"), outJson.get().end()); - ASSERT_TRUE(outJson.get()["id"].is()); + ASSERT_TRUE(outJson.get()["id"].is()); EXPECT_EQ(outJson.get()["method"], Json::string("getValue")); - int requestId = outJson.get()["id"].get(); + int requestId = outJson.get()["id"].get(); EXPECT_EQ(zeValue, 0); @@ -249,8 +249,8 @@ TEST(JsonRpcDispatcher, SendRequestWithParamsAndReceiveResponse) { EXPECT_TRUE(dispatcher.sendRequest( // out, "getValue", Json::null(), {[&zeValue](const Json::Value &result) { - if (result.is()) { - zeValue = result.get(); + if (result.is()) { + zeValue = result.get(); } }, [&zeValue, &receivedError](const std::string &error) { @@ -264,10 +264,10 @@ TEST(JsonRpcDispatcher, SendRequestWithParamsAndReceiveResponse) { ASSERT_NE(outJson.get().find("id"), outJson.get().end()); ASSERT_NE(outJson.get().find("method"), outJson.get().end()); - ASSERT_TRUE(outJson.get()["id"].is()); + ASSERT_TRUE(outJson.get()["id"].is()); EXPECT_EQ(outJson.get()["method"], Json::string("getValue")); - int requestId = outJson.get()["id"].get(); + int requestId = outJson.get()["id"].get(); auto response = Json::Object({ { "id", Json::number(requestId)},