Skip to main content

turul_rpc_core/
response.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::error::JsonRpcError;
5use crate::types::{JsonRpcVersion, RequestId};
6
7/// Result data for a JSON-RPC response
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(untagged)]
10pub enum ResponseResult {
11    /// Success result with data
12    Success(Value),
13    /// Null result (for void methods)
14    Null,
15}
16
17impl ResponseResult {
18    pub fn success(value: Value) -> Self {
19        ResponseResult::Success(value)
20    }
21
22    pub fn null() -> Self {
23        ResponseResult::Null
24    }
25
26    pub fn is_null(&self) -> bool {
27        matches!(self, ResponseResult::Null)
28    }
29
30    pub fn as_value(&self) -> Option<&Value> {
31        match self {
32            ResponseResult::Success(value) => Some(value),
33            ResponseResult::Null => None,
34        }
35    }
36}
37
38impl From<Value> for ResponseResult {
39    fn from(value: Value) -> Self {
40        if value.is_null() {
41            ResponseResult::Null
42        } else {
43            ResponseResult::Success(value)
44        }
45    }
46}
47
48impl From<()> for ResponseResult {
49    fn from(_: ()) -> Self {
50        ResponseResult::Null
51    }
52}
53
54/// A successful JSON-RPC response object (JSON-RPC 2.0 §5, success case):
55/// `{jsonrpc, id, result}`.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct JsonRpcSuccessResponse {
58    #[serde(rename = "jsonrpc")]
59    pub version: JsonRpcVersion,
60    pub id: RequestId,
61    pub result: ResponseResult,
62}
63
64impl JsonRpcSuccessResponse {
65    pub fn new(id: RequestId, result: ResponseResult) -> Self {
66        Self {
67            version: JsonRpcVersion::V2_0,
68            id,
69            result,
70        }
71    }
72
73    pub fn success(id: RequestId, result: Value) -> Self {
74        Self::new(id, ResponseResult::Success(result))
75    }
76
77    pub fn null(id: RequestId) -> Self {
78        Self::new(id, ResponseResult::Null)
79    }
80}
81
82impl<T> From<(RequestId, T)> for JsonRpcSuccessResponse
83where
84    T: Into<ResponseResult>,
85{
86    fn from((id, result): (RequestId, T)) -> Self {
87        Self::new(id, result.into())
88    }
89}
90
91/// A JSON-RPC Response object (JSON-RPC 2.0 §5): success XOR error.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93#[serde(untagged)]
94pub enum JsonRpcResponse {
95    /// Successful response carrying a `result`.
96    Success(JsonRpcSuccessResponse),
97    /// Error response carrying an `error`.
98    Error(JsonRpcError),
99}
100
101impl JsonRpcResponse {
102    /// Build a success response.
103    pub fn success(id: RequestId, result: ResponseResult) -> Self {
104        Self::Success(JsonRpcSuccessResponse::new(id, result))
105    }
106
107    /// Build an error response.
108    pub fn error(error: JsonRpcError) -> Self {
109        Self::Error(error)
110    }
111
112    /// True if this is the error variant.
113    pub fn is_error(&self) -> bool {
114        matches!(self, JsonRpcResponse::Error(_))
115    }
116
117    /// The request id this response correlates to, if present.
118    pub fn id(&self) -> Option<&RequestId> {
119        match self {
120            JsonRpcResponse::Success(r) => Some(&r.id),
121            JsonRpcResponse::Error(e) => e.id.as_ref(),
122        }
123    }
124}
125
126impl From<JsonRpcSuccessResponse> for JsonRpcResponse {
127    fn from(r: JsonRpcSuccessResponse) -> Self {
128        Self::Success(r)
129    }
130}
131
132impl From<JsonRpcError> for JsonRpcResponse {
133    fn from(e: JsonRpcError) -> Self {
134        Self::Error(e)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::error::{JsonRpcError, JsonRpcErrorObject};
142    use serde_json::{Value, from_str, json, to_string};
143
144    #[test]
145    fn success_response_serializes_with_result_no_error() {
146        let r = JsonRpcSuccessResponse::success(RequestId::Number(1), json!({"ok": true}));
147        let v: Value = serde_json::to_value(&r).unwrap();
148        assert_eq!(v["jsonrpc"], "2.0");
149        assert_eq!(v["id"], 1);
150        assert!(v.get("result").is_some());
151        assert!(v.get("error").is_none());
152    }
153
154    #[test]
155    fn response_union_serializes_success_shape() {
156        let r = JsonRpcResponse::success(
157            RequestId::Number(1),
158            ResponseResult::Success(json!({"ok": true})),
159        );
160        let v: Value = serde_json::to_value(&r).unwrap();
161        assert!(v.get("result").is_some());
162        assert!(v.get("error").is_none());
163        assert!(!r.is_error());
164    }
165
166    #[test]
167    fn response_union_serializes_error_shape() {
168        let err = JsonRpcError::new(
169            Some(RequestId::Number(7)),
170            JsonRpcErrorObject {
171                code: -32601,
172                message: "Method not found".into(),
173                data: None,
174            },
175        );
176        let r = JsonRpcResponse::error(err);
177        let v: Value = serde_json::to_value(&r).unwrap();
178        assert!(v.get("error").is_some());
179        assert!(v.get("result").is_none());
180        assert!(r.is_error());
181        assert_eq!(r.id(), Some(&RequestId::Number(7)));
182    }
183
184    #[test]
185    fn response_union_deserializes_both_shapes() {
186        let ok: JsonRpcResponse = from_str(r#"{"jsonrpc":"2.0","id":1,"result":{"a":1}}"#).unwrap();
187        assert!(matches!(ok, JsonRpcResponse::Success(_)));
188        let err: JsonRpcResponse =
189            from_str(r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"x"}}"#).unwrap();
190        assert!(matches!(err, JsonRpcResponse::Error(_)));
191    }
192
193    #[test]
194    fn success_response_round_trip_and_null() {
195        let r = JsonRpcSuccessResponse::null(RequestId::String("t".into()));
196        let s = to_string(&r).unwrap();
197        let parsed: JsonRpcSuccessResponse = from_str(&s).unwrap();
198        assert_eq!(parsed.id, RequestId::String("t".into()));
199        match parsed.result {
200            ResponseResult::Success(ref val) if val.is_null() => {}
201            ResponseResult::Null => {}
202            _ => panic!("expected null-ish result, got {:?}", parsed.result),
203        }
204    }
205
206    #[test]
207    fn success_response_from_tuple() {
208        let r: JsonRpcSuccessResponse = (RequestId::Number(1), json!({"x": true})).into();
209        assert_eq!(r.id, RequestId::Number(1));
210    }
211
212    #[test]
213    fn response_result_conversion() {
214        assert!(matches!(
215            ResponseResult::from(json!({"d": 42})),
216            ResponseResult::Success(_)
217        ));
218        assert!(matches!(
219            ResponseResult::from(json!(null)),
220            ResponseResult::Null
221        ));
222        assert!(matches!(ResponseResult::from(()), ResponseResult::Null));
223    }
224}