1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
use std::{borrow::Cow, fmt::Debug, hash::Hash};

use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::{error, warn};

/// A marker trait for a type suitable for use as an error code when constructing an [`Error`].
///
/// The implementing type must also implement `Into<(i64, &'static str)>` where the tuple represents
/// the "code" and "message" fields of the `Error`.
///
/// As per the JSON-RPC specification, the code must not fall in the reserved range, i.e. it must
/// not be between -32768 and -32000 inclusive.
///
/// Generally the "message" will be a brief const &str, where additional request-specific info can
/// be provided via the `additional_info` parameter of [`Error::new`].
///
/// # Example
///
/// ```
/// use serde::{Deserialize, Serialize};
/// use casper_json_rpc::ErrorCodeT;
///
/// #[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
/// #[repr(i64)]
/// pub enum ErrorCode {
///     /// The requested item was not found.
///     NoSuchItem = -1,
///     /// Failed to put the requested item to storage.
///     FailedToPutItem = -2,
/// }
///
/// impl From<ErrorCode> for (i64, &'static str) {
///     fn from(error_code: ErrorCode) -> Self {
///         match error_code {
///             ErrorCode::NoSuchItem => (error_code as i64, "No such item"),
///             ErrorCode::FailedToPutItem => (error_code as i64, "Failed to put item"),
///         }
///     }
/// }
///
/// impl ErrorCodeT for ErrorCode {}
/// ```
pub trait ErrorCodeT:
    Into<(i64, &'static str)> + for<'de> Deserialize<'de> + Copy + Eq + Debug
{
    /// Whether this type represents reserved error codes or not.
    ///
    /// This should normally be left with the default return value of `false`.
    #[doc(hidden)]
    fn is_reserved() -> bool {
        false
    }
}

/// The various reserved codes which can be returned in the JSON-RPC response's "error" object.
///
/// See [the JSON-RPC Specification](https://www.jsonrpc.org/specification#error_object) for further
/// details.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Debug)]
#[repr(i64)]
pub enum ReservedErrorCode {
    /// Invalid JSON was received by the server.
    ParseError = -32700,
    /// The JSON sent is not a valid Request object.
    InvalidRequest = -32600,
    /// The method does not exist or is not available.
    MethodNotFound = -32601,
    /// Invalid method parameter(s).
    InvalidParams = -32602,
    /// Internal JSON-RPC error.
    InternalError = -32603,
}

impl From<ReservedErrorCode> for (i64, &'static str) {
    fn from(error_code: ReservedErrorCode) -> Self {
        match error_code {
            ReservedErrorCode::ParseError => (error_code as i64, "Parse error"),
            ReservedErrorCode::InvalidRequest => (error_code as i64, "Invalid Request"),
            ReservedErrorCode::MethodNotFound => (error_code as i64, "Method not found"),
            ReservedErrorCode::InvalidParams => (error_code as i64, "Invalid params"),
            ReservedErrorCode::InternalError => (error_code as i64, "Internal error"),
        }
    }
}

impl ErrorCodeT for ReservedErrorCode {
    fn is_reserved() -> bool {
        true
    }
}

/// An object suitable to be returned in a JSON-RPC response as the "error" field.
///
/// See [the JSON-RPC Specification](https://www.jsonrpc.org/specification#error_object) for further
/// details.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Error {
    /// A number that indicates the error type that occurred.
    code: i64,
    /// A short description of the error.
    message: Cow<'static, str>,
    /// Additional information about the error.
    #[serde(skip_serializing_if = "Option::is_none")]
    data: Option<Value>,
}

impl Error {
    /// Returns a new `Error`, converting `error_code` to the "code" and "message" fields, and
    /// JSON-encoding `additional_info` as the "data" field.
    ///
    /// Other than when providing a [`ReservedErrorCode`], the converted "code" must not fall in the
    /// reserved range as defined in the JSON-RPC specification, i.e. it must not be between -32768
    /// and -32100 inclusive.
    ///
    /// Note that in an upcoming release, the restriction will be tightened to disallow error codes
    /// in the implementation-defined server-errors range.  I.e. codes in the range -32768 to -32000
    /// inclusive will be disallowed.
    ///
    /// If the converted code is within the reserved range when it should not be, or if
    /// JSON-encoding `additional_data` fails, the returned `Self` is built from
    /// [`ReservedErrorCode::InternalError`] with the "data" field being a String providing more
    /// info on the underlying error.
    pub fn new<C: ErrorCodeT, T: Serialize>(error_code: C, additional_info: T) -> Self {
        let (code, message): (i64, &'static str) = error_code.into();

        if !C::is_reserved() && (-32768..=-32100).contains(&code) {
            warn!(%code, "provided json-rpc error code is reserved; returning internal error");
            let (code, message) = ReservedErrorCode::InternalError.into();
            return Error {
                code,
                message: Cow::Borrowed(message),
                data: Some(Value::String(format!(
                    "attempted to return reserved error code {}",
                    code
                ))),
            };
        }

        let data = match serde_json::to_value(additional_info) {
            Ok(Value::Null) => None,
            Ok(value) => Some(value),
            Err(error) => {
                error!(%error, "failed to json-encode additional info in json-rpc error");
                let (code, message) = ReservedErrorCode::InternalError.into();
                return Error {
                    code,
                    message: Cow::Borrowed(message),
                    data: Some(Value::String(format!(
                        "failed to json-encode additional info in json-rpc error: {}",
                        error
                    ))),
                };
            }
        };

        Error {
            code,
            message: Cow::Borrowed(message),
            data,
        }
    }
}

#[cfg(test)]
mod tests {
    use serde::ser::{Error as _, Serializer};

    use super::*;

    #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Debug)]
    struct TestErrorCode {
        // If `true` the error code will be one in the reserved range.
        in_reserved_range: bool,
    }

    impl From<TestErrorCode> for (i64, &'static str) {
        fn from(error_code: TestErrorCode) -> Self {
            if error_code.in_reserved_range {
                (-32768, "Invalid test error")
            } else {
                (-123, "Valid test error")
            }
        }
    }

    impl ErrorCodeT for TestErrorCode {}

    #[derive(Serialize)]
    struct AdditionalInfo {
        id: u64,
        context: &'static str,
    }

    impl Default for AdditionalInfo {
        fn default() -> Self {
            AdditionalInfo {
                id: 1314,
                context: "TEST",
            }
        }
    }

    struct FailToEncode;

    impl Serialize for FailToEncode {
        fn serialize<S: Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
            Err(S::Error::custom("won't encode"))
        }
    }

    #[test]
    fn should_construct_reserved_error() {
        const EXPECTED_WITH_DATA: &str =
            r#"{"code":-32700,"message":"Parse error","data":{"id":1314,"context":"TEST"}}"#;
        const EXPECTED_WITHOUT_DATA: &str = r#"{"code":-32601,"message":"Method not found"}"#;
        const EXPECTED_WITH_BAD_DATA: &str = r#"{"code":-32603,"message":"Internal error","data":"failed to json-encode additional info in json-rpc error: won't encode"}"#;

        let error_with_data = Error::new(ReservedErrorCode::ParseError, AdditionalInfo::default());
        let encoded = serde_json::to_string(&error_with_data).unwrap();
        assert_eq!(encoded, EXPECTED_WITH_DATA);

        let error_without_data = Error::new(ReservedErrorCode::MethodNotFound, None::<u8>);
        let encoded = serde_json::to_string(&error_without_data).unwrap();
        assert_eq!(encoded, EXPECTED_WITHOUT_DATA);

        let error_with_bad_data = Error::new(ReservedErrorCode::InvalidParams, FailToEncode);
        let encoded = serde_json::to_string(&error_with_bad_data).unwrap();
        assert_eq!(encoded, EXPECTED_WITH_BAD_DATA);
    }

    #[test]
    fn should_construct_custom_error() {
        const EXPECTED_WITH_DATA: &str =
            r#"{"code":-123,"message":"Valid test error","data":{"id":1314,"context":"TEST"}}"#;
        const EXPECTED_WITHOUT_DATA: &str = r#"{"code":-123,"message":"Valid test error"}"#;
        const EXPECTED_WITH_BAD_DATA: &str = r#"{"code":-32603,"message":"Internal error","data":"failed to json-encode additional info in json-rpc error: won't encode"}"#;

        let good_error_code = TestErrorCode {
            in_reserved_range: false,
        };

        let error_with_data = Error::new(good_error_code, AdditionalInfo::default());
        let encoded = serde_json::to_string(&error_with_data).unwrap();
        assert_eq!(encoded, EXPECTED_WITH_DATA);

        let error_without_data = Error::new(good_error_code, ());
        let encoded = serde_json::to_string(&error_without_data).unwrap();
        assert_eq!(encoded, EXPECTED_WITHOUT_DATA);

        let error_with_bad_data = Error::new(good_error_code, FailToEncode);
        let encoded = serde_json::to_string(&error_with_bad_data).unwrap();
        assert_eq!(encoded, EXPECTED_WITH_BAD_DATA);
    }

    #[test]
    fn should_fall_back_to_internal_error_on_bad_custom_error() {
        const EXPECTED: &str = r#"{"code":-32603,"message":"Internal error","data":"attempted to return reserved error code -32603"}"#;

        let bad_error_code = TestErrorCode {
            in_reserved_range: true,
        };

        let error_with_data = Error::new(bad_error_code, AdditionalInfo::default());
        let encoded = serde_json::to_string(&error_with_data).unwrap();
        assert_eq!(encoded, EXPECTED);

        let error_without_data = Error::new(bad_error_code, None::<u8>);
        let encoded = serde_json::to_string(&error_without_data).unwrap();
        assert_eq!(encoded, EXPECTED);

        let error_with_bad_data = Error::new(bad_error_code, FailToEncode);
        let encoded = serde_json::to_string(&error_with_bad_data).unwrap();
        assert_eq!(encoded, EXPECTED);
    }
}