Skip to main content

dolang_vfs/
error.rs

1use std::{fmt, io};
2
3use serde::{Deserialize, Serialize};
4
5use crate::target::OperatingSystem;
6
7/// Portable classification of an I/O or VFS error.
8///
9/// Most variants correspond directly to [`io::ErrorKind`].
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[non_exhaustive]
12pub enum ErrorKind {
13    NotFound,
14    PermissionDenied,
15    ConnectionRefused,
16    ConnectionReset,
17    HostUnreachable,
18    NetworkUnreachable,
19    ConnectionAborted,
20    NotConnected,
21    AddrInUse,
22    AddrNotAvailable,
23    NetworkDown,
24    BrokenPipe,
25    AlreadyExists,
26    WouldBlock,
27    NotADirectory,
28    IsADirectory,
29    DirectoryNotEmpty,
30    ReadOnlyFilesystem,
31    StaleNetworkFileHandle,
32    InvalidInput,
33    InvalidData,
34    TimedOut,
35    WriteZero,
36    StorageFull,
37    NotSeekable,
38    QuotaExceeded,
39    FileTooLarge,
40    ResourceBusy,
41    ExecutableFileBusy,
42    Deadlock,
43    CrossesDevices,
44    TooManyLinks,
45    InvalidFilename,
46    ArgumentListTooLong,
47    Interrupted,
48    Unsupported,
49    UnexpectedEof,
50    OutOfMemory,
51    Other,
52}
53
54impl From<io::ErrorKind> for ErrorKind {
55    fn from(kind: io::ErrorKind) -> Self {
56        match kind {
57            io::ErrorKind::NotFound => Self::NotFound,
58            io::ErrorKind::PermissionDenied => Self::PermissionDenied,
59            io::ErrorKind::ConnectionRefused => Self::ConnectionRefused,
60            io::ErrorKind::ConnectionReset => Self::ConnectionReset,
61            io::ErrorKind::HostUnreachable => Self::HostUnreachable,
62            io::ErrorKind::NetworkUnreachable => Self::NetworkUnreachable,
63            io::ErrorKind::ConnectionAborted => Self::ConnectionAborted,
64            io::ErrorKind::NotConnected => Self::NotConnected,
65            io::ErrorKind::AddrInUse => Self::AddrInUse,
66            io::ErrorKind::AddrNotAvailable => Self::AddrNotAvailable,
67            io::ErrorKind::NetworkDown => Self::NetworkDown,
68            io::ErrorKind::BrokenPipe => Self::BrokenPipe,
69            io::ErrorKind::AlreadyExists => Self::AlreadyExists,
70            io::ErrorKind::WouldBlock => Self::WouldBlock,
71            io::ErrorKind::NotADirectory => Self::NotADirectory,
72            io::ErrorKind::IsADirectory => Self::IsADirectory,
73            io::ErrorKind::DirectoryNotEmpty => Self::DirectoryNotEmpty,
74            io::ErrorKind::ReadOnlyFilesystem => Self::ReadOnlyFilesystem,
75            io::ErrorKind::StaleNetworkFileHandle => Self::StaleNetworkFileHandle,
76            io::ErrorKind::InvalidInput => Self::InvalidInput,
77            io::ErrorKind::InvalidData => Self::InvalidData,
78            io::ErrorKind::TimedOut => Self::TimedOut,
79            io::ErrorKind::WriteZero => Self::WriteZero,
80            io::ErrorKind::StorageFull => Self::StorageFull,
81            io::ErrorKind::NotSeekable => Self::NotSeekable,
82            io::ErrorKind::QuotaExceeded => Self::QuotaExceeded,
83            io::ErrorKind::FileTooLarge => Self::FileTooLarge,
84            io::ErrorKind::ResourceBusy => Self::ResourceBusy,
85            io::ErrorKind::ExecutableFileBusy => Self::ExecutableFileBusy,
86            io::ErrorKind::Deadlock => Self::Deadlock,
87            io::ErrorKind::CrossesDevices => Self::CrossesDevices,
88            io::ErrorKind::TooManyLinks => Self::TooManyLinks,
89            io::ErrorKind::InvalidFilename => Self::InvalidFilename,
90            io::ErrorKind::ArgumentListTooLong => Self::ArgumentListTooLong,
91            io::ErrorKind::Interrupted => Self::Interrupted,
92            io::ErrorKind::Unsupported => Self::Unsupported,
93            io::ErrorKind::UnexpectedEof => Self::UnexpectedEof,
94            io::ErrorKind::OutOfMemory => Self::OutOfMemory,
95            _ => Self::Other,
96        }
97    }
98}
99
100impl From<ErrorKind> for io::ErrorKind {
101    fn from(kind: ErrorKind) -> Self {
102        match kind {
103            ErrorKind::NotFound => Self::NotFound,
104            ErrorKind::PermissionDenied => Self::PermissionDenied,
105            ErrorKind::ConnectionRefused => Self::ConnectionRefused,
106            ErrorKind::ConnectionReset => Self::ConnectionReset,
107            ErrorKind::HostUnreachable => Self::HostUnreachable,
108            ErrorKind::NetworkUnreachable => Self::NetworkUnreachable,
109            ErrorKind::ConnectionAborted => Self::ConnectionAborted,
110            ErrorKind::NotConnected => Self::NotConnected,
111            ErrorKind::AddrInUse => Self::AddrInUse,
112            ErrorKind::AddrNotAvailable => Self::AddrNotAvailable,
113            ErrorKind::NetworkDown => Self::NetworkDown,
114            ErrorKind::BrokenPipe => Self::BrokenPipe,
115            ErrorKind::AlreadyExists => Self::AlreadyExists,
116            ErrorKind::WouldBlock => Self::WouldBlock,
117            ErrorKind::NotADirectory => Self::NotADirectory,
118            ErrorKind::IsADirectory => Self::IsADirectory,
119            ErrorKind::DirectoryNotEmpty => Self::DirectoryNotEmpty,
120            ErrorKind::ReadOnlyFilesystem => Self::ReadOnlyFilesystem,
121            ErrorKind::StaleNetworkFileHandle => Self::StaleNetworkFileHandle,
122            ErrorKind::InvalidInput => Self::InvalidInput,
123            ErrorKind::InvalidData => Self::InvalidData,
124            ErrorKind::TimedOut => Self::TimedOut,
125            ErrorKind::WriteZero => Self::WriteZero,
126            ErrorKind::StorageFull => Self::StorageFull,
127            ErrorKind::NotSeekable => Self::NotSeekable,
128            ErrorKind::QuotaExceeded => Self::QuotaExceeded,
129            ErrorKind::FileTooLarge => Self::FileTooLarge,
130            ErrorKind::ResourceBusy => Self::ResourceBusy,
131            ErrorKind::ExecutableFileBusy => Self::ExecutableFileBusy,
132            ErrorKind::Deadlock => Self::Deadlock,
133            ErrorKind::CrossesDevices => Self::CrossesDevices,
134            ErrorKind::TooManyLinks => Self::TooManyLinks,
135            ErrorKind::InvalidFilename => Self::InvalidFilename,
136            ErrorKind::ArgumentListTooLong => Self::ArgumentListTooLong,
137            ErrorKind::Interrupted => Self::Interrupted,
138            ErrorKind::Unsupported => Self::Unsupported,
139            ErrorKind::UnexpectedEof => Self::UnexpectedEof,
140            ErrorKind::OutOfMemory => Self::OutOfMemory,
141            ErrorKind::Other => Self::Other,
142        }
143    }
144}
145
146impl PartialEq<io::ErrorKind> for ErrorKind {
147    fn eq(&self, other: &io::ErrorKind) -> bool {
148        *self == Self::from(*other)
149    }
150}
151
152impl PartialEq<ErrorKind> for io::ErrorKind {
153    fn eq(&self, other: &ErrorKind) -> bool {
154        ErrorKind::from(*self) == *other
155    }
156}
157
158/// A native operating-system error number tagged with its source platform.
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
160pub struct SystemCode {
161    operating_system: OperatingSystem,
162    raw: i32,
163}
164
165impl SystemCode {
166    /// Creates a tagged native error number.
167    pub const fn new(operating_system: OperatingSystem, raw: i32) -> Self {
168        Self {
169            operating_system,
170            raw,
171        }
172    }
173
174    /// Returns the platform whose numbering scheme produced this value.
175    pub const fn operating_system(self) -> OperatingSystem {
176        self.operating_system
177    }
178
179    /// Returns the native error number.
180    pub const fn raw(self) -> i32 {
181        self.raw
182    }
183}
184
185/// An error returned by a VFS operation.
186///
187/// In addition to a portable [`ErrorKind`], this retains the original message
188/// and, when available, the originating system's native error number.
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct Error {
191    kind: ErrorKind,
192    message: String,
193    system_code: Option<SystemCode>,
194}
195
196impl Error {
197    /// Creates an error without a native error number.
198    pub fn new(kind: ErrorKind, message: impl ToString) -> Self {
199        Self {
200            kind,
201            message: message.to_string(),
202            system_code: None,
203        }
204    }
205
206    /// Creates an unclassified error from a displayable value.
207    pub fn other(error: impl ToString) -> Self {
208        Self::new(ErrorKind::Other, error.to_string())
209    }
210
211    /// Creates an error with a native error number and its source platform.
212    pub fn from_system_code(
213        kind: ErrorKind,
214        message: impl Into<String>,
215        operating_system: OperatingSystem,
216        raw: i32,
217    ) -> Self {
218        Self {
219            kind,
220            message: message.into(),
221            system_code: Some(SystemCode::new(operating_system, raw)),
222        }
223    }
224
225    /// Converts a native error number from the current host.
226    pub fn from_raw_os_error(raw: i32) -> Self {
227        let error = io::Error::from_raw_os_error(raw);
228        Self::from_raw_os_error_with_message(raw, error.to_string())
229    }
230
231    /// Captures the calling thread's last operating-system error.
232    pub fn last_os_error() -> Self {
233        io::Error::last_os_error().into()
234    }
235
236    /// Converts a native error number from the current host while preserving
237    /// a caller-supplied message.
238    pub fn from_raw_os_error_with_message(raw: i32, message: impl Into<String>) -> Self {
239        let kind = io::Error::from_raw_os_error(raw).kind().into();
240        Self::from_system_code(kind, message, OperatingSystem::current(), raw)
241    }
242
243    /// Converts a native error number from the current host while preserving
244    /// a caller-supplied kind.
245    pub fn from_raw_os_error_with_kind(raw: i32, kind: ErrorKind) -> Self {
246        let error = io::Error::from_raw_os_error(raw);
247        Self::from_system_code(kind, error.to_string(), OperatingSystem::current(), raw)
248    }
249
250    /// Returns this error's portable classification.
251    pub const fn kind(&self) -> ErrorKind {
252        self.kind
253    }
254
255    /// Returns the human-readable error message.
256    pub fn message(&self) -> &str {
257        &self.message
258    }
259
260    /// Returns the tagged native error number, if one was supplied.
261    pub const fn system_code(&self) -> Option<SystemCode> {
262        self.system_code
263    }
264
265    /// Returns the untagged native error number, if one was supplied.
266    pub const fn raw_os_error(&self) -> Option<i32> {
267        match self.system_code {
268            Some(code) => Some(code.raw()),
269            None => None,
270        }
271    }
272
273    /// Converts this error into [`io::Error`], preserving its portable kind.
274    pub fn into_io_error(self) -> io::Error {
275        io::Error::new(self.kind.into(), self)
276    }
277}
278
279impl From<io::Error> for Error {
280    fn from(error: io::Error) -> Self {
281        let kind = error.kind().into();
282        let message = error.to_string();
283        match error.raw_os_error() {
284            Some(raw) => Self::from_system_code(kind, message, OperatingSystem::current(), raw),
285            None => Self::new(kind, message),
286        }
287    }
288}
289
290impl From<dolang_rpc::Error> for Error {
291    fn from(error: dolang_rpc::Error) -> Self {
292        match error {
293            dolang_rpc::Error::Io(error) => error.into(),
294            dolang_rpc::Error::Serialize(_)
295            | dolang_rpc::Error::Deserialize(_)
296            | dolang_rpc::Error::Protocol(_) => Self::new(ErrorKind::InvalidData, error),
297            dolang_rpc::Error::Auth(_) => Self::new(ErrorKind::PermissionDenied, error),
298            dolang_rpc::Error::ConnectionClosed => Self::new(ErrorKind::ConnectionReset, error),
299            dolang_rpc::Error::Cancelled => Self::new(ErrorKind::Interrupted, error),
300            dolang_rpc::Error::UnsupportedCapability => Self::new(ErrorKind::Unsupported, error),
301        }
302    }
303}
304
305impl From<std::ffi::NulError> for Error {
306    fn from(error: std::ffi::NulError) -> Self {
307        io::Error::from(error).into()
308    }
309}
310
311#[cfg(unix)]
312impl From<nix::errno::Errno> for Error {
313    fn from(error: nix::errno::Errno) -> Self {
314        Self::from_raw_os_error(error as i32)
315    }
316}
317
318impl From<Error> for io::Error {
319    fn from(error: Error) -> Self {
320        error.into_io_error()
321    }
322}
323
324impl fmt::Display for Error {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        f.write_str(&self.message)
327    }
328}
329
330impl std::error::Error for Error {}
331
332/// A consuming conversion that did not take effect, returned with the handle
333/// it was given.
334///
335/// Modelled on [`std::io::IntoInnerError`]: an operation that takes ownership
336/// has nowhere to put the value back on failure, so it hands it to the caller
337/// along with the reason. Receiving one means *nothing was surrendered* — the
338/// handle is exactly as usable as it was before the call — so a caller that
339/// only wants the error can discard it, and one that wants to retry can keep
340/// it.
341///
342/// It deliberately does not implement `Debug` in terms of the handle, so that
343/// `unwrap()` works on handles that are not themselves `Debug`.
344pub struct HandoffError<H> {
345    handle: H,
346    error: Error,
347}
348
349impl<H> HandoffError<H> {
350    /// Pairs a handle with the reason its conversion did not happen.
351    pub fn new(handle: H, error: impl Into<Error>) -> Self {
352        Self {
353            handle,
354            error: error.into(),
355        }
356    }
357
358    /// Returns the reason the conversion did not happen.
359    pub fn error(&self) -> &Error {
360        &self.error
361    }
362
363    /// Recovers the handle, discarding the reason.
364    pub fn into_handle(self) -> H {
365        self.handle
366    }
367
368    /// Discards the handle, keeping the reason.
369    pub fn into_error(self) -> Error {
370        self.error
371    }
372
373    /// Splits into the recovered handle and the reason.
374    pub fn into_parts(self) -> (H, Error) {
375        (self.handle, self.error)
376    }
377}
378
379impl<H> fmt::Debug for HandoffError<H> {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        f.debug_struct("HandoffError")
382            .field("error", &self.error)
383            .finish_non_exhaustive()
384    }
385}
386
387impl<H> fmt::Display for HandoffError<H> {
388    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389        fmt::Display::fmt(&self.error, f)
390    }
391}
392
393impl<H> std::error::Error for HandoffError<H> {}
394
395impl<H> From<HandoffError<H>> for Error {
396    fn from(error: HandoffError<H>) -> Self {
397        error.error
398    }
399}
400
401/// The result type returned by VFS operations.
402pub type Result<T> = std::result::Result<T, Error>;
403
404#[cfg(test)]
405mod tests {
406    use super::{Error, ErrorKind, OperatingSystem};
407    use std::io;
408
409    #[test]
410    fn io_error_preserves_formatted_message_and_origin() {
411        #[cfg(unix)]
412        let raw = libc::ENOENT;
413        #[cfg(windows)]
414        let raw = windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND as i32;
415
416        let io_error = io::Error::from_raw_os_error(raw);
417        let message = io_error.to_string();
418        let error = Error::from(io_error);
419        assert_eq!(error.kind(), ErrorKind::NotFound);
420        assert_eq!(error.message(), message);
421        let code = error.system_code().unwrap();
422        assert_eq!(code.operating_system(), OperatingSystem::current());
423        assert_eq!(code.raw(), raw);
424    }
425
426    #[test]
427    fn foreign_system_code_keeps_supplied_message() {
428        let error = Error::from_system_code(
429            ErrorKind::PermissionDenied,
430            "access is denied",
431            OperatingSystem::Windows,
432            5,
433        );
434        assert_eq!(error.message(), "access is denied");
435        assert_eq!(
436            error.system_code().unwrap().operating_system(),
437            OperatingSystem::Windows
438        );
439    }
440
441    #[test]
442    fn raw_os_error_with_message_derives_kind_and_preserves_details() {
443        #[cfg(unix)]
444        let raw = libc::ENOENT;
445        #[cfg(windows)]
446        let raw = windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND as i32;
447
448        let error = Error::from_raw_os_error_with_message(raw, "custom message");
449        assert_eq!(error.kind(), ErrorKind::NotFound);
450        assert_eq!(error.message(), "custom message");
451        let code = error.system_code().unwrap();
452        assert_eq!(code.operating_system(), OperatingSystem::current());
453        assert_eq!(code.raw(), raw);
454    }
455}