maelstrom/
error.rs

1use crate::protocol::{ErrorMessageBody, MessageBody};
2use std::fmt::{Display, Formatter};
3
4/// [source](https://github.com/jepsen-io/maelstrom/blob/main/doc/protocol.md#errors).
5#[derive(Debug, PartialEq, Eq, Clone)]
6pub enum Error {
7    /// Indicates that the requested operation could not be completed within a timeout.
8    Timeout,
9    /// Use this error to indicate that a requested operation is not supported by
10    /// the current implementation. Helpful for stubbing out APIs during development.
11    NotSupported(String),
12    /// Indicates that the operation definitely cannot be performed at this time--perhaps
13    /// because the server is in a read-only state, has not yet been initialized,
14    /// believes its peers to be down, and so on. Do not use this error for indeterminate
15    /// cases, when the operation may actually have taken place.
16    TemporarilyUnavailable,
17    /// The client's request did not conform to the server's expectations,
18    /// and could not possibly have been processed.
19    MalformedRequest,
20    /// Indicates that some kind of general, indefinite error occurred.
21    /// Use this as a catch-all for errors you can't otherwise categorize,
22    /// or as a starting point for your error handler: it's safe to return
23    /// internal-error for every problem by default, then add special cases
24    /// for more specific errors later.
25    Crash,
26    /// Indicates that some kind of general, definite error occurred.
27    /// Use this as a catch-all for errors you can't otherwise categorize,
28    /// when you specifically know that the requested operation has not taken place.
29    /// For instance, you might encounter an indefinite failure during
30    /// the prepare phase of a transaction: since you haven't started the commit process yet,
31    /// the transaction can't have taken place. It's therefore safe to return
32    /// a definite abort to the client.
33    Abort,
34    /// The client requested an operation on a key which does not exist
35    /// (assuming the operation should not automatically create missing keys).
36    KeyDoesNotExist,
37    /// The client requested the creation of a key which already exists,
38    /// and the server will not overwrite it.
39    KeyAlreadyExists,
40    /// The requested operation expected some conditions to hold,
41    /// and those conditions were not met. For instance, a compare-and-set operation
42    /// might assert that the value of a key is currently 5; if the value is 3,
43    /// the server would return precondition-failed.
44    PreconditionFailed,
45    /// The requested transaction has been aborted because of a conflict with
46    /// another transaction. Servers need not return this error on every conflict:
47    /// they may choose to retry automatically instead.
48    TxnConflict,
49    /// Custom error code for anything you would like to add.
50    Custom(i32, String),
51}
52
53impl Error {
54    #[must_use] pub fn code(&self) -> i32 {
55        match self {
56            Error::Timeout => 0,
57            Error::NotSupported(_) => 10,
58            Error::TemporarilyUnavailable => 11,
59            Error::MalformedRequest => 12,
60            Error::Crash => 13,
61            Error::Abort => 14,
62            Error::KeyDoesNotExist => 20,
63            Error::KeyAlreadyExists => 21,
64            Error::PreconditionFailed => 22,
65            Error::TxnConflict => 30,
66            Error::Custom(code, _) => *code,
67        }
68    }
69
70    #[must_use] pub fn description(&self) -> &str {
71        match self {
72            Error::Timeout => "timeout",
73            Error::NotSupported(t) => t.as_str(),
74            Error::TemporarilyUnavailable => "temporarily unavailable",
75            Error::MalformedRequest => "malformed request",
76            Error::Crash => "crash",
77            Error::Abort => "abort",
78            Error::KeyDoesNotExist => "key does not exist",
79            Error::KeyAlreadyExists => "key already exists",
80            Error::PreconditionFailed => "precondition failed",
81            Error::TxnConflict => "txn conflict",
82            Error::Custom(_, text) => text.as_str(),
83        }
84    }
85}
86
87impl From<&MessageBody> for Error {
88    fn from(value: &MessageBody) -> Self {
89        if !value.is_error() {
90            return Error::Custom(-1, "serialized response that was not an error".to_string());
91        }
92
93        match value.as_obj::<ErrorMessageBody>() {
94            Err(t) => Error::Custom(Error::Crash.code(), t.to_string()),
95            Ok(obj) => Error::from(obj),
96        }
97    }
98}
99
100impl From<ErrorMessageBody> for Error {
101    fn from(value: ErrorMessageBody) -> Self {
102        match value.code {
103            0 => Error::Timeout,
104            10 => Error::NotSupported(value.text),
105            11 => Error::TemporarilyUnavailable,
106            12 => Error::MalformedRequest,
107            13 => Error::Crash,
108            14 => Error::Abort,
109            20 => Error::KeyDoesNotExist,
110            21 => Error::KeyAlreadyExists,
111            22 => Error::PreconditionFailed,
112            30 => Error::TxnConflict,
113            code => Error::Custom(code, value.text),
114        }
115    }
116}
117
118impl Display for Error {
119    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
120        write!(f, "error({}): {}", self.code(), self.description())
121    }
122}
123
124impl std::error::Error for Error {}
125
126#[cfg(test)]
127mod test {
128    use crate::protocol::MessageBody;
129    use crate::runtime::Result;
130    use crate::Error;
131
132    #[test]
133    fn parse_non_error() -> Result<()> {
134        let raw = r#"{"type":"none","msg_id":1}"#;
135        let msg: MessageBody = serde_json::from_str(&raw)?;
136        assert_eq!(msg.is_error(), false);
137
138        let err = Error::from(&msg);
139        assert_eq!(err.code(), -1);
140
141        Ok(())
142    }
143
144    #[test]
145    fn parse_not_supported_error() -> Result<()> {
146        let raw = r#"{"type":"error","msg_id":1, "code": 10}"#;
147        let msg: MessageBody = serde_json::from_str(&raw)?;
148        assert_eq!(msg.is_error(), true);
149
150        let err = Error::from(&msg);
151        let expected = Error::NotSupported("".to_string());
152        assert_eq!(err, expected);
153
154        Ok(())
155    }
156}