Skip to main content

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    /// A weak reference has expired.
148    WeakReferenceExpired = 6,
149}
150
151/// Helper: Return an error code (for backward compat with json-rpc) for an
152/// ErrorKind.
153///
154/// These are not especially helpful and nobody should really use them.
155fn kind_to_code(kind: tor_error::ErrorKind) -> RpcErrorKind {
156    use RpcErrorKind as RC;
157    use tor_error::ErrorKind as EK;
158    match kind {
159        EK::Internal | EK::BadApiUsage => RC::InternalError,
160        _ => RC::RequestError, // (This is our catch-all "request error.")
161    }
162}
163
164impl std::fmt::Debug for RpcError {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        f.debug_struct("RpcError")
167            .field("message", &self.message)
168            .field("code", &self.code)
169            .field("kinds", &self.kinds)
170            .finish()
171    }
172}
173
174#[cfg(test)]
175mod test {
176    // @@ begin test lint list maintained by maint/add_warning @@
177    #![allow(clippy::bool_assert_comparison)]
178    #![allow(clippy::clone_on_copy)]
179    #![allow(clippy::dbg_macro)]
180    #![allow(clippy::mixed_attributes_style)]
181    #![allow(clippy::print_stderr)]
182    #![allow(clippy::print_stdout)]
183    #![allow(clippy::single_char_pattern)]
184    #![allow(clippy::unwrap_used)]
185    #![allow(clippy::unchecked_time_subtraction)]
186    #![allow(clippy::useless_vec)]
187    #![allow(clippy::needless_pass_by_value)]
188    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
189
190    use super::*;
191
192    #[derive(Debug, thiserror::Error, serde::Serialize)]
193    enum ExampleError {
194        #[error("The {} exploded because {}", what, why)]
195        SomethingExploded { what: String, why: String },
196
197        #[error("I'm hiding the {0} in my {1}")]
198        SomethingWasHidden(String, String),
199
200        #[error("The {0} was missing")]
201        SomethingWasMissing(String),
202
203        #[error("I don't feel up to it today")]
204        ProgramUnwilling,
205    }
206
207    impl tor_error::HasKind for ExampleError {
208        fn kind(&self) -> tor_error::ErrorKind {
209            match self {
210                Self::SomethingExploded { .. } => tor_error::ErrorKind::Other,
211                Self::SomethingWasHidden(_, _) => tor_error::ErrorKind::RemoteHostNotFound,
212                Self::SomethingWasMissing(_) => tor_error::ErrorKind::FeatureDisabled,
213                Self::ProgramUnwilling => tor_error::ErrorKind::Internal,
214            }
215        }
216    }
217
218    /// Assert that two json strings deserialize to equivalent objects.
219    macro_rules! assert_json_eq {
220        ($a:expr, $b:expr) => {
221            let json_a: serde_json::Value = serde_json::from_str($a).unwrap();
222            let json_b: serde_json::Value = serde_json::from_str($b).unwrap();
223            assert_eq!(json_a, json_b);
224        };
225    }
226
227    #[test]
228    fn serialize_error() {
229        let err = ExampleError::SomethingExploded {
230            what: "previous implementation".into(),
231            why: "worse things happen at C".into(),
232        };
233        let err = RpcError::from(err);
234        assert_eq!(err.code, RpcErrorKind::RequestError);
235        let serialized = serde_json::to_string(&err).unwrap();
236        let expected_json = r#"
237          {
238            "message": "error: The previous implementation exploded because worse things happen at C",
239            "code": 2,
240            "kinds": ["arti:Other"]
241         }
242        "#;
243        assert_json_eq!(&serialized, expected_json);
244
245        let err = ExampleError::SomethingWasHidden(
246            "zircon-encrusted tweezers".into(),
247            "chrome dinette".into(),
248        );
249        let err = RpcError::from(err);
250        let serialized = serde_json::to_string(&err).unwrap();
251        let expected = r#"
252        {
253            "message": "error: I'm hiding the zircon-encrusted tweezers in my chrome dinette",
254            "code": 2,
255            "kinds": ["arti:RemoteHostNotFound"]
256         }
257        "#;
258        assert_json_eq!(&serialized, expected);
259
260        let err = ExampleError::SomethingWasMissing("turbo-encabulator".into());
261        let err = RpcError::from(err);
262        let serialized = serde_json::to_string(&err).unwrap();
263        let expected = r#"
264        {
265            "message": "error: The turbo-encabulator was missing",
266            "code": 2,
267            "kinds": ["arti:FeatureDisabled"]
268         }
269        "#;
270        assert_json_eq!(&serialized, expected);
271
272        let err = ExampleError::ProgramUnwilling;
273        let err = RpcError::from(err);
274        let serialized = serde_json::to_string(&err).unwrap();
275        let expected = r#"
276        {
277            "message": "error: I don't feel up to it today",
278            "code": -32603,
279            "kinds": ["arti:Internal"]
280         }
281        "#;
282        assert_json_eq!(&serialized, expected);
283    }
284
285    #[test]
286    fn create_error() {
287        let mut e = RpcError::new("Example error".to_string(), RpcErrorKind::RequestError);
288        e.set_kind(tor_error::ErrorKind::CacheCorrupted);
289        e.set_datum("rpc:example".to_string(), "Hello world".to_string())
290            .unwrap();
291        let serialized = serde_json::to_string(&e).unwrap();
292        let expected = r#"
293        {
294            "message": "Example error",
295            "code": 2,
296            "kinds": ["arti:CacheCorrupted"],
297            "data": {
298                "rpc:example": "Hello world"
299            }
300        }
301        "#;
302        assert_json_eq!(&serialized, expected);
303    }
304}