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}