tor_rpcbase/
err.rs

1//! Error-related functionality for RPC functions.
2
3use std::collections::HashMap;
4
5/// Alias for a type-erased value used in an error's `data` field
6type ErrorDatum = Box<dyn erased_serde::Serialize + Send + 'static>;
7
8/// An error type returned by failing RPC methods.
9#[derive(serde::Serialize)]
10pub struct RpcError {
11    /// A human-readable message.
12    message: String,
13    /// An error code inspired by json-rpc.
14    #[serde(serialize_with = "ser_code")]
15    code: RpcErrorKind,
16    /// The ErrorKind(s) of this error.
17    #[serde(serialize_with = "ser_kind")]
18    kinds: AnyErrorKind,
19    /// Map from namespaced keyword to related data.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    data: Option<HashMap<String, ErrorDatum>>,
22}
23
24impl RpcError {
25    /// Construct a new `RpcError` with the provided message and error code.
26    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    /// Change the declared kind of this error to `kind`.
36    pub fn set_kind(&mut self, kind: tor_error::ErrorKind) {
37        self.kinds = AnyErrorKind::Tor(kind);
38    }
39
40    /// Replace the `data` field named `keyword`, if any, with the object `datum`.
41    ///
42    /// Note that to conform with the spec, keyword must be a C identifier prefixed with a
43    /// namespace, as in `rpc:missing_features`
44    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    /// Return true if this is an internal error.
61    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
88/// Helper: Serialize an AnyErrorKind in RpcError.
89fn ser_kind<S: serde::Serializer>(kind: &AnyErrorKind, s: S) -> Result<S::Ok, S::Error> {
90    // Our spec says that `kinds` is a list.  Any tor_error::ErrorKind is prefixed with `arti:`,
91    // and any RpcErrorKind is prefixed with `rpc:`
92
93    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
102/// Helper: Serialize an RpcErrorKind as a numeric code.
103fn ser_code<S: serde::Serializer>(kind: &RpcErrorKind, s: S) -> Result<S::Ok, S::Error> {
104    s.serialize_i32(*kind as i32)
105}
106
107/// An ErrorKind as held by an `RpcError`
108#[derive(Clone, Copy, Debug)]
109enum AnyErrorKind {
110    /// An ErrorKind representing a non-RPC problem.
111    Tor(tor_error::ErrorKind),
112    /// An ErrorKind originating within the RPC system.
113    #[allow(unused)]
114    Rpc(RpcErrorKind),
115}
116
117/// Error kinds for RPC errors.
118///
119/// Unlike `tor_error::ErrorKind`,
120/// these codes do not represent a problem in an Arti function per se:
121/// they are only visible to the RPC system, and should only be reported there.
122///
123/// For backward compatibility with json-rpc,
124/// each of these codes has a unique numeric ID.
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126#[repr(i32)]
127#[non_exhaustive]
128pub enum RpcErrorKind {
129    /// "The JSON sent is not a valid Request object."
130    InvalidRequest = -32600,
131    /// "The method does not exist."
132    NoSuchMethod = -32601,
133    /// "Invalid method parameter(s)."
134    InvalidMethodParameters = -32602,
135    /// "The server suffered some kind of internal problem"
136    InternalError = -32603,
137    /// "Some requested object was not valid"
138    ObjectNotFound = 1,
139    /// "Some other error occurred"
140    RequestError = 2,
141    /// This method exists, but wasn't implemented on this object.
142    MethodNotImpl = 3,
143    /// This request was cancelled before it could finish.
144    RequestCancelled = 4,
145    /// This request listed a required feature that doesn't exist.
146    FeatureNotPresent = 5,
147}
148
149/// Helper: Return an error code (for backward compat with json-rpc) for an
150/// ErrorKind.
151///
152/// These are not especially helpful and nobody should really use them.
153fn 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, // (This is our catch-all "request error.")
159    }
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    // @@ begin test lint list maintained by maint/add_warning @@
175    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
187
188    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    /// Assert that two json strings deserialize to equivalent objects.
217    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}