Skip to main content

mountos_native/
error.rs

1use std::{fmt, io};
2
3/// Errors returned by the client. New variants can be added, so a `match` needs a wildcard arm.
4#[derive(Debug)]
5#[non_exhaustive]
6pub enum Error {
7  /// The server answered with an error frame. `code` is a POSIX name such as ENOENT.
8  Server { code: String, message: String },
9  /// The peer broke the frame or message contract.
10  Protocol(String),
11  /// The socket failed.
12  Io(io::Error),
13  /// A call did not finish within `Options::call_timeout`. On a pipelining connection only this
14  /// call fails and the late reply is dropped. Otherwise the connection was dropped, so a late
15  /// reply cannot reach another call, and its open files and watches are gone. The code is
16  /// `DEADLINE_EXCEEDED` and the `io::ErrorKind` is `TimedOut`.
17  Timeout(String),
18  /// A request is larger than the frame limits. Nothing was sent and the connection is intact.
19  TooLarge(String),
20  /// A directory helper hit its bound: `read_dir_all` found more entries than its limit, or
21  /// `walk` queued more pending directories than its limit. The message names the limit.
22  TooManyEntries(String),
23  /// A write that covers more than one frame failed after `written` bytes were stored.
24  /// `source` is the failure. `code` and the `io::ErrorKind` come from `source`.
25  Partial { written: usize, source: Box<Error> },
26  /// Notices were lost, so the end of an operation cannot be known. The server may still run it.
27  /// Returned by `Operation::wait`.
28  OperationStateUnknown,
29}
30
31pub type Result<T> = std::result::Result<T, Error>;
32
33impl Error {
34  /// Server error code, if this is a server error. A timeout has the code `DEADLINE_EXCEEDED`.
35  /// A partial write has the code of its cause.
36  pub fn code(&self) -> Option<&str> {
37    match self {
38      Error::Server { code, .. } => Some(code),
39      Error::Timeout(_) => Some("DEADLINE_EXCEEDED"),
40      Error::Partial { source, .. } => source.code(),
41      _ => None,
42    }
43  }
44
45  /// Copies the error. An I/O error keeps its kind and text.
46  pub(crate) fn dup(&self) -> Error {
47    match self {
48      Error::Server { code, message } => Error::Server {
49        code: code.clone(),
50        message: message.clone(),
51      },
52      Error::Protocol(m) => Error::Protocol(m.clone()),
53      Error::Io(e) => Error::Io(io::Error::new(e.kind(), e.to_string())),
54      Error::Timeout(m) => Error::Timeout(m.clone()),
55      Error::TooLarge(m) => Error::TooLarge(m.clone()),
56      Error::TooManyEntries(m) => Error::TooManyEntries(m.clone()),
57      Error::Partial { written, source } => Error::Partial {
58        written: *written,
59        source: Box::new(source.dup()),
60      },
61      Error::OperationStateUnknown => Error::OperationStateUnknown,
62    }
63  }
64
65  pub(crate) fn server(code: &str, message: &str) -> Error {
66    Error::Server {
67      code: code.into(),
68      message: message.into(),
69    }
70  }
71}
72
73impl fmt::Display for Error {
74  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75    match self {
76      Error::Server { code, message } if message.is_empty() => write!(f, "{code}"),
77      Error::Server { code, message } => write!(f, "{code}: {message}"),
78      Error::Protocol(m) => write!(f, "protocol error: {m}"),
79      Error::Io(e) => write!(f, "io error: {e}"),
80      Error::Timeout(m) => write!(f, "DEADLINE_EXCEEDED: {m}"),
81      Error::TooLarge(m) => write!(f, "request too large: {m}"),
82      Error::TooManyEntries(m) => write!(f, "too many entries: {m}"),
83      Error::Partial { written, source } => write!(f, "partial write of {written} bytes: {source}"),
84      Error::OperationStateUnknown => write!(f, "operation state unknown, notices were lost"),
85    }
86  }
87}
88
89impl std::error::Error for Error {
90  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
91    match self {
92      Error::Io(e) => Some(e),
93      Error::Partial { source, .. } => Some(source.as_ref()),
94      _ => None,
95    }
96  }
97}
98
99impl From<io::Error> for Error {
100  fn from(e: io::Error) -> Error {
101    Error::Io(e)
102  }
103}
104
105impl From<Error> for io::Error {
106  fn from(e: Error) -> io::Error {
107    let kind = match &e {
108      Error::TooLarge(_) | Error::TooManyEntries(_) => io::ErrorKind::InvalidInput,
109      e => e.code().map_or(io::ErrorKind::Other, kind_for_code),
110    };
111    match e {
112      Error::Io(e) => e,
113      e => io::Error::new(kind, e),
114    }
115  }
116}
117
118/// Maps a server error code to the closest `io::ErrorKind`. A code without a close kind maps to
119/// `Other`. The code string stays available through `Error::code`.
120pub(crate) fn kind_for_code(code: &str) -> io::ErrorKind {
121  use io::ErrorKind as K;
122  match code {
123    "ENOENT" => K::NotFound,
124    "EACCES" | "EPERM" => K::PermissionDenied,
125    "EEXIST" => K::AlreadyExists,
126    "EINVAL" => K::InvalidInput,
127    "ENOTDIR" => K::NotADirectory,
128    "EISDIR" => K::IsADirectory,
129    "ENOTEMPTY" => K::DirectoryNotEmpty,
130    "EROFS" => K::ReadOnlyFilesystem,
131    "ENOSPC" => K::StorageFull,
132    "EFBIG" => K::FileTooLarge,
133    "EXDEV" => K::CrossesDevices,
134    "EMLINK" => K::TooManyLinks,
135    "EBUSY" => K::ResourceBusy,
136    "ENAMETOOLONG" => K::InvalidFilename,
137    "EDQUOT" => K::QuotaExceeded,
138    "ESTALE" => K::StaleNetworkFileHandle,
139    "EAGAIN" => K::WouldBlock,
140    "EINTR" => K::Interrupted,
141    "ETIMEDOUT" | "DEADLINE_EXCEEDED" => K::TimedOut,
142    "ENOTSUP" | "ENOSYS" | "UNSUPPORTED_OPERATION" | "UNSUPPORTED_VERSION" => K::Unsupported,
143    "SESSION_CLOSED" => K::ConnectionAborted,
144    "FRAME_TOO_LARGE" => K::InvalidInput,
145    "MALFORMED_FRAME" => K::InvalidData,
146    "PERMISSION_DENIED_PEER" => K::PermissionDenied,
147    "TOO_MANY_INFLIGHT" => K::WouldBlock,
148    _ => K::Other,
149  }
150}
151
152#[cfg(test)]
153mod tests {
154  use super::*;
155  use io::ErrorKind as K;
156
157  #[test]
158  fn every_spec_code_maps() {
159    let want = [
160      ("ENOENT", K::NotFound),
161      ("EACCES", K::PermissionDenied),
162      ("EPERM", K::PermissionDenied),
163      ("EEXIST", K::AlreadyExists),
164      ("EINVAL", K::InvalidInput),
165      ("ENOTDIR", K::NotADirectory),
166      ("EISDIR", K::IsADirectory),
167      ("ENOTEMPTY", K::DirectoryNotEmpty),
168      ("EROFS", K::ReadOnlyFilesystem),
169      ("ENOSPC", K::StorageFull),
170      ("EFBIG", K::FileTooLarge),
171      ("EXDEV", K::CrossesDevices),
172      ("EMLINK", K::TooManyLinks),
173      ("EBUSY", K::ResourceBusy),
174      ("ENAMETOOLONG", K::InvalidFilename),
175      ("EDQUOT", K::QuotaExceeded),
176      ("ESTALE", K::StaleNetworkFileHandle),
177      ("EAGAIN", K::WouldBlock),
178      ("UNSUPPORTED_OPERATION", K::Unsupported),
179      ("UNSUPPORTED_VERSION", K::Unsupported),
180      ("DEADLINE_EXCEEDED", K::TimedOut),
181      ("ETIMEDOUT", K::TimedOut),
182      ("EINTR", K::Interrupted),
183      ("ENOTSUP", K::Unsupported),
184      ("ENOSYS", K::Unsupported),
185      ("SESSION_CLOSED", K::ConnectionAborted),
186      ("FRAME_TOO_LARGE", K::InvalidInput),
187      ("MALFORMED_FRAME", K::InvalidData),
188      ("PERMISSION_DENIED_PEER", K::PermissionDenied),
189      ("TOO_MANY_INFLIGHT", K::WouldBlock),
190      ("EIO", K::Other),
191      ("EBADF", K::Other),
192      ("ELOOP", K::Other),
193      ("EMFILE", K::Other),
194      ("EUNKNOWN", K::Other),
195    ];
196    for (code, kind) in want {
197      let e: io::Error = Error::server(code, "m").into();
198      assert_eq!(e.kind(), kind, "{code}");
199    }
200  }
201
202  #[test]
203  fn code_survives_conversion() {
204    let e: io::Error = Error::server("ENOSPC", "disk full").into();
205    let inner = e.get_ref().unwrap().downcast_ref::<Error>().unwrap();
206    assert_eq!(inner.code(), Some("ENOSPC"));
207  }
208
209  #[test]
210  fn timeout_too_large_and_partial_convert() {
211    let e: io::Error = Error::Timeout("t".into()).into();
212    assert_eq!(e.kind(), K::TimedOut);
213    assert_eq!(Error::Timeout("t".into()).code(), Some("DEADLINE_EXCEEDED"));
214    let e: io::Error = Error::TooLarge("x".into()).into();
215    assert_eq!(e.kind(), K::InvalidInput);
216    let p = Error::Partial {
217      written: 5,
218      source: Box::new(Error::server("ENOSPC", "full")),
219    };
220    assert_eq!(p.code(), Some("ENOSPC"));
221    let e: io::Error = p.dup().into();
222    assert_eq!(e.kind(), K::StorageFull);
223  }
224
225  #[test]
226  fn non_server_errors_keep_other_kind() {
227    let e: io::Error = Error::Protocol("x".into()).into();
228    assert_eq!(e.kind(), K::Other);
229  }
230}