Skip to main content

oxihuman_export/
jsonrpc_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! JSON-RPC 2.0 request/response serialization.
6
7/// A JSON-RPC 2.0 request.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct JsonRpcRequest {
11    pub jsonrpc: String,
12    pub method: String,
13    pub params: String,
14    pub id: Option<i64>,
15}
16
17/// A JSON-RPC 2.0 response.
18#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct JsonRpcResponse {
21    pub jsonrpc: String,
22    pub result: Option<String>,
23    pub error: Option<JsonRpcError>,
24    pub id: Option<i64>,
25}
26
27/// A JSON-RPC 2.0 error object.
28#[allow(dead_code)]
29#[derive(Debug, Clone)]
30pub struct JsonRpcError {
31    pub code: i32,
32    pub message: String,
33}
34
35/// Build a JSON-RPC request.
36#[allow(dead_code)]
37pub fn new_jsonrpc_request(method: &str, params_json: &str, id: Option<i64>) -> JsonRpcRequest {
38    JsonRpcRequest {
39        jsonrpc: "2.0".to_string(),
40        method: method.to_string(),
41        params: params_json.to_string(),
42        id,
43    }
44}
45
46/// Build a successful JSON-RPC response.
47#[allow(dead_code)]
48pub fn new_jsonrpc_result(result_json: &str, id: Option<i64>) -> JsonRpcResponse {
49    JsonRpcResponse {
50        jsonrpc: "2.0".to_string(),
51        result: Some(result_json.to_string()),
52        error: None,
53        id,
54    }
55}
56
57/// Build an error JSON-RPC response.
58#[allow(dead_code)]
59pub fn new_jsonrpc_error(code: i32, message: &str, id: Option<i64>) -> JsonRpcResponse {
60    JsonRpcResponse {
61        jsonrpc: "2.0".to_string(),
62        result: None,
63        error: Some(JsonRpcError {
64            code,
65            message: message.to_string(),
66        }),
67        id,
68    }
69}
70
71/// Serialize a request to JSON text.
72#[allow(dead_code)]
73pub fn serialize_request(req: &JsonRpcRequest) -> String {
74    let id_part = match req.id {
75        Some(id) => id.to_string(),
76        None => "null".to_string(),
77    };
78    format!(
79        r#"{{"jsonrpc":"{}","method":"{}","params":{},"id":{}}}"#,
80        req.jsonrpc, req.method, req.params, id_part
81    )
82}
83
84/// Serialize a response to JSON text.
85#[allow(dead_code)]
86pub fn serialize_response(resp: &JsonRpcResponse) -> String {
87    let id_part = match resp.id {
88        Some(id) => id.to_string(),
89        None => "null".to_string(),
90    };
91    if let Some(ref result) = resp.result {
92        format!(
93            r#"{{"jsonrpc":"{}","result":{},"id":{}}}"#,
94            resp.jsonrpc, result, id_part
95        )
96    } else if let Some(ref err) = resp.error {
97        format!(
98            r#"{{"jsonrpc":"{}","error":{{"code":{},"message":"{}"}},"id":{}}}"#,
99            resp.jsonrpc, err.code, err.message, id_part
100        )
101    } else {
102        format!(r#"{{"jsonrpc":"{}","id":{}}}"#, resp.jsonrpc, id_part)
103    }
104}
105
106/// Check if a response is successful.
107#[allow(dead_code)]
108pub fn is_success(resp: &JsonRpcResponse) -> bool {
109    resp.result.is_some() && resp.error.is_none()
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn request_version_is_2_0() {
118        let req = new_jsonrpc_request("add", "[1,2]", Some(1));
119        assert_eq!(req.jsonrpc, "2.0");
120    }
121
122    #[test]
123    fn request_method_stored() {
124        let req = new_jsonrpc_request("ping", "{}", None);
125        assert_eq!(req.method, "ping");
126    }
127
128    #[test]
129    fn serialize_request_contains_method() {
130        let req = new_jsonrpc_request("ping", "null", Some(1));
131        let s = serialize_request(&req);
132        assert!(s.contains("ping"));
133    }
134
135    #[test]
136    fn serialize_request_null_id() {
137        let req = new_jsonrpc_request("ping", "null", None);
138        let s = serialize_request(&req);
139        assert!(s.contains("\"id\":null"));
140    }
141
142    #[test]
143    fn success_response_is_success() {
144        let resp = new_jsonrpc_result("42", Some(1));
145        assert!(is_success(&resp));
146    }
147
148    #[test]
149    fn error_response_not_success() {
150        let resp = new_jsonrpc_error(-32600, "Invalid Request", Some(1));
151        assert!(!is_success(&resp));
152    }
153
154    #[test]
155    fn serialize_response_contains_result() {
156        let resp = new_jsonrpc_result("99", Some(1));
157        let s = serialize_response(&resp);
158        assert!(s.contains("result"));
159    }
160
161    #[test]
162    fn serialize_error_contains_code() {
163        let resp = new_jsonrpc_error(-32600, "Bad", Some(1));
164        let s = serialize_response(&resp);
165        assert!(s.contains("-32600"));
166    }
167
168    #[test]
169    fn error_code_stored() {
170        let resp = new_jsonrpc_error(-32700, "Parse error", None);
171        assert_eq!(resp.error.as_ref().expect("should succeed").code, -32700);
172    }
173
174    #[test]
175    fn id_none_null_in_output() {
176        let resp = new_jsonrpc_result("true", None);
177        let s = serialize_response(&resp);
178        assert!(s.contains("null"));
179    }
180}