1use std::{fmt, io};
2
3#[derive(Debug)]
5#[non_exhaustive]
6pub enum Error {
7 Server { code: String, message: String },
9 Protocol(String),
11 Io(io::Error),
13 Timeout(String),
18 TooLarge(String),
20 TooManyEntries(String),
23 Partial { written: usize, source: Box<Error> },
26 OperationStateUnknown,
29}
30
31pub type Result<T> = std::result::Result<T, Error>;
32
33impl Error {
34 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 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
118pub(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}