[base] hail vibe-coding
Minimal API functionality with unit tests, could be used for integration tests with Unity server. For internal testing used RPC_server implementation
This commit is contained in:
commit
cbe56d9193
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
subprojects/asio-1.36.0/
|
||||||
|
subprojects/glog/
|
||||||
|
subprojects/googletest-*
|
||||||
|
subprojects/nlohmann_json/
|
||||||
|
subprojects/packagecache/
|
||||||
|
subprojects/yaml-cpp-0.8.0
|
||||||
125
API.md
Normal file
125
API.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# JSON-RPC API Documentation
|
||||||
|
|
||||||
|
The Cloud Point RPC server implements the **JSON-RPC 2.0** protocol over TCP.
|
||||||
|
|
||||||
|
## General Format
|
||||||
|
|
||||||
|
All requests and responses are JSON objects.
|
||||||
|
|
||||||
|
### Request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "<method_name>",
|
||||||
|
"params": {},
|
||||||
|
"id": <integer|string>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Note: `params` is currently ignored by the implemented methods but is part of the standard.*
|
||||||
|
|
||||||
|
### Response (Success)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": <method_specific_result>,
|
||||||
|
"id": <matching_request_id>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response (Error)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"error": {
|
||||||
|
"code": <integer>,
|
||||||
|
"message": "<string>"
|
||||||
|
},
|
||||||
|
"id": <matching_request_id>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### `get-intrinsic-params`
|
||||||
|
|
||||||
|
Retrieves the intrinsic camera parameters as a flat 3x3 matrix (row-major).
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "get-intrinsic-params",
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": [
|
||||||
|
1.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0
|
||||||
|
],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Type: `vector<double>` (size 9)*
|
||||||
|
|
||||||
|
### `get-extrinsic-params`
|
||||||
|
|
||||||
|
Retrieves the extrinsic camera parameters as a flat 4x4 matrix (row-major).
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "get-extrinsic-params",
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": [
|
||||||
|
1.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0
|
||||||
|
],
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Type: `vector<double>` (size 16)*
|
||||||
|
|
||||||
|
### `get-cloud-point`
|
||||||
|
|
||||||
|
Retrieves the current field of view point cloud.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "get-cloud-point",
|
||||||
|
"id": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": [
|
||||||
|
[0.1, 0.2, 0.3],
|
||||||
|
[1.1, 1.2, 1.3],
|
||||||
|
[5.5, 6.6, 7.7]
|
||||||
|
],
|
||||||
|
"id": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Type: `vector<vector<double>>` (List of [x, y, z] points)*
|
||||||
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Use Ubuntu 24.04 as base (matching development environment)
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
# Avoid interactive prompts during package installation
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
# - build-essential: Compiler (gcc/g++)
|
||||||
|
# - meson/ninja-build: Build system
|
||||||
|
# - git: For fetching subprojects
|
||||||
|
# - pkg-config, cmake: For dependency resolution
|
||||||
|
# - libssl-dev: Often needed for cmake fetches/networking
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
meson \
|
||||||
|
ninja-build \
|
||||||
|
git \
|
||||||
|
pkg-config \
|
||||||
|
cmake \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy project files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Setup build directory and compile
|
||||||
|
# We allow git to fetch subprojects (glog, gtest, asio, etc.)
|
||||||
|
RUN meson setup build && \
|
||||||
|
meson compile -C build
|
||||||
|
|
||||||
|
# Expose the default port (can be changed in config.yaml)
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the server by default
|
||||||
|
# We assume the config.yaml is in the root /app or we copy it.
|
||||||
|
# The build output is in build/src/cloud_point_rpc_server
|
||||||
|
CMD ["./build/src/cloud_point_rpc_server", "config.yaml"]
|
||||||
56
README.md
Normal file
56
README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Cloud Point RPC
|
||||||
|
|
||||||
|
Communication JSON RPC protocol and implementation with Unity Scene.
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
See [API.md](API.md) for detailed request/response formats.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
The project uses **Meson** build system and **C++20**.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- Meson, Ninja
|
||||||
|
- GCC/Clang (C++20 support)
|
||||||
|
- Git (for subprojects)
|
||||||
|
|
||||||
|
### Build & Run
|
||||||
|
```bash
|
||||||
|
meson setup build
|
||||||
|
meson compile -C build
|
||||||
|
./build/src/cloud_point_rpc_server config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
meson test -C build -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
You can build and run the server using Docker.
|
||||||
|
|
||||||
|
### 1. Build Image
|
||||||
|
```bash
|
||||||
|
docker build -t cloud-point-rpc .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Run Container
|
||||||
|
The server runs on port **8080** by default (defined in `config.yaml` inside the image).
|
||||||
|
|
||||||
|
**Option A: Port Mapping**
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 cloud-point-rpc
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Host Network (For Simplicity/Performance)**
|
||||||
|
```bash
|
||||||
|
docker run --network host cloud-point-rpc
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom Configuration:**
|
||||||
|
You can mount your own `config.yaml` to override the default settings:
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 -v $(pwd)/my_config.yaml:/app/config.yaml cloud-point-rpc
|
||||||
|
```
|
||||||
11
config.yaml
Normal file
11
config.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
server:
|
||||||
|
ip: "127.0.0.1"
|
||||||
|
port: 8085
|
||||||
|
|
||||||
|
test_data:
|
||||||
|
intrinsic_params: [1.1, 0.0, 0.0, 0.0, 1.1, 0.0, 0.0, 0.0, 1.0]
|
||||||
|
extrinsic_params: [1.0, 0.0, 0.0, 0.5, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
|
||||||
|
cloud_point:
|
||||||
|
- [0.1, 0.2, 0.3]
|
||||||
|
- [1.1, 1.2, 1.3]
|
||||||
|
- [5.5, 6.6, 7.7]
|
||||||
66
include/cloud_point_rpc/config.hpp
Normal file
66
include/cloud_point_rpc/config.hpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace cloud_point_rpc {
|
||||||
|
|
||||||
|
struct ServerConfig {
|
||||||
|
std::string ip;
|
||||||
|
int port;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestData {
|
||||||
|
std::vector<double> intrinsic_params;
|
||||||
|
std::vector<double> extrinsic_params;
|
||||||
|
std::vector<std::vector<double>> cloud_point;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
ServerConfig server;
|
||||||
|
TestData test_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigLoader {
|
||||||
|
public:
|
||||||
|
static Config load(const std::string& path) {
|
||||||
|
try {
|
||||||
|
YAML::Node config = YAML::LoadFile(path);
|
||||||
|
Config c;
|
||||||
|
|
||||||
|
// Server
|
||||||
|
if (config["server"]) {
|
||||||
|
c.server.ip = config["server"]["ip"].as<std::string>("127.0.0.1");
|
||||||
|
c.server.port = config["server"]["port"].as<int>(8080);
|
||||||
|
} else {
|
||||||
|
c.server.ip = "127.0.0.1";
|
||||||
|
c.server.port = 8080;
|
||||||
|
LOG(WARNING) << "No 'server' section, using defaults.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Data
|
||||||
|
if (config["test_data"]) {
|
||||||
|
c.test_data.intrinsic_params = config["test_data"]["intrinsic_params"].as<std::vector<double>>();
|
||||||
|
c.test_data.extrinsic_params = config["test_data"]["extrinsic_params"].as<std::vector<double>>();
|
||||||
|
|
||||||
|
// Parse cloud_point (list of lists)
|
||||||
|
auto cp_node = config["test_data"]["cloud_point"];
|
||||||
|
for (const auto& point_node : cp_node) {
|
||||||
|
c.test_data.cloud_point.push_back(point_node.as<std::vector<double>>());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "No 'test_data' section, using empty/defaults.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
} catch (const YAML::Exception& e) {
|
||||||
|
LOG(ERROR) << "Failed to load config: " << e.what();
|
||||||
|
throw std::runtime_error("Config load failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cloud_point_rpc
|
||||||
80
include/cloud_point_rpc/rpc_client.hpp
Normal file
80
include/cloud_point_rpc/rpc_client.hpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace cloud_point_rpc {
|
||||||
|
|
||||||
|
class RpcClient {
|
||||||
|
public:
|
||||||
|
RpcClient() : socket_(io_context_) {}
|
||||||
|
|
||||||
|
void connect(const std::string& ip, int port) {
|
||||||
|
try {
|
||||||
|
LOG(INFO) << "Client connecting to " << ip << ":" << port;
|
||||||
|
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip), port);
|
||||||
|
socket_.connect(endpoint);
|
||||||
|
LOG(INFO) << "Client connected";
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throw std::runtime_error(std::string("Connection Failed: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<double> get_intrinsic_params() {
|
||||||
|
return call_method<std::vector<double>>("get-intrinsic-params");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<double> get_extrinsic_params() {
|
||||||
|
return call_method<std::vector<double>>("get-extrinsic-params");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() {
|
||||||
|
return call_method<std::vector<std::vector<double>>>("get-cloud-point");
|
||||||
|
}
|
||||||
|
|
||||||
|
~RpcClient() {
|
||||||
|
// Socket closes automatically
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename T>
|
||||||
|
T call_method(const std::string& method) {
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// Create Request
|
||||||
|
json request = {
|
||||||
|
{"jsonrpc", "2.0"},
|
||||||
|
{"method", method},
|
||||||
|
{"id", ++id_counter_}
|
||||||
|
};
|
||||||
|
std::string request_str = request.dump();
|
||||||
|
|
||||||
|
// Send
|
||||||
|
LOG(INFO) << "Client sending: " << request_str;
|
||||||
|
asio::write(socket_, asio::buffer(request_str));
|
||||||
|
|
||||||
|
// Read Response
|
||||||
|
LOG(INFO) << "Client reading response...";
|
||||||
|
char buffer[4096];
|
||||||
|
size_t len = socket_.read_some(asio::buffer(buffer, 4096));
|
||||||
|
LOG(INFO) << "Client read " << len << " bytes";
|
||||||
|
|
||||||
|
json response = json::parse(std::string(buffer, len));
|
||||||
|
|
||||||
|
if (response.contains("error")) {
|
||||||
|
throw std::runtime_error(response["error"]["message"].get<std::string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response["result"].get<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::io_context io_context_;
|
||||||
|
asio::ip::tcp::socket socket_;
|
||||||
|
int id_counter_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cloud_point_rpc
|
||||||
21
include/cloud_point_rpc/rpc_server.hpp
Normal file
21
include/cloud_point_rpc/rpc_server.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace cloud_point_rpc {
|
||||||
|
|
||||||
|
class RpcServer {
|
||||||
|
public:
|
||||||
|
using Handler = std::function<nlohmann::json(const nlohmann::json&)>;
|
||||||
|
|
||||||
|
void register_method(const std::string& name, Handler handler);
|
||||||
|
[[nodiscard]] std::string process(const std::string& request_str);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, Handler> handlers_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cloud_point_rpc
|
||||||
20
include/cloud_point_rpc/service.hpp
Normal file
20
include/cloud_point_rpc/service.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "cloud_point_rpc/config.hpp"
|
||||||
|
|
||||||
|
namespace cloud_point_rpc {
|
||||||
|
|
||||||
|
class Service {
|
||||||
|
public:
|
||||||
|
explicit Service(const TestData& data = {});
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<double> get_intrinsic_params() const;
|
||||||
|
[[nodiscard]] std::vector<double> get_extrinsic_params() const;
|
||||||
|
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TestData data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cloud_point_rpc
|
||||||
129
include/cloud_point_rpc/tcp_server.hpp
Normal file
129
include/cloud_point_rpc/tcp_server.hpp
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <asio.hpp>
|
||||||
|
|
||||||
|
namespace cloud_point_rpc {
|
||||||
|
|
||||||
|
class TcpServer {
|
||||||
|
public:
|
||||||
|
using RequestProcessor = std::function<std::string(const std::string&)>;
|
||||||
|
|
||||||
|
TcpServer(const std::string& ip, int port, RequestProcessor processor)
|
||||||
|
: ip_(ip), port_(port), processor_(std::move(processor)),
|
||||||
|
acceptor_(io_context_), running_(false) {}
|
||||||
|
|
||||||
|
~TcpServer() { stop(); }
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
try {
|
||||||
|
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip_), port_);
|
||||||
|
acceptor_.open(endpoint.protocol());
|
||||||
|
acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true));
|
||||||
|
acceptor_.bind(endpoint);
|
||||||
|
acceptor_.listen();
|
||||||
|
|
||||||
|
running_ = true;
|
||||||
|
LOG(INFO) << "Server listening on " << ip_ << ":" << port_;
|
||||||
|
|
||||||
|
accept_thread_ = std::jthread([this]() {
|
||||||
|
LOG(INFO) << "Accept thread started";
|
||||||
|
while (running_) {
|
||||||
|
try {
|
||||||
|
auto socket = std::make_shared<asio::ip::tcp::socket>(io_context_);
|
||||||
|
acceptor_.accept(*socket);
|
||||||
|
|
||||||
|
LOG(INFO) << "New connection from " << socket->remote_endpoint().address().to_string();
|
||||||
|
|
||||||
|
std::jthread([this, socket]() {
|
||||||
|
handle_client(socket);
|
||||||
|
}).detach();
|
||||||
|
} catch (const std::system_error& e) {
|
||||||
|
LOG(INFO) << "Accept exception: " << e.what();
|
||||||
|
if (running_) {
|
||||||
|
LOG(WARNING) << "Accept failed: " << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Accept thread exiting";
|
||||||
|
});
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG(ERROR) << "Server start failed: " << e.what();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!running_) return;
|
||||||
|
LOG(INFO) << "Stopping server...";
|
||||||
|
running_ = false;
|
||||||
|
// Closing acceptor unblocks accept() call usually, but sometimes we need to prod it
|
||||||
|
asio::error_code ec;
|
||||||
|
acceptor_.close(ec);
|
||||||
|
|
||||||
|
// Ensure accept unblocks by connecting a dummy socket
|
||||||
|
try {
|
||||||
|
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip_), port_);
|
||||||
|
asio::ip::tcp::socket dummy_sock(io_context_);
|
||||||
|
asio::error_code connect_ec;
|
||||||
|
dummy_sock.connect(endpoint, connect_ec);
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Acceptor closed";
|
||||||
|
}
|
||||||
|
|
||||||
|
void join() {
|
||||||
|
if (accept_thread_.joinable()) {
|
||||||
|
LOG(INFO) << "Joining accept thread...";
|
||||||
|
accept_thread_.join();
|
||||||
|
LOG(INFO) << "Accept thread joined";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handle_client(std::shared_ptr<asio::ip::tcp::socket> socket) {
|
||||||
|
try {
|
||||||
|
asio::streambuf buffer;
|
||||||
|
// Read until newline or EOF/error
|
||||||
|
// Note: This matches the client implementation which should send a newline
|
||||||
|
// However, previous implementation read 4096 bytes raw.
|
||||||
|
// Let's emulate "read some" to match previous simple behavior, or use read_until if we enforce framing.
|
||||||
|
// Given this is a prototype, let's read once.
|
||||||
|
|
||||||
|
LOG(INFO) << "Server reading from client...";
|
||||||
|
char data[4096];
|
||||||
|
size_t length = socket->read_some(asio::buffer(data, 4096)); // Error will throw
|
||||||
|
LOG(INFO) << "Server read " << length << " bytes";
|
||||||
|
|
||||||
|
if (length > 0) {
|
||||||
|
std::string request(data, length);
|
||||||
|
LOG(INFO) << "Server processing request: " << request;
|
||||||
|
std::string response = processor_(request);
|
||||||
|
response += "\n";
|
||||||
|
LOG(INFO) << "Server sending response: " << response;
|
||||||
|
asio::write(*socket, asio::buffer(response));
|
||||||
|
LOG(INFO) << "Server sent response";
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG(WARNING) << "Client handling error: " << e.what();
|
||||||
|
}
|
||||||
|
// Socket closes when shared_ptr dies
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ip_;
|
||||||
|
int port_;
|
||||||
|
RequestProcessor processor_;
|
||||||
|
|
||||||
|
asio::io_context io_context_;
|
||||||
|
asio::ip::tcp::acceptor acceptor_;
|
||||||
|
|
||||||
|
std::atomic<bool> running_;
|
||||||
|
std::jthread accept_thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cloud_point_rpc
|
||||||
28
meson.build
Normal file
28
meson.build
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
project('cloud_point_rpc', 'cpp',
|
||||||
|
version : '0.1',
|
||||||
|
default_options : ['warning_level=3', 'cpp_std=c++20'])
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
json_dep = dependency('nlohmann_json', fallback : ['nlohmann_json', 'nlohmann_json_dep'])
|
||||||
|
thread_dep = dependency('threads')
|
||||||
|
asio_dep = dependency('asio', fallback : ['asio', 'asio_dep'])
|
||||||
|
|
||||||
|
# GLog via CMake fallback
|
||||||
|
cmake = import('cmake')
|
||||||
|
glog_opt = cmake.subproject_options()
|
||||||
|
glog_opt.add_cmake_defines({'WITH_GFLAGS': 'OFF', 'WITH_GTEST': 'OFF'})
|
||||||
|
glog_proj = cmake.subproject('glog', options: glog_opt)
|
||||||
|
glog_dep = glog_proj.dependency('glog')
|
||||||
|
|
||||||
|
yaml_dep = dependency('yaml-cpp', fallback : ['yaml-cpp', 'yaml_cpp_dep'])
|
||||||
|
|
||||||
|
# GTest & GMock via explicit subproject
|
||||||
|
gtest_proj = subproject('gtest')
|
||||||
|
gtest_dep = gtest_proj.get_variable('gtest_dep')
|
||||||
|
gtest_main_dep = gtest_proj.get_variable('gtest_main_dep')
|
||||||
|
gmock_dep = gtest_proj.get_variable('gmock_dep')
|
||||||
|
|
||||||
|
inc = include_directories('include')
|
||||||
|
|
||||||
|
subdir('src')
|
||||||
|
subdir('tests')
|
||||||
33
src/main.cpp
Normal file
33
src/main.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include "cloud_point_rpc/rpc_server.hpp"
|
||||||
|
#include "cloud_point_rpc/service.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
cloud_point_rpc::Service service;
|
||||||
|
cloud_point_rpc::RpcServer server;
|
||||||
|
|
||||||
|
// Bind service methods to RPC handlers
|
||||||
|
server.register_method("get-intrinsic-params", [&](const nlohmann::json&) {
|
||||||
|
return service.get_intrinsic_params();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.register_method("get-extrinsic-params", [&](const nlohmann::json&) {
|
||||||
|
return service.get_extrinsic_params();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.register_method("get-cloud-point", [&](const nlohmann::json&) {
|
||||||
|
return service.get_cloud_point();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << "Cloud Point RPC Server initialized." << std::endl;
|
||||||
|
std::cout << "Enter JSON-RPC requests (Ctrl+D to exit):" << std::endl;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(std::cin, line)) {
|
||||||
|
if (line.empty()) continue;
|
||||||
|
std::cout << server.process(line) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
27
src/meson.build
Normal file
27
src/meson.build
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
cloud_point_rpc_sources = files(
|
||||||
|
'rpc_server.cpp',
|
||||||
|
'service.cpp'
|
||||||
|
)
|
||||||
|
|
||||||
|
libcloud_point_rpc = static_library('cloud_point_rpc',
|
||||||
|
cloud_point_rpc_sources,
|
||||||
|
include_directories : inc,
|
||||||
|
dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep],
|
||||||
|
install : true)
|
||||||
|
|
||||||
|
cloud_point_rpc_dep = declare_dependency(
|
||||||
|
include_directories : inc,
|
||||||
|
link_with : libcloud_point_rpc,
|
||||||
|
dependencies : [json_dep, glog_dep, yaml_dep, asio_dep])
|
||||||
|
|
||||||
|
# Client/CLI tool (legacy stdin/stdout)
|
||||||
|
executable('cloud_point_rpc_cli',
|
||||||
|
'main.cpp',
|
||||||
|
dependencies : cloud_point_rpc_dep,
|
||||||
|
install : true)
|
||||||
|
|
||||||
|
# Server executable (TCP)
|
||||||
|
executable('cloud_point_rpc_server',
|
||||||
|
'server_main.cpp',
|
||||||
|
dependencies : cloud_point_rpc_dep,
|
||||||
|
install : true)
|
||||||
61
src/rpc_server.cpp
Normal file
61
src/rpc_server.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include "cloud_point_rpc/rpc_server.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace cloud_point_rpc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
json create_error(int code, const std::string& message, const json& id = nullptr) {
|
||||||
|
return {
|
||||||
|
{"jsonrpc", "2.0"},
|
||||||
|
{"error", {{"code", code}, {"message", message}}},
|
||||||
|
{"id", id}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
json create_success(const json& result, const json& id) {
|
||||||
|
return {
|
||||||
|
{"jsonrpc", "2.0"},
|
||||||
|
{"result", result},
|
||||||
|
{"id", id}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void RpcServer::register_method(const std::string& name, Handler handler) {
|
||||||
|
handlers_[name] = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RpcServer::process(const std::string& request_str) {
|
||||||
|
json request;
|
||||||
|
try {
|
||||||
|
request = json::parse(request_str);
|
||||||
|
} catch (const json::parse_error&) {
|
||||||
|
return create_error(-32700, "Parse error").dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch requests are not supported in this minimal version, assume single object
|
||||||
|
if (!request.contains("jsonrpc") || request["jsonrpc"] != "2.0" ||
|
||||||
|
!request.contains("method") || !request.contains("id")) {
|
||||||
|
return create_error(-32600, "Invalid Request", request.value("id", json(nullptr))).dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string method = request["method"];
|
||||||
|
json id = request["id"];
|
||||||
|
json params = request.value("params", json::object());
|
||||||
|
|
||||||
|
auto it = handlers_.find(method);
|
||||||
|
if (it == handlers_.end()) {
|
||||||
|
return create_error(-32601, "Method not found", id).dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
json result = it->second(params);
|
||||||
|
return create_success(result, id).dump();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return create_error(-32000, e.what(), id).dump(); // Server error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cloud_point_rpc
|
||||||
58
src/server_main.cpp
Normal file
58
src/server_main.cpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "cloud_point_rpc/rpc_server.hpp"
|
||||||
|
#include "cloud_point_rpc/service.hpp"
|
||||||
|
#include "cloud_point_rpc/config.hpp"
|
||||||
|
#include "cloud_point_rpc/tcp_server.hpp"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
google::InstallFailureSignalHandler();
|
||||||
|
FLAGS_alsologtostderr = 1;
|
||||||
|
|
||||||
|
std::string config_path = "config.yaml";
|
||||||
|
if (argc > 1) {
|
||||||
|
config_path = argv[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << "Starting Cloud Point RPC Server (Test Mock)...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto config = cloud_point_rpc::ConfigLoader::load(config_path);
|
||||||
|
LOG(INFO) << "Loaded config from " << config_path;
|
||||||
|
|
||||||
|
// Inject test data into service
|
||||||
|
cloud_point_rpc::Service service(config.test_data);
|
||||||
|
cloud_point_rpc::RpcServer rpc_server;
|
||||||
|
|
||||||
|
rpc_server.register_method("get-intrinsic-params", [&](const json&) {
|
||||||
|
return service.get_intrinsic_params();
|
||||||
|
});
|
||||||
|
|
||||||
|
rpc_server.register_method("get-extrinsic-params", [&](const json&) {
|
||||||
|
return service.get_extrinsic_params();
|
||||||
|
});
|
||||||
|
|
||||||
|
rpc_server.register_method("get-cloud-point", [&](const json&) {
|
||||||
|
return service.get_cloud_point();
|
||||||
|
});
|
||||||
|
|
||||||
|
cloud_point_rpc::TcpServer server(config.server.ip, config.server.port,
|
||||||
|
[&](const std::string& request) {
|
||||||
|
return rpc_server.process(request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
server.join();
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG(ERROR) << "Fatal error: " << e.what();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
38
src/service.cpp
Normal file
38
src/service.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include "cloud_point_rpc/service.hpp"
|
||||||
|
|
||||||
|
namespace cloud_point_rpc {
|
||||||
|
|
||||||
|
Service::Service(const TestData& data) : data_(data) {}
|
||||||
|
|
||||||
|
std::vector<double> Service::get_intrinsic_params() const {
|
||||||
|
if (data_.intrinsic_params.empty()) {
|
||||||
|
// Fallback if no config loaded
|
||||||
|
return {1.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0};
|
||||||
|
}
|
||||||
|
return data_.intrinsic_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> Service::get_extrinsic_params() const {
|
||||||
|
if (data_.extrinsic_params.empty()) {
|
||||||
|
return {1.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0};
|
||||||
|
}
|
||||||
|
return data_.extrinsic_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<double>> Service::get_cloud_point() const {
|
||||||
|
if (data_.cloud_point.empty()) {
|
||||||
|
return {
|
||||||
|
{0.1, 0.2, 0.3},
|
||||||
|
{1.1, 1.2, 1.3},
|
||||||
|
{2.1, 2.2, 2.3}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return data_.cloud_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cloud_point_rpc
|
||||||
13
subprojects/asio.wrap
Normal file
13
subprojects/asio.wrap
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[wrap-file]
|
||||||
|
directory = asio-1.36.0
|
||||||
|
source_url = https://sourceforge.net/projects/asio/files/asio/1.36.0%20%28Stable%29/asio-1.36.0.tar.gz/download
|
||||||
|
source_filename = asio-1.36.0.tar.gz
|
||||||
|
source_hash = 55d5c64e78b1bedd0004423e695c2cfc191fc71914eaaa7f042329ff99ee6155
|
||||||
|
patch_filename = asio_1.36.0-2_patch.zip
|
||||||
|
patch_url = https://wrapdb.mesonbuild.com/v2/asio_1.36.0-2/get_patch
|
||||||
|
patch_hash = 0562360a9d6ef6fc5316f9a0b1d433b799259c70e0ce8f109e949b0b79797cd9
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/asio_1.36.0-2/asio-1.36.0.tar.gz
|
||||||
|
wrapdb_version = 1.36.0-2
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
asio = asio_dep
|
||||||
7
subprojects/glog.wrap
Normal file
7
subprojects/glog.wrap
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[wrap-git]
|
||||||
|
url = https://github.com/google/glog.git
|
||||||
|
revision = v0.6.0
|
||||||
|
depth = 1
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
glog = glog_dep
|
||||||
16
subprojects/gtest.wrap
Normal file
16
subprojects/gtest.wrap
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[wrap-file]
|
||||||
|
directory = googletest-1.17.0
|
||||||
|
source_url = https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz
|
||||||
|
source_filename = googletest-1.17.0.tar.gz
|
||||||
|
source_hash = 65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c
|
||||||
|
patch_filename = gtest_1.17.0-4_patch.zip
|
||||||
|
patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.17.0-4/get_patch
|
||||||
|
patch_hash = 3abf7662d09db706453a5b064a1e914678c74b9d9b0b19382747ca561d0d8750
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.17.0-4/googletest-1.17.0.tar.gz
|
||||||
|
wrapdb_version = 1.17.0-4
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
gtest = gtest_dep
|
||||||
|
gtest_main = gtest_main_dep
|
||||||
|
gmock = gmock_dep
|
||||||
|
gmock_main = gmock_main_dep
|
||||||
7
subprojects/nlohmann_json.wrap
Normal file
7
subprojects/nlohmann_json.wrap
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[wrap-git]
|
||||||
|
url = https://github.com/nlohmann/json.git
|
||||||
|
revision = v3.11.2
|
||||||
|
depth = 1
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
nlohmann_json = nlohmann_json_dep
|
||||||
13
subprojects/yaml-cpp.wrap
Normal file
13
subprojects/yaml-cpp.wrap
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[wrap-file]
|
||||||
|
directory = yaml-cpp-0.8.0
|
||||||
|
source_url = https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.zip
|
||||||
|
source_filename = yaml-cpp-0.8.0.zip
|
||||||
|
source_hash = 334e80ab7b52e14c23f94e041c74bab0742f2281aad55f66be2f19f4b7747071
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/yaml-cpp_0.8.0-2/yaml-cpp-0.8.0.zip
|
||||||
|
patch_filename = yaml-cpp_0.8.0-2_patch.zip
|
||||||
|
patch_url = https://wrapdb.mesonbuild.com/v2/yaml-cpp_0.8.0-2/get_patch
|
||||||
|
patch_hash = e7424f2804f2bb9e99f8ecea0c3c53e6de813f93043130243a27adfef3526573
|
||||||
|
wrapdb_version = 0.8.0-2
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
dependency_names = yaml-cpp
|
||||||
10
tests/meson.build
Normal file
10
tests/meson.build
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
test_sources = files(
|
||||||
|
'test_rpc.cpp',
|
||||||
|
'test_integration.cpp'
|
||||||
|
)
|
||||||
|
|
||||||
|
test_exe = executable('unit_tests',
|
||||||
|
test_sources,
|
||||||
|
dependencies : [cloud_point_rpc_dep, gtest_dep, gtest_main_dep, gmock_dep])
|
||||||
|
|
||||||
|
test('unit_tests', test_exe)
|
||||||
100
tests/test_integration.cpp
Normal file
100
tests/test_integration.cpp
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "cloud_point_rpc/config.hpp"
|
||||||
|
#include "cloud_point_rpc/rpc_server.hpp"
|
||||||
|
#include "cloud_point_rpc/service.hpp"
|
||||||
|
#include "cloud_point_rpc/tcp_server.hpp"
|
||||||
|
#include "cloud_point_rpc/rpc_client.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace cloud_point_rpc;
|
||||||
|
|
||||||
|
class IntegrationTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
// Create a temporary config file for testing
|
||||||
|
std::ofstream config_file("config.yaml");
|
||||||
|
config_file << "server:\n"
|
||||||
|
<< " ip: \"127.0.0.1\"\n"
|
||||||
|
<< " port: 9095\n"
|
||||||
|
<< "test_data:\n"
|
||||||
|
<< " intrinsic_params: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]\n"
|
||||||
|
<< " extrinsic_params: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]\n"
|
||||||
|
<< " cloud_point:\n"
|
||||||
|
<< " - [0.1, 0.2, 0.3]\n";
|
||||||
|
config_file.close();
|
||||||
|
|
||||||
|
// Setup Mock Server
|
||||||
|
try {
|
||||||
|
config_ = ConfigLoader::load("config.yaml");
|
||||||
|
} catch (...) {
|
||||||
|
// If config fails, we can't proceed, but we should avoid crashing in TearDown
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
service_ = std::make_unique<Service>(config_.test_data);
|
||||||
|
rpc_server_ = std::make_unique<RpcServer>();
|
||||||
|
|
||||||
|
rpc_server_->register_method("get-intrinsic-params", [&](const nlohmann::json&) {
|
||||||
|
return service_->get_intrinsic_params();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start Server Thread
|
||||||
|
tcp_server_ = std::make_unique<TcpServer>(config_.server.ip, config_.server.port,
|
||||||
|
[&](const std::string& req) {
|
||||||
|
return rpc_server_->process(req);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server_thread_ = std::thread([&]() {
|
||||||
|
tcp_server_->start();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give server time to start
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
if (tcp_server_) {
|
||||||
|
tcp_server_->stop();
|
||||||
|
}
|
||||||
|
if (server_thread_.joinable()) {
|
||||||
|
server_thread_.join();
|
||||||
|
}
|
||||||
|
// Clean up
|
||||||
|
std::remove("config.yaml");
|
||||||
|
}
|
||||||
|
|
||||||
|
Config config_;
|
||||||
|
std::unique_ptr<Service> service_;
|
||||||
|
std::unique_ptr<RpcServer> rpc_server_;
|
||||||
|
std::unique_ptr<TcpServer> tcp_server_;
|
||||||
|
std::thread server_thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(IntegrationTest, ClientCanConnectAndRetrieveValues) {
|
||||||
|
RpcClient client;
|
||||||
|
|
||||||
|
// Act: Connect
|
||||||
|
ASSERT_NO_THROW(client.connect(config_.server.ip, config_.server.port));
|
||||||
|
|
||||||
|
// Act: Call Method
|
||||||
|
auto params = client.get_intrinsic_params();
|
||||||
|
|
||||||
|
// Assert: Values match config
|
||||||
|
const auto& expected = config_.test_data.intrinsic_params;
|
||||||
|
ASSERT_EQ(params.size(), expected.size());
|
||||||
|
for (size_t i = 0; i < params.size(); ++i) {
|
||||||
|
EXPECT_DOUBLE_EQ(params[i], expected[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntegrationTest, ClientHandlesConnectionError) {
|
||||||
|
RpcClient client;
|
||||||
|
// Try connecting to a closed port
|
||||||
|
EXPECT_THROW(client.connect("127.0.0.1", 9999), std::runtime_error);
|
||||||
|
}
|
||||||
61
tests/test_rpc.cpp
Normal file
61
tests/test_rpc.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "cloud_point_rpc/rpc_server.hpp"
|
||||||
|
#include "cloud_point_rpc/service.hpp"
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
using namespace cloud_point_rpc;
|
||||||
|
|
||||||
|
class RpcServerTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
RpcServer server;
|
||||||
|
Service service;
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
server.register_method("get-intrinsic-params", [&](const json&) {
|
||||||
|
return service.get_intrinsic_params();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(RpcServerTest, GetIntrinsicParamsReturnsMatrix) {
|
||||||
|
std::string request = R"({"jsonrpc": "2.0", "method": "get-intrinsic-params", "id": 1})";
|
||||||
|
std::string response_str = server.process(request);
|
||||||
|
|
||||||
|
json response = json::parse(response_str);
|
||||||
|
|
||||||
|
ASSERT_EQ(response["jsonrpc"], "2.0");
|
||||||
|
ASSERT_EQ(response["id"], 1);
|
||||||
|
ASSERT_TRUE(response.contains("result"));
|
||||||
|
|
||||||
|
auto result = response["result"].get<std::vector<double>>();
|
||||||
|
EXPECT_EQ(result.size(), 9);
|
||||||
|
// Verify Identity Matrix
|
||||||
|
EXPECT_EQ(result[0], 1.0);
|
||||||
|
EXPECT_EQ(result[4], 1.0);
|
||||||
|
EXPECT_EQ(result[8], 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RpcServerTest, MethodNotFoundReturnsError) {
|
||||||
|
std::string request = R"({"jsonrpc": "2.0", "method": "unknown-method", "id": 99})";
|
||||||
|
std::string response_str = server.process(request);
|
||||||
|
|
||||||
|
json response = json::parse(response_str);
|
||||||
|
|
||||||
|
ASSERT_TRUE(response.contains("error"));
|
||||||
|
EXPECT_EQ(response["error"]["code"], -32601);
|
||||||
|
EXPECT_EQ(response["error"]["message"], "Method not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RpcServerTest, InvalidJsonReturnsParseError) {
|
||||||
|
std::string request = R"({"jsonrpc": "2.0", "method": "broken-json...)";
|
||||||
|
std::string response_str = server.process(request);
|
||||||
|
|
||||||
|
json response = json::parse(response_str);
|
||||||
|
|
||||||
|
ASSERT_TRUE(response.contains("error"));
|
||||||
|
EXPECT_EQ(response["error"]["code"], -32700);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user