1mod invalid;
7use serde::{Deserialize, Serialize};
8use tor_rpcbase as rpc;
9
10#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
15#[serde(untagged)]
16pub(crate) enum RequestId {
17 Str(Box<str>),
22 Int(i64),
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, Default)]
37pub(crate) struct ReqMeta {
38 #[serde(default)]
41 pub(crate) updates: bool,
42
43 #[serde(default)]
46 pub(crate) require: Vec<String>,
47}
48
49#[derive(Debug, Deserialize)]
51pub(crate) struct Request {
52 pub(crate) id: RequestId,
56 pub(crate) obj: rpc::ObjectId,
58 #[serde(default)]
60 pub(crate) meta: ReqMeta,
61 #[serde(flatten)]
65 pub(crate) method: Box<dyn rpc::DeserMethod>,
66}
67
68#[derive(Debug, serde::Deserialize)]
72#[serde(untagged)]
73pub(crate) enum FlexibleRequest {
74 Valid(Request),
76 Invalid(invalid::InvalidRequest),
78 }
88
89#[derive(Debug, Serialize)]
91pub(crate) struct BoxedResponse {
92 #[serde(skip_serializing_if = "Option::is_none")]
99 pub(crate) id: Option<RequestId>,
100 #[serde(flatten)]
102 pub(crate) body: ResponseBody,
103}
104
105impl BoxedResponse {
106 pub(crate) fn from_error<E>(id: Option<RequestId>, error: E) -> Self
109 where
110 E: Into<rpc::RpcError>,
111 {
112 let error: rpc::RpcError = error.into();
113 let body = ResponseBody::Error(Box::new(error));
114 Self { id, body }
115 }
116}
117
118#[derive(Serialize)]
120pub(crate) enum ResponseBody {
121 #[serde(rename = "error")]
123 Error(Box<rpc::RpcError>),
124 #[serde(rename = "result")]
130 Success(Box<dyn erased_serde::Serialize + Send>),
131 #[serde(rename = "update")]
134 Update(Box<dyn erased_serde::Serialize + Send>),
135}
136
137impl ResponseBody {
138 #[cfg(test)]
141 pub(crate) fn is_final(&self) -> bool {
142 match self {
143 ResponseBody::Error(_) | ResponseBody::Success(_) => true,
144 ResponseBody::Update(_) => false,
145 }
146 }
147}
148
149impl From<rpc::RpcError> for ResponseBody {
150 fn from(inp: rpc::RpcError) -> ResponseBody {
151 ResponseBody::Error(Box::new(inp))
152 }
153}
154
155impl std::fmt::Debug for ResponseBody {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 let json = |x| match serde_json::to_string(x) {
159 Ok(s) => s,
160 Err(e) => format!("«could not serialize: {}»", e),
161 };
162 match self {
163 Self::Error(arg0) => f.debug_tuple("Error").field(arg0).finish(),
164 Self::Update(arg0) => f.debug_tuple("Update").field(&json(arg0)).finish(),
165 Self::Success(arg0) => f.debug_tuple("Success").field(&json(arg0)).finish(),
166 }
167 }
168}
169
170#[cfg(test)]
171mod test {
172 #![allow(clippy::bool_assert_comparison)]
174 #![allow(clippy::clone_on_copy)]
175 #![allow(clippy::dbg_macro)]
176 #![allow(clippy::mixed_attributes_style)]
177 #![allow(clippy::print_stderr)]
178 #![allow(clippy::print_stdout)]
179 #![allow(clippy::single_char_pattern)]
180 #![allow(clippy::unwrap_used)]
181 #![allow(clippy::unchecked_time_subtraction)]
182 #![allow(clippy::useless_vec)]
183 #![allow(clippy::needless_pass_by_value)]
184 #![allow(clippy::string_slice)] use super::*;
187 use derive_deftly::Deftly;
188 use tor_rpcbase::templates::*;
189
190 macro_rules! assert_dbg_eq {
195 ($a:expr, $b:expr) => {
196 assert_eq!(format!("{:?}", $a), format!("{:?}", $b));
197 };
198 }
199
200 #[derive(Debug, serde::Deserialize, Deftly)]
204 #[derive_deftly(DynMethod)]
205 #[deftly(rpc(method_name = "x-test:dummy"))]
206 struct DummyMethod {
207 #[serde(default)]
208 #[allow(dead_code)]
209 stuff: u64,
210 }
211
212 impl rpc::RpcMethod for DummyMethod {
213 type Output = DummyResponse;
214 type Update = rpc::NoUpdates;
215 }
216
217 #[derive(Serialize)]
218 struct DummyResponse {
219 hello: i64,
220 world: String,
221 }
222
223 #[test]
224 fn valid_requests() {
225 let parse_request = |s| match serde_json::from_str::<FlexibleRequest>(s) {
226 Ok(FlexibleRequest::Valid(req)) => req,
227 other => panic!("{:?}", other),
228 };
229
230 let r =
231 parse_request(r#"{"id": 7, "obj": "hello", "method": "x-test:dummy", "params": {} }"#);
232 assert_dbg_eq!(
233 r,
234 Request {
235 id: RequestId::Int(7),
236 obj: rpc::ObjectId::from("hello"),
237 meta: ReqMeta::default(),
238 method: Box::new(DummyMethod { stuff: 0 })
239 }
240 );
241 }
242
243 #[test]
244 fn invalid_requests() {
245 use crate::err::RequestParseError as RPE;
246 fn parsing_error(s: &str) -> RPE {
247 match serde_json::from_str::<FlexibleRequest>(s) {
248 Ok(FlexibleRequest::Invalid(req)) => req.error(),
249 x => panic!("Didn't expect {:?}", x),
250 }
251 }
252
253 macro_rules! expect_err {
254 ($p:pat, $e:expr) => {
255 let err = parsing_error($e);
256 assert!(matches!(err, $p), "Unexpected error type {:?}", err);
257 };
258 }
259
260 expect_err!(
261 RPE::IdMissing,
262 r#"{ "obj": "hello", "method": "x-test:dummy", "params": {} }"#
263 );
264 expect_err!(
265 RPE::IdType,
266 r#"{ "id": {}, "obj": "hello", "method": "x-test:dummy", "params": {} }"#
267 );
268 expect_err!(
269 RPE::ObjMissing,
270 r#"{ "id": 3, "method": "x-test:dummy", "params": {} }"#
271 );
272 expect_err!(
273 RPE::ObjType,
274 r#"{ "id": 3, "obj": 9, "method": "x-test:dummy", "params": {} }"#
275 );
276 expect_err!(
277 RPE::MethodMissing,
278 r#"{ "id": 3, "obj": "hello", "params": {} }"#
279 );
280 expect_err!(
281 RPE::MethodType,
282 r#"{ "id": 3, "obj": "hello", "method": [], "params": {} }"#
283 );
284 expect_err!(
285 RPE::MetaType,
286 r#"{ "id": 3, "obj": "hello", "meta": 7, "method": "x-test:dummy", "params": {} }"#
287 );
288 expect_err!(
289 RPE::MetaType,
290 r#"{ "id": 3, "obj": "hello", "meta": { "updates": 3}, "method": "x-test:dummy", "params": {} }"#
291 );
292 expect_err!(
293 RPE::NoSuchMethod,
294 r#"{ "id": 3, "obj": "hello", "method": "arti:this-is-not-a-method", "params": {} }"#
295 );
296 expect_err!(
297 RPE::MissingParams,
298 r#"{ "id": 3, "obj": "hello", "method": "x-test:dummy" }"#
299 );
300 expect_err!(
301 RPE::ParamType,
302 r#"{ "id": 3, "obj": "hello", "method": "x-test:dummy", "params": 7 }"#
303 );
304 }
305
306 #[test]
307 fn fmt_replies() {
308 let resp = BoxedResponse {
309 id: Some(RequestId::Int(7)),
310 body: ResponseBody::Success(Box::new(DummyResponse {
311 hello: 99,
312 world: "foo".into(),
313 })),
314 };
315 let s = serde_json::to_string(&resp).unwrap();
316 assert_eq!(s, r#"{"id":7,"result":{"hello":99,"world":"foo"}}"#);
320
321 let resp = BoxedResponse::from_error(
322 None,
323 rpc::RpcError::from(crate::err::RequestParseError::IdMissing),
324 );
325 let s = serde_json::to_string(&resp).unwrap();
326 assert_eq!(
328 s,
329 r#"{"error":{"message":"Request did not have any `id` field.","code":-32600,"kinds":["rpc:InvalidRequest"]}}"#
330 );
331 }
332
333 #[test]
334 fn response_body_is_final() {
335 let response_body_error = ResponseBody::from(rpc::RpcError::new(
336 "This is a test!".to_string(),
337 rpc::RpcErrorKind::ObjectNotFound,
338 ));
339 assert!(response_body_error.is_final());
340
341 let response_body_success = ResponseBody::Success(Box::new(DummyResponse {
342 hello: 99,
343 world: "foo".into(),
344 }));
345 assert!(response_body_success.is_final());
346
347 let response_body_update = ResponseBody::Update(Box::new(DummyResponse {
348 hello: 99,
349 world: "foo".into(),
350 }));
351 assert!(!response_body_update.is_final());
352 }
353}