#![allow(dead_code)]
#![allow(unused_variables)]
use serde_json::json;
use server_less::jsonrpc;
#[derive(Clone)]
struct Calculator;
#[jsonrpc]
impl Calculator {
pub fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
pub fn subtract(&self, a: i32, b: i32) -> i32 {
a - b
}
pub fn multiply(&self, a: i32, b: i32) -> i32 {
a * b
}
pub fn echo(&self, message: String) -> String {
message
}
}
#[test]
fn test_jsonrpc_methods_list() {
let methods = Calculator::jsonrpc_methods();
assert!(methods.contains(&"add"));
assert!(methods.contains(&"subtract"));
assert!(methods.contains(&"multiply"));
assert!(methods.contains(&"echo"));
}
#[tokio::test]
async fn test_jsonrpc_handle_add() {
let calc = Calculator;
let request = json!({
"jsonrpc": "2.0",
"method": "add",
"params": {"a": 5, "b": 3},
"id": 1
});
let response = calc.jsonrpc_handle(request).await;
assert_eq!(response["jsonrpc"], "2.0");
assert_eq!(response["result"], 8);
assert_eq!(response["id"], 1);
}
#[tokio::test]
async fn test_jsonrpc_handle_subtract() {
let calc = Calculator;
let request = json!({
"jsonrpc": "2.0",
"method": "subtract",
"params": {"a": 10, "b": 4},
"id": 2
});
let response = calc.jsonrpc_handle(request).await;
assert_eq!(response["result"], 6);
}
#[tokio::test]
async fn test_jsonrpc_handle_string_params() {
let calc = Calculator;
let request = json!({
"jsonrpc": "2.0",
"method": "echo",
"params": {"message": "hello world"},
"id": 3
});
let response = calc.jsonrpc_handle(request).await;
assert_eq!(response["result"], "hello world");
}
#[tokio::test]
async fn test_jsonrpc_method_not_found() {
let calc = Calculator;
let request = json!({
"jsonrpc": "2.0",
"method": "nonexistent",
"params": {},
"id": 4
});
let response = calc.jsonrpc_handle(request).await;
assert!(response["error"].is_object());
assert!(
response["error"]["message"]
.as_str()
.unwrap()
.contains("not found")
);
}
#[tokio::test]
async fn test_jsonrpc_invalid_version() {
let calc = Calculator;
let request = json!({
"jsonrpc": "1.0",
"method": "add",
"params": {"a": 1, "b": 2},
"id": 5
});
let response = calc.jsonrpc_handle(request).await;
assert!(response["error"].is_object());
assert_eq!(response["error"]["code"], -32600);
}
#[tokio::test]
async fn test_jsonrpc_notification_no_response() {
let calc = Calculator;
let request = json!({
"jsonrpc": "2.0",
"method": "add",
"params": {"a": 1, "b": 2}
});
let response = calc.jsonrpc_handle(request).await;
assert!(response.is_null());
}
#[tokio::test]
async fn test_jsonrpc_batch_request() {
let calc = Calculator;
let request = json!([
{"jsonrpc": "2.0", "method": "add", "params": {"a": 1, "b": 2}, "id": 1},
{"jsonrpc": "2.0", "method": "multiply", "params": {"a": 3, "b": 4}, "id": 2}
]);
let response = calc.jsonrpc_handle(request).await;
assert!(response.is_array());
let arr = response.as_array().unwrap();
assert_eq!(arr.len(), 2);
assert_eq!(arr[0]["result"], 3);
assert_eq!(arr[1]["result"], 12);
}
#[tokio::test]
async fn test_jsonrpc_batch_with_notifications() {
let calc = Calculator;
let request = json!([
{"jsonrpc": "2.0", "method": "add", "params": {"a": 1, "b": 2}, "id": 1},
{"jsonrpc": "2.0", "method": "multiply", "params": {"a": 3, "b": 4}} ]);
let response = calc.jsonrpc_handle(request).await;
assert!(response.is_array());
let arr = response.as_array().unwrap();
assert_eq!(arr.len(), 1);
assert_eq!(arr[0]["result"], 3);
}
#[derive(Clone)]
struct AsyncService;
#[jsonrpc]
impl AsyncService {
pub async fn async_echo(&self, message: String) -> String {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
message
}
}
#[tokio::test]
async fn test_jsonrpc_async_method() {
let svc = AsyncService;
let request = json!({
"jsonrpc": "2.0",
"method": "async_echo",
"params": {"message": "async works"},
"id": 1
});
let response = svc.jsonrpc_handle(request).await;
assert_eq!(response["result"], "async works");
}
#[derive(Clone)]
struct CustomPathService;
#[jsonrpc(path = "/api/v1/rpc")]
impl CustomPathService {
pub fn ping(&self) -> String {
"pong".to_string()
}
}
#[test]
fn test_jsonrpc_custom_path_compiles() {
let methods = CustomPathService::jsonrpc_methods();
assert!(methods.contains(&"ping"));
}
#[test]
fn test_jsonrpc_openapi_paths_generated() {
let paths = Calculator::jsonrpc_openapi_paths();
assert_eq!(paths.len(), 1);
let rpc_path = &paths[0];
assert_eq!(rpc_path.path, "/rpc");
assert_eq!(rpc_path.method, "post");
assert!(
rpc_path
.operation
.summary
.as_ref()
.unwrap()
.contains("JSON-RPC")
);
assert!(rpc_path.operation.request_body.is_some());
assert!(rpc_path.operation.responses.contains_key("200"));
assert!(rpc_path.operation.responses.contains_key("204"));
}
#[derive(Clone)]
struct MathTools;
#[jsonrpc]
impl MathTools {
fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
fn double(&self, n: i32) -> i32 {
n * 2
}
}
#[derive(Clone)]
struct StringTools;
#[jsonrpc]
impl StringTools {
fn upper(&self, s: String) -> String {
s.to_uppercase()
}
}
#[derive(Clone)]
struct JsonRpcApp {
math: MathTools,
strings: StringTools,
}
#[jsonrpc]
impl JsonRpcApp {
fn ping(&self) -> String {
"pong".to_string()
}
fn math(&self) -> &MathTools {
&self.math
}
fn strings(&self) -> &StringTools {
&self.strings
}
}
#[test]
fn test_jsonrpc_static_mount_methods_listed() {
let methods = JsonRpcApp::jsonrpc_methods();
assert!(methods.contains(&"ping"));
assert!(methods.contains(&"math.add"));
assert!(methods.contains(&"math.double"));
assert!(methods.contains(&"strings.upper"));
}
#[tokio::test]
async fn test_jsonrpc_static_mount_dispatch() {
let app = JsonRpcApp {
math: MathTools,
strings: StringTools,
};
let response = app
.jsonrpc_handle(json!({
"jsonrpc": "2.0",
"method": "ping",
"params": {},
"id": 1
}))
.await;
assert_eq!(response["result"], "pong");
let response = app
.jsonrpc_handle(json!({
"jsonrpc": "2.0",
"method": "math.add",
"params": {"a": 10, "b": 5},
"id": 2
}))
.await;
assert_eq!(response["result"], 15);
let response = app
.jsonrpc_handle(json!({
"jsonrpc": "2.0",
"method": "strings.upper",
"params": {"s": "hello"},
"id": 3
}))
.await;
assert_eq!(response["result"], "HELLO");
}
#[tokio::test]
async fn test_jsonrpc_static_mount_double() {
let app = JsonRpcApp {
math: MathTools,
strings: StringTools,
};
let response = app
.jsonrpc_handle(json!({
"jsonrpc": "2.0",
"method": "math.double",
"params": {"n": 21},
"id": 1
}))
.await;
assert_eq!(response["result"], 42);
}
#[derive(Clone)]
struct JsonRpcSlugApp {
math: MathTools,
}
#[jsonrpc]
impl JsonRpcSlugApp {
fn calc(&self, id: String) -> &MathTools {
let _ = &id;
&self.math
}
}
#[test]
fn test_jsonrpc_slug_mount_methods_listed() {
let methods = JsonRpcSlugApp::jsonrpc_methods();
assert!(methods.contains(&"calc.add"));
assert!(methods.contains(&"calc.double"));
}
#[tokio::test]
async fn test_jsonrpc_slug_mount_dispatch() {
let app = JsonRpcSlugApp { math: MathTools };
let response = app
.jsonrpc_handle(json!({
"jsonrpc": "2.0",
"method": "calc.add",
"params": {"id": "calc-1", "a": 3, "b": 4},
"id": 1
}))
.await;
assert_eq!(response["result"], 7);
}
#[test]
fn test_jsonrpc_mount_trait_implemented() {
use server_less::JsonRpcMount;
let methods = <MathTools as JsonRpcMount>::jsonrpc_mount_methods();
assert_eq!(methods.len(), 2);
assert!(methods.contains(&"add".to_string()));
assert!(methods.contains(&"double".to_string()));
}