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}
27
28pub type Result<T> = std::result::Result<T, Error>;
29
30impl Error {
31  /// Server error code, if this is a server error. A timeout has the code `DEADLINE_EXCEEDED`.
32  /// A partial write has the code of its cause.
33  pub fn code(&self) -> Option<&str> {
34    match self {
35      Error::Server { code, .. } => Some(code),
36      Error::Timeout(_) => Some("DEADLINE_EXCEEDED"),
37      Error::Partial { source, .. } => source.code(),
38      _ => None,
39    }
40  }
41
42  /// Copies the error. An I/O error keeps its kind and text.
43  pub(crate) fn dup(&self) -> Error {
44    match self {
45      Error::Server { code, message } => Error::Server {
46        code: code.clone(),
47        message: message.clone(),
48      },
49      Error::Protocol(m) => Error::Protocol(m.clone()),
50      Error::Io(e) => Error::Io(io::Error::new(e.kind(), e.to_string())),
51      Error::Timeout(m) => Error::Timeout(m.clone()),
52      Error::TooLarge(m) => Error::TooLarge(m.clone()),
53      Error::TooManyEntries(m) => Error::TooManyEntries(m.clone()),
54      Error::Partial { written, source } => Error::Partial {
55        written: *written,
56        source: Box::new(source.dup()),
57      },
58    }
59  }
60
61  pub(crate) fn server(code: &str, message: &str) -> Error {
62    Error::Server {
63      code: code.into(),
64      message: message.into(),
65    }
66  }
67}
68
69impl fmt::Display for Error {
70  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71    match self {
72      Error::Server { code, message } if message.is_empty() => write!(f, "{code}"),
73      Error::Server { code, message } => write!(f, "{code}: {message}"),
74      Error::Protocol(m) => write!(f, "protocol error: {m}"),
75      Error::Io(e) => write!(f, "io error: {e}"),
76      Error::Timeout(m) => write!(f, "DEADLINE_EXCEEDED: {m}"),
77      Error::TooLarge(m) => write!(f, "request too large: {m}"),
78      Error::TooManyEntries(m) => write!(f, "too many entries: {m}"),
79      Error::Partial { written, source } => write!(f, "partial write of {written} bytes: {source}"),
80    }
81  }
82}
83
84impl std::error::Error for Error {
85  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
86    match self {
87      Error::Io(e) => Some(e),
88      Error::Partial { source, .. } => Some(source.as_ref()),
89      _ => None,
90    }
91  }
92}
93
94impl From<io::Error> for Error {
95  fn from(e: io::Error) -> Error {
96    Error::Io(e)
97  }
98}
99
100impl From<Error> for io::Error {
101  fn from(e: Error) -> io::Error {
102    let kind = match &e {
103      Error::TooLarge(_) | Error::TooManyEntries(_) => io::ErrorKind::InvalidInput,
104      e => e.code().map_or(io::ErrorKind::Other, kind_for_code),
105    };
106    match e {
107      Error::Io(e) => e,
108      e => io::Error::new(kind, e),
109    }
110  }
111}
112
113/// Maps a server error code to the closest `io::ErrorKind`. A code without a close kind maps to
114/// `Other`. The code string stays available through `Error::code`.
115pub(crate) fn kind_for_code(code: &str) -> io::ErrorKind {
116  use io::ErrorKind as K;
117  match code {
118    "ENOENT" => K::NotFound,
119    "EACCES" | "EPERM" => K::PermissionDenied,
120    "EEXIST" => K::AlreadyExists,
121    "EINVAL" => K::InvalidInput,
122    "ENOTDIR" => K::NotADirectory,
123    "EISDIR" => K::IsADirectory,
124    "ENOTEMPTY" => K::DirectoryNotEmpty,
125    "EROFS" => K::ReadOnlyFilesystem,
126    "ENOSPC" => K::StorageFull,
127    "EFBIG" => K::FileTooLarge,
128    "EXDEV" => K::CrossesDevices,
129    "EMLINK" => K::TooManyLinks,
130    "EBUSY" => K::ResourceBusy,
131    "ENAMETOOLONG" => K::InvalidFilename,
132    "EDQUOT" => K::QuotaExceeded,
133    "ESTALE" => K::StaleNetworkFileHandle,
134    "EAGAIN" => K::WouldBlock,
135    "EINTR" => K::Interrupted,
136    "ETIMEDOUT" | "DEADLINE_EXCEEDED" => K::TimedOut,
137    "ENOTSUP" | "ENOSYS" | "UNSUPPORTED_OPERATION" | "UNSUPPORTED_VERSION" => K::Unsupported,
138    "SESSION_CLOSED" => K::ConnectionAborted,
139    "FRAME_TOO_LARGE" => K::InvalidInput,
140    "MALFORMED_FRAME" => K::InvalidData,
141    "PERMISSION_DENIED_PEER" => K::PermissionDenied,
142    "TOO_MANY_INFLIGHT" => K::WouldBlock,
143    _ => K::Other,
144  }
145}
146
147#[cfg(test)]
148mod tests {
149  use super::*;
150  use io::ErrorKind as K;
151
152  #[test]
153  fn every_spec_code_maps() {
154    let want = [
155      ("ENOENT", K::NotFound),
156      ("EACCES", K::PermissionDenied),
157      ("EPERM", K::PermissionDenied),
158      ("EEXIST", K::AlreadyExists),
159      ("EINVAL", K::InvalidInput),
160      ("ENOTDIR", K::NotADirectory),
161      ("EISDIR", K::IsADirectory),
162      ("ENOTEMPTY", K::DirectoryNotEmpty),
163      ("EROFS", K::ReadOnlyFilesystem),
164      ("ENOSPC", K::StorageFull),
165      ("EFBIG", K::FileTooLarge),
166      ("EXDEV", K::CrossesDevices),
167      ("EMLINK", K::TooManyLinks),
168      ("EBUSY", K::ResourceBusy),
169      ("ENAMETOOLONG", K::InvalidFilename),
170      ("EDQUOT", K::QuotaExceeded),
171      ("ESTALE", K::StaleNetworkFileHandle),
172      ("EAGAIN", K::WouldBlock),
173      ("UNSUPPORTED_OPERATION", K::Unsupported),
174      ("UNSUPPORTED_VERSION", K::Unsupported),
175      ("DEADLINE_EXCEEDED", K::TimedOut),
176      ("ETIMEDOUT", K::TimedOut),
177      ("EINTR", K::Interrupted),
178      ("ENOTSUP", K::Unsupported),
179      ("ENOSYS", K::Unsupported),
180      ("SESSION_CLOSED", K::ConnectionAborted),
181      ("FRAME_TOO_LARGE", K::InvalidInput),
182      ("MALFORMED_FRAME", K::InvalidData),
183      ("PERMISSION_DENIED_PEER", K::PermissionDenied),
184      ("TOO_MANY_INFLIGHT", K::WouldBlock),
185      ("EIO", K::Other),
186      ("EBADF", K::Other),
187      ("ELOOP", K::Other),
188      ("EMFILE", K::Other),
189      ("EUNKNOWN", K::Other),
190    ];
191    for (code, kind) in want {
192      let e: io::Error = Error::server(code, "m").into();
193      assert_eq!(e.kind(), kind, "{code}");
194    }
195  }
196
197  #[test]
198  fn code_survives_conversion() {
199    let e: io::Error = Error::server("ENOSPC", "disk full").into();
200    let inner = e.get_ref().unwrap().downcast_ref::<Error>().unwrap();
201    assert_eq!(inner.code(), Some("ENOSPC"));
202  }
203
204  #[test]
205  fn timeout_too_large_and_partial_convert() {
206    let e: io::Error = Error::Timeout("t".into()).into();
207    assert_eq!(e.kind(), K::TimedOut);
208    assert_eq!(Error::Timeout("t".into()).code(), Some("DEADLINE_EXCEEDED"));
209    let e: io::Error = Error::TooLarge("x".into()).into();
210    assert_eq!(e.kind(), K::InvalidInput);
211    let p = Error::Partial {
212      written: 5,
213      source: Box::new(Error::server("ENOSPC", "full")),
214    };
215    assert_eq!(p.code(), Some("ENOSPC"));
216    let e: io::Error = p.dup().into();
217    assert_eq!(e.kind(), K::StorageFull);
218  }
219
220  #[test]
221  fn non_server_errors_keep_other_kind() {
222    let e: io::Error = Error::Protocol("x".into()).into();
223    assert_eq!(e.kind(), K::Other);
224  }
225}