mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-23 19:02:10 +00:00
feat(automation): JSON-RPC dispatcher envelope + version + error model
This commit is contained in:
83
src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp
Normal file
83
src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "JsonRpcDispatcher.hpp"
|
||||
#include "WidgetSerializer.hpp"
|
||||
#include "Locator.hpp"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r { namespace GUI { namespace Automation {
|
||||
|
||||
JsonRpcDispatcher::JsonRpcDispatcher(IUiBackend& backend) : m_backend(backend) {}
|
||||
|
||||
nlohmann::json JsonRpcDispatcher::make_result(const nlohmann::json& id, nlohmann::json result) {
|
||||
return { {"jsonrpc","2.0"}, {"id", id}, {"result", std::move(result)} };
|
||||
}
|
||||
|
||||
nlohmann::json JsonRpcDispatcher::make_error(const nlohmann::json& id, int code,
|
||||
const std::string& msg) {
|
||||
return { {"jsonrpc","2.0"}, {"id", id},
|
||||
{"error", { {"code", code}, {"message", msg} }} };
|
||||
}
|
||||
|
||||
nlohmann::json JsonRpcDispatcher::m_version(const nlohmann::json&) {
|
||||
return { {"version", kAutomationVersion},
|
||||
{"protocol", "2.0"},
|
||||
{"capabilities", nlohmann::json::array({
|
||||
"tree.dump","tree.find","widget.get","input.click","input.type",
|
||||
"input.key","sync.wait_for","app.state","screenshot.window",
|
||||
"screenshot.viewport3d" })} };
|
||||
}
|
||||
|
||||
nlohmann::json JsonRpcDispatcher::dispatch(const nlohmann::json& request) {
|
||||
nlohmann::json id = request.contains("id") ? request.at("id") : nlohmann::json(nullptr);
|
||||
|
||||
if (!request.is_object() || !request.contains("method") ||
|
||||
!request.at("method").is_string()) {
|
||||
return make_error(id, kInvalidRequest, "missing or invalid 'method'");
|
||||
}
|
||||
const std::string method = request.at("method").get<std::string>();
|
||||
const nlohmann::json params =
|
||||
request.contains("params") ? request.at("params") : nlohmann::json::object();
|
||||
|
||||
try {
|
||||
if (method == "automation.version") return make_result(id, m_version(params));
|
||||
if (method == "tree.dump") return make_result(id, m_tree_dump(params));
|
||||
if (method == "tree.find") return make_result(id, m_tree_find(params));
|
||||
if (method == "widget.get") return make_result(id, m_widget_get(params));
|
||||
if (method == "input.click") return make_result(id, m_input_click(params));
|
||||
if (method == "input.type") return make_result(id, m_input_type(params));
|
||||
if (method == "input.key") return make_result(id, m_input_key(params));
|
||||
if (method == "sync.wait_for") return make_result(id, m_sync_wait_for(params));
|
||||
if (method == "app.state") return make_result(id, m_app_state(params));
|
||||
if (method == "screenshot.window") return make_result(id, m_screenshot_window(params));
|
||||
if (method == "screenshot.viewport3d") return make_result(id, m_screenshot_viewport3d(params));
|
||||
return make_error(id, kMethodNotFound, "unknown method: " + method);
|
||||
} catch (const AutomationError& e) {
|
||||
return make_error(id, e.code, e.what());
|
||||
} catch (const std::exception& e) {
|
||||
return make_error(id, kInvalidParams, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::string JsonRpcDispatcher::handle_request(const std::string& body) {
|
||||
nlohmann::json req;
|
||||
try {
|
||||
req = nlohmann::json::parse(body);
|
||||
} catch (const std::exception& e) {
|
||||
return make_error(nullptr, kParseError, std::string("parse error: ") + e.what()).dump();
|
||||
}
|
||||
return dispatch(req).dump();
|
||||
}
|
||||
|
||||
// --- method handlers implemented in Tasks 7-10 (stubs throw for now) ---
|
||||
nlohmann::json JsonRpcDispatcher::m_tree_dump(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_tree_find(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_widget_get(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_input_click(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_input_type(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_input_key(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_sync_wait_for(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_app_state(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_screenshot_window(const nlohmann::json&) { throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
nlohmann::json JsonRpcDispatcher::m_screenshot_viewport3d(const nlohmann::json&){ throw AutomationError(kMethodNotFound, "not implemented"); }
|
||||
|
||||
}}} // namespace
|
||||
55
src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp
Normal file
55
src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include "IUiBackend.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r { namespace GUI { namespace Automation {
|
||||
|
||||
// JSON-RPC 2.0 standard error codes.
|
||||
constexpr int kParseError = -32700;
|
||||
constexpr int kInvalidRequest = -32600;
|
||||
constexpr int kMethodNotFound = -32601;
|
||||
constexpr int kInvalidParams = -32602;
|
||||
// Application error codes (design spec §5).
|
||||
constexpr int kErrNotFound = 1001; // widget/target not found (or ambiguous)
|
||||
constexpr int kErrNotActionable = 1002; // disabled / hidden
|
||||
constexpr int kErrWaitTimeout = 1003;
|
||||
constexpr int kErrGuiBusy = 1004; // GUI thread timeout
|
||||
constexpr int kErrScreenshotFail = 1005;
|
||||
constexpr int kErrDisabled = 1006;
|
||||
|
||||
constexpr const char* kProtocolVersion = "2.0";
|
||||
constexpr const char* kAutomationVersion = "1.0.0";
|
||||
|
||||
class JsonRpcDispatcher {
|
||||
public:
|
||||
explicit JsonRpcDispatcher(IUiBackend& backend);
|
||||
|
||||
// Parse a JSON-RPC request body, dispatch, and return the response body.
|
||||
// Never throws; transport-level/parse errors become JSON-RPC error responses.
|
||||
std::string handle_request(const std::string& body);
|
||||
|
||||
// For tests: dispatch an already-parsed request object and return the response.
|
||||
nlohmann::json dispatch(const nlohmann::json& request);
|
||||
|
||||
private:
|
||||
nlohmann::json make_result(const nlohmann::json& id, nlohmann::json result);
|
||||
nlohmann::json make_error(const nlohmann::json& id, int code, const std::string& msg);
|
||||
|
||||
// Method handlers (each returns the `result` object or throws AutomationError).
|
||||
nlohmann::json m_version(const nlohmann::json& params);
|
||||
nlohmann::json m_tree_dump(const nlohmann::json& params);
|
||||
nlohmann::json m_tree_find(const nlohmann::json& params);
|
||||
nlohmann::json m_widget_get(const nlohmann::json& params);
|
||||
nlohmann::json m_input_click(const nlohmann::json& params);
|
||||
nlohmann::json m_input_type(const nlohmann::json& params);
|
||||
nlohmann::json m_input_key(const nlohmann::json& params);
|
||||
nlohmann::json m_sync_wait_for(const nlohmann::json& params);
|
||||
nlohmann::json m_app_state(const nlohmann::json& params);
|
||||
nlohmann::json m_screenshot_window(const nlohmann::json& params);
|
||||
nlohmann::json m_screenshot_viewport3d(const nlohmann::json& params);
|
||||
|
||||
IUiBackend& m_backend;
|
||||
};
|
||||
|
||||
}}} // namespace
|
||||
@@ -4,9 +4,11 @@ add_executable(${_TEST_NAME}_tests
|
||||
automation_tests.cpp
|
||||
test_serializer.cpp
|
||||
test_locator.cpp
|
||||
test_dispatcher.cpp
|
||||
MockUiBackend.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/slic3r/GUI/Automation/WidgetSerializer.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/slic3r/GUI/Automation/Locator.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common Catch2::Catch2WithMain nlohmann_json)
|
||||
# The nlohmann_json INTERFACE target only exposes deps_src/nlohmann on the include
|
||||
|
||||
44
tests/automation/test_dispatcher.cpp
Normal file
44
tests/automation/test_dispatcher.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include "slic3r/GUI/Automation/JsonRpcDispatcher.hpp"
|
||||
#include "MockUiBackend.hpp"
|
||||
|
||||
using namespace Slic3r::GUI::Automation;
|
||||
using nlohmann::json;
|
||||
|
||||
TEST_CASE("dispatch automation.version", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json req = {{"jsonrpc","2.0"},{"id",1},{"method","automation.version"}};
|
||||
const json resp = d.dispatch(req);
|
||||
CHECK(resp.at("jsonrpc") == "2.0");
|
||||
CHECK(resp.at("id") == 1);
|
||||
CHECK(resp.at("result").at("version") == kAutomationVersion);
|
||||
CHECK(resp.at("result").at("protocol") == "2.0");
|
||||
CHECK(resp.at("result").at("capabilities").is_array());
|
||||
}
|
||||
|
||||
TEST_CASE("unknown method -> -32601", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json req = {{"jsonrpc","2.0"},{"id",7},{"method","does.not.exist"}};
|
||||
const json resp = d.dispatch(req);
|
||||
CHECK(resp.at("id") == 7);
|
||||
CHECK(resp.at("error").at("code") == kMethodNotFound);
|
||||
}
|
||||
|
||||
TEST_CASE("malformed JSON body -> parse error", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const std::string resp = d.handle_request("{not json");
|
||||
const json j = json::parse(resp);
|
||||
CHECK(j.at("error").at("code") == kParseError);
|
||||
CHECK(j.at("id").is_null());
|
||||
}
|
||||
|
||||
TEST_CASE("missing method field -> invalid request", "[automation][rpc]") {
|
||||
MockUiBackend mock;
|
||||
JsonRpcDispatcher d(mock);
|
||||
const json req = {{"jsonrpc","2.0"},{"id",2}};
|
||||
const json resp = d.dispatch(req);
|
||||
CHECK(resp.at("error").at("code") == kInvalidRequest);
|
||||
}
|
||||
Reference in New Issue
Block a user