1
//! Error-related functionality for RPC functions.
2

            
3
use std::collections::HashMap;
4

            
5
/// Alias for a type-erased value used in an error's `data` field
6
type ErrorDatum = Box<dyn erased_serde::Serialize + Send + 'static>;
7

            
8
/// An error type returned by failing RPC methods.
9
#[derive(serde::Serialize)]
10
pub 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

            
24
impl RpcError {
25
    /// Construct a new `RpcError` with the provided message and error code.
26
26
    pub fn new(message: String, code: RpcErrorKind) -> Self {
27
26
        Self {
28
26
            message,
29
26
            code,
30
26
            kinds: AnyErrorKind::Rpc(code),
31
26
            data: None,
32
26
        }
33
26
    }
34

            
35
    /// Change the declared kind of this error to `kind`.
36
2
    pub fn set_kind(&mut self, kind: tor_error::ErrorKind) {
37
2
        self.kinds = AnyErrorKind::Tor(kind);
38
2
    }
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
2
    pub fn set_datum<D>(
45
2
        &mut self,
46
2
        keyword: String,
47
2
        datum: D,
48
2
    ) -> Result<(), crate::InvalidRpcIdentifier>
49
2
    where
50
2
        D: serde::Serialize + Send + 'static,
51
    {
52
2
        crate::is_valid_rpc_identifier(None, &keyword)?;
53
2
        self.data
54
2
            .get_or_insert_with(HashMap::new)
55
2
            .insert(keyword, Box::new(datum) as _);
56

            
57
2
        Ok(())
58
2
    }
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

            
70
impl<T> From<T> for RpcError
71
where
72
    T: std::error::Error + tor_error::HasKind + Send + 'static,
73
{
74
8
    fn from(value: T) -> RpcError {
75
        use tor_error::ErrorReport as _;
76
8
        let message = value.report().to_string();
77
8
        let code = kind_to_code(value.kind());
78
8
        let kinds = AnyErrorKind::Tor(value.kind());
79
8
        RpcError {
80
8
            message,
81
8
            code,
82
8
            kinds,
83
8
            data: None,
84
8
        }
85
8
    }
86
}
87

            
88
/// Helper: Serialize an AnyErrorKind in RpcError.
89
16
fn 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
16
    let mut seq = s.serialize_seq(None)?;
95
16
    match kind {
96
10
        AnyErrorKind::Tor(kind) => seq.serialize_element(&format!("arti:{:?}", kind))?,
97
6
        AnyErrorKind::Rpc(kind) => seq.serialize_element(&format!("rpc:{:?}", kind))?,
98
    }
99
16
    seq.end()
100
16
}
101

            
102
/// Helper: Serialize an RpcErrorKind as a numeric code.
103
16
fn ser_code<S: serde::Serializer>(kind: &RpcErrorKind, s: S) -> Result<S::Ok, S::Error> {
104
16
    s.serialize_i32(*kind as i32)
105
16
}
106

            
107
/// An ErrorKind as held by an `RpcError`
108
#[derive(Clone, Copy, Debug)]
109
enum 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]
128
pub 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.
155
8
fn kind_to_code(kind: tor_error::ErrorKind) -> RpcErrorKind {
156
    use RpcErrorKind as RC;
157
    use tor_error::ErrorKind as EK;
158
8
    match kind {
159
2
        EK::Internal | EK::BadApiUsage => RC::InternalError,
160
6
        _ => RC::RequestError, // (This is our catch-all "request error.")
161
    }
162
8
}
163

            
164
impl 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)]
175
mod 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
}