1use std::collections::HashMap;
4
5type ErrorDatum = Box<dyn erased_serde::Serialize + Send + 'static>;
7
8#[derive(serde::Serialize)]
10pub struct RpcError {
11 message: String,
13 #[serde(serialize_with = "ser_code")]
15 code: RpcErrorKind,
16 #[serde(serialize_with = "ser_kind")]
18 kinds: AnyErrorKind,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 data: Option<HashMap<String, ErrorDatum>>,
22}
23
24impl RpcError {
25 pub fn new(message: String, code: RpcErrorKind) -> Self {
27 Self {
28 message,
29 code,
30 kinds: AnyErrorKind::Rpc(code),
31 data: None,
32 }
33 }
34
35 pub fn set_kind(&mut self, kind: tor_error::ErrorKind) {
37 self.kinds = AnyErrorKind::Tor(kind);
38 }
39
40 pub fn set_datum<D>(
45 &mut self,
46 keyword: String,
47 datum: D,
48 ) -> Result<(), crate::InvalidRpcIdentifier>
49 where
50 D: serde::Serialize + Send + 'static,
51 {
52 crate::is_valid_rpc_identifier(None, &keyword)?;
53 self.data
54 .get_or_insert_with(HashMap::new)
55 .insert(keyword, Box::new(datum) as _);
56
57 Ok(())
58 }
59
60 pub fn is_internal(&self) -> bool {
62 matches!(
63 self.kinds,
64 AnyErrorKind::Tor(tor_error::ErrorKind::Internal)
65 | AnyErrorKind::Rpc(RpcErrorKind::InternalError)
66 )
67 }
68}
69
70impl<T> From<T> for RpcError
71where
72 T: std::error::Error + tor_error::HasKind + Send + 'static,
73{
74 fn from(value: T) -> RpcError {
75 use tor_error::ErrorReport as _;
76 let message = value.report().to_string();
77 let code = kind_to_code(value.kind());
78 let kinds = AnyErrorKind::Tor(value.kind());
79 RpcError {
80 message,
81 code,
82 kinds,
83 data: None,
84 }
85 }
86}
87
88fn ser_kind<S: serde::Serializer>(kind: &AnyErrorKind, s: S) -> Result<S::Ok, S::Error> {
90 use serde::ser::SerializeSeq;
94 let mut seq = s.serialize_seq(None)?;
95 match kind {
96 AnyErrorKind::Tor(kind) => seq.serialize_element(&format!("arti:{:?}", kind))?,
97 AnyErrorKind::Rpc(kind) => seq.serialize_element(&format!("rpc:{:?}", kind))?,
98 }
99 seq.end()
100}
101
102fn ser_code<S: serde::Serializer>(kind: &RpcErrorKind, s: S) -> Result<S::Ok, S::Error> {
104 s.serialize_i32(*kind as i32)
105}
106
107#[derive(Clone, Copy, Debug)]
109enum AnyErrorKind {
110 Tor(tor_error::ErrorKind),
112 #[allow(unused)]
114 Rpc(RpcErrorKind),
115}
116
117#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126#[repr(i32)]
127#[non_exhaustive]
128pub enum RpcErrorKind {
129 InvalidRequest = -32600,
131 NoSuchMethod = -32601,
133 InvalidMethodParameters = -32602,
135 InternalError = -32603,
137 ObjectNotFound = 1,
139 RequestError = 2,
141 MethodNotImpl = 3,
143 RequestCancelled = 4,
145 FeatureNotPresent = 5,
147}
148
149fn kind_to_code(kind: tor_error::ErrorKind) -> RpcErrorKind {
154 use tor_error::ErrorKind as EK;
155 use RpcErrorKind as RC;
156 match kind {
157 EK::Internal | EK::BadApiUsage => RC::InternalError,
158 _ => RC::RequestError, }
160}
161
162impl std::fmt::Debug for RpcError {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 f.debug_struct("RpcError")
165 .field("message", &self.message)
166 .field("code", &self.code)
167 .field("kinds", &self.kinds)
168 .finish()
169 }
170}
171
172#[cfg(test)]
173mod test {
174 #![allow(clippy::bool_assert_comparison)]
176 #![allow(clippy::clone_on_copy)]
177 #![allow(clippy::dbg_macro)]
178 #![allow(clippy::mixed_attributes_style)]
179 #![allow(clippy::print_stderr)]
180 #![allow(clippy::print_stdout)]
181 #![allow(clippy::single_char_pattern)]
182 #![allow(clippy::unwrap_used)]
183 #![allow(clippy::unchecked_duration_subtraction)]
184 #![allow(clippy::useless_vec)]
185 #![allow(clippy::needless_pass_by_value)]
186 use super::*;
189
190 #[derive(Debug, thiserror::Error, serde::Serialize)]
191 enum ExampleError {
192 #[error("The {} exploded because {}", what, why)]
193 SomethingExploded { what: String, why: String },
194
195 #[error("I'm hiding the {0} in my {1}")]
196 SomethingWasHidden(String, String),
197
198 #[error("The {0} was missing")]
199 SomethingWasMissing(String),
200
201 #[error("I don't feel up to it today")]
202 ProgramUnwilling,
203 }
204
205 impl tor_error::HasKind for ExampleError {
206 fn kind(&self) -> tor_error::ErrorKind {
207 match self {
208 Self::SomethingExploded { .. } => tor_error::ErrorKind::Other,
209 Self::SomethingWasHidden(_, _) => tor_error::ErrorKind::RemoteHostNotFound,
210 Self::SomethingWasMissing(_) => tor_error::ErrorKind::FeatureDisabled,
211 Self::ProgramUnwilling => tor_error::ErrorKind::Internal,
212 }
213 }
214 }
215
216 macro_rules! assert_json_eq {
218 ($a:expr, $b:expr) => {
219 let json_a: serde_json::Value = serde_json::from_str($a).unwrap();
220 let json_b: serde_json::Value = serde_json::from_str($b).unwrap();
221 assert_eq!(json_a, json_b);
222 };
223 }
224
225 #[test]
226 fn serialize_error() {
227 let err = ExampleError::SomethingExploded {
228 what: "previous implementation".into(),
229 why: "worse things happen at C".into(),
230 };
231 let err = RpcError::from(err);
232 assert_eq!(err.code, RpcErrorKind::RequestError);
233 let serialized = serde_json::to_string(&err).unwrap();
234 let expected_json = r#"
235 {
236 "message": "error: The previous implementation exploded because worse things happen at C",
237 "code": 2,
238 "kinds": ["arti:Other"]
239 }
240 "#;
241 assert_json_eq!(&serialized, expected_json);
242
243 let err = ExampleError::SomethingWasHidden(
244 "zircon-encrusted tweezers".into(),
245 "chrome dinette".into(),
246 );
247 let err = RpcError::from(err);
248 let serialized = serde_json::to_string(&err).unwrap();
249 let expected = r#"
250 {
251 "message": "error: I'm hiding the zircon-encrusted tweezers in my chrome dinette",
252 "code": 2,
253 "kinds": ["arti:RemoteHostNotFound"]
254 }
255 "#;
256 assert_json_eq!(&serialized, expected);
257
258 let err = ExampleError::SomethingWasMissing("turbo-encabulator".into());
259 let err = RpcError::from(err);
260 let serialized = serde_json::to_string(&err).unwrap();
261 let expected = r#"
262 {
263 "message": "error: The turbo-encabulator was missing",
264 "code": 2,
265 "kinds": ["arti:FeatureDisabled"]
266 }
267 "#;
268 assert_json_eq!(&serialized, expected);
269
270 let err = ExampleError::ProgramUnwilling;
271 let err = RpcError::from(err);
272 let serialized = serde_json::to_string(&err).unwrap();
273 let expected = r#"
274 {
275 "message": "error: I don't feel up to it today",
276 "code": -32603,
277 "kinds": ["arti:Internal"]
278 }
279 "#;
280 assert_json_eq!(&serialized, expected);
281 }
282
283 #[test]
284 fn create_error() {
285 let mut e = RpcError::new("Example error".to_string(), RpcErrorKind::RequestError);
286 e.set_kind(tor_error::ErrorKind::CacheCorrupted);
287 e.set_datum("rpc:example".to_string(), "Hello world".to_string())
288 .unwrap();
289 let serialized = serde_json::to_string(&e).unwrap();
290 let expected = r#"
291 {
292 "message": "Example error",
293 "code": 2,
294 "kinds": ["arti:CacheCorrupted"],
295 "data": {
296 "rpc:example": "Hello world"
297 }
298 }
299 "#;
300 assert_json_eq!(&serialized, expected);
301 }
302}