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
2
    {
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
2

            
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
}
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.
153
8
fn kind_to_code(kind: tor_error::ErrorKind) -> RpcErrorKind {
154
    use tor_error::ErrorKind as EK;
155
    use RpcErrorKind as RC;
156
8
    match kind {
157
2
        EK::Internal | EK::BadApiUsage => RC::InternalError,
158
6
        _ => RC::RequestError, // (This is our catch-all "request error.")
159
    }
160
8
}
161

            
162
impl 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)]
173
mod 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
}