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}
27
28pub type Result<T> = std::result::Result<T, Error>;
29
30impl Error {
31 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 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
113pub(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}