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    /// The file was closed, through this handle or a clone of it.
53    Closed,
54}
55
56impl From<io::ErrorKind> for ErrorKind {
57    fn from(kind: io::ErrorKind) -> Self {
58        match kind {
59            io::ErrorKind::NotFound => Self::NotFound,
60            io::ErrorKind::PermissionDenied => Self::PermissionDenied,
61            io::ErrorKind::ConnectionRefused => Self::ConnectionRefused,
62            io::ErrorKind::ConnectionReset => Self::ConnectionReset,
63            io::ErrorKind::HostUnreachable => Self::HostUnreachable,
64            io::ErrorKind::NetworkUnreachable => Self::NetworkUnreachable,
65            io::ErrorKind::ConnectionAborted => Self::ConnectionAborted,
66            io::ErrorKind::NotConnected => Self::NotConnected,
67            io::ErrorKind::AddrInUse => Self::AddrInUse,
68            io::ErrorKind::AddrNotAvailable => Self::AddrNotAvailable,
69            io::ErrorKind::NetworkDown => Self::NetworkDown,
70            io::ErrorKind::BrokenPipe => Self::BrokenPipe,
71            io::ErrorKind::AlreadyExists => Self::AlreadyExists,
72            io::ErrorKind::WouldBlock => Self::WouldBlock,
73            io::ErrorKind::NotADirectory => Self::NotADirectory,
74            io::ErrorKind::IsADirectory => Self::IsADirectory,
75            io::ErrorKind::DirectoryNotEmpty => Self::DirectoryNotEmpty,
76            io::ErrorKind::ReadOnlyFilesystem => Self::ReadOnlyFilesystem,
77            io::ErrorKind::StaleNetworkFileHandle => Self::StaleNetworkFileHandle,
78            io::ErrorKind::InvalidInput => Self::InvalidInput,
79            io::ErrorKind::InvalidData => Self::InvalidData,
80            io::ErrorKind::TimedOut => Self::TimedOut,
81            io::ErrorKind::WriteZero => Self::WriteZero,
82            io::ErrorKind::StorageFull => Self::StorageFull,
83            io::ErrorKind::NotSeekable => Self::NotSeekable,
84            io::ErrorKind::QuotaExceeded => Self::QuotaExceeded,
85            io::ErrorKind::FileTooLarge => Self::FileTooLarge,
86            io::ErrorKind::ResourceBusy => Self::ResourceBusy,
87            io::ErrorKind::ExecutableFileBusy => Self::ExecutableFileBusy,
88            io::ErrorKind::Deadlock => Self::Deadlock,
89            io::ErrorKind::CrossesDevices => Self::CrossesDevices,
90            io::ErrorKind::TooManyLinks => Self::TooManyLinks,
91            io::ErrorKind::InvalidFilename => Self::InvalidFilename,
92            io::ErrorKind::ArgumentListTooLong => Self::ArgumentListTooLong,
93            io::ErrorKind::Interrupted => Self::Interrupted,
94            io::ErrorKind::Unsupported => Self::Unsupported,
95            io::ErrorKind::UnexpectedEof => Self::UnexpectedEof,
96            io::ErrorKind::OutOfMemory => Self::OutOfMemory,
97            _ => Self::Other,
98        }
99    }
100}
101
102impl From<ErrorKind> for io::ErrorKind {
103    fn from(kind: ErrorKind) -> Self {
104        match kind {
105            ErrorKind::NotFound => Self::NotFound,
106            ErrorKind::PermissionDenied => Self::PermissionDenied,
107            ErrorKind::ConnectionRefused => Self::ConnectionRefused,
108            ErrorKind::ConnectionReset => Self::ConnectionReset,
109            ErrorKind::HostUnreachable => Self::HostUnreachable,
110            ErrorKind::NetworkUnreachable => Self::NetworkUnreachable,
111            ErrorKind::ConnectionAborted => Self::ConnectionAborted,
112            ErrorKind::NotConnected => Self::NotConnected,
113            ErrorKind::AddrInUse => Self::AddrInUse,
114            ErrorKind::AddrNotAvailable => Self::AddrNotAvailable,
115            ErrorKind::NetworkDown => Self::NetworkDown,
116            ErrorKind::BrokenPipe => Self::BrokenPipe,
117            ErrorKind::AlreadyExists => Self::AlreadyExists,
118            ErrorKind::WouldBlock => Self::WouldBlock,
119            ErrorKind::NotADirectory => Self::NotADirectory,
120            ErrorKind::IsADirectory => Self::IsADirectory,
121            ErrorKind::DirectoryNotEmpty => Self::DirectoryNotEmpty,
122            ErrorKind::ReadOnlyFilesystem => Self::ReadOnlyFilesystem,
123            ErrorKind::StaleNetworkFileHandle => Self::StaleNetworkFileHandle,
124            ErrorKind::InvalidInput => Self::InvalidInput,
125            ErrorKind::InvalidData => Self::InvalidData,
126            ErrorKind::TimedOut => Self::TimedOut,
127            ErrorKind::WriteZero => Self::WriteZero,
128            ErrorKind::StorageFull => Self::StorageFull,
129            ErrorKind::NotSeekable => Self::NotSeekable,
130            ErrorKind::QuotaExceeded => Self::QuotaExceeded,
131            ErrorKind::FileTooLarge => Self::FileTooLarge,
132            ErrorKind::ResourceBusy => Self::ResourceBusy,
133            ErrorKind::ExecutableFileBusy => Self::ExecutableFileBusy,
134            ErrorKind::Deadlock => Self::Deadlock,
135            ErrorKind::CrossesDevices => Self::CrossesDevices,
136            ErrorKind::TooManyLinks => Self::TooManyLinks,
137            ErrorKind::InvalidFilename => Self::InvalidFilename,
138            ErrorKind::ArgumentListTooLong => Self::ArgumentListTooLong,
139            ErrorKind::Interrupted => Self::Interrupted,
140            ErrorKind::Unsupported => Self::Unsupported,
141            ErrorKind::UnexpectedEof => Self::UnexpectedEof,
142            ErrorKind::OutOfMemory => Self::OutOfMemory,
143            ErrorKind::Other | ErrorKind::Closed => Self::Other,
144        }
145    }
146}
147
148impl PartialEq<io::ErrorKind> for ErrorKind {
149    fn eq(&self, other: &io::ErrorKind) -> bool {
150        *self == Self::from(*other)
151    }
152}
153
154impl PartialEq<ErrorKind> for io::ErrorKind {
155    fn eq(&self, other: &ErrorKind) -> bool {
156        ErrorKind::from(*self) == *other
157    }
158}
159
160/// A native operating-system error number tagged with its source platform.
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162pub struct SystemCode {
163    operating_system: OperatingSystem,
164    raw: i32,
165}
166
167impl SystemCode {
168    /// Creates a tagged native error number.
169    pub const fn new(operating_system: OperatingSystem, raw: i32) -> Self {
170        Self {
171            operating_system,
172            raw,
173        }
174    }
175
176    /// Returns the platform whose numbering scheme produced this value.
177    pub const fn operating_system(self) -> OperatingSystem {
178        self.operating_system
179    }
180
181    /// Returns the native error number.
182    pub const fn raw(self) -> i32 {
183        self.raw
184    }
185}
186
187/// An error returned by a VFS operation.
188///
189/// In addition to a portable [`ErrorKind`], this retains the original message
190/// and, when available, the originating system's native error number.
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Error {
193    kind: ErrorKind,
194    message: String,
195    system_code: Option<SystemCode>,
196}
197
198impl Error {
199    /// Creates an error without a native error number.
200    pub fn new(kind: ErrorKind, message: impl ToString) -> Self {
201        Self {
202            kind,
203            message: message.to_string(),
204            system_code: None,
205        }
206    }
207
208    /// Creates an unclassified error from a displayable value.
209    pub fn other(error: impl ToString) -> Self {
210        Self::new(ErrorKind::Other, error.to_string())
211    }
212
213    /// Creates an error with a native error number and its source platform.
214    pub fn from_system_code(
215        kind: ErrorKind,
216        message: impl Into<String>,
217        operating_system: OperatingSystem,
218        raw: i32,
219    ) -> Self {
220        Self {
221            kind,
222            message: message.into(),
223            system_code: Some(SystemCode::new(operating_system, raw)),
224        }
225    }
226
227    /// Converts a native error number from the current host.
228    pub fn from_raw_os_error(raw: i32) -> Self {
229        let error = io::Error::from_raw_os_error(raw);
230        Self::from_raw_os_error_with_message(raw, error.to_string())
231    }
232
233    /// Captures the calling thread's last operating-system error.
234    pub fn last_os_error() -> Self {
235        io::Error::last_os_error().into()
236    }
237
238    /// Converts a native error number from the current host while preserving
239    /// a caller-supplied message.
240    pub fn from_raw_os_error_with_message(raw: i32, message: impl Into<String>) -> Self {
241        let kind = io::Error::from_raw_os_error(raw).kind().into();
242        Self::from_system_code(kind, message, OperatingSystem::current(), raw)
243    }
244
245    /// Converts a native error number from the current host while preserving
246    /// a caller-supplied kind.
247    pub fn from_raw_os_error_with_kind(raw: i32, kind: ErrorKind) -> Self {
248        let error = io::Error::from_raw_os_error(raw);
249        Self::from_system_code(kind, error.to_string(), OperatingSystem::current(), raw)
250    }
251
252    /// Returns this error's portable classification.
253    pub const fn kind(&self) -> ErrorKind {
254        self.kind
255    }
256
257    /// Returns the human-readable error message.
258    pub fn message(&self) -> &str {
259        &self.message
260    }
261
262    /// Returns the tagged native error number, if one was supplied.
263    pub const fn system_code(&self) -> Option<SystemCode> {
264        self.system_code
265    }
266
267    /// Returns the untagged native error number, if one was supplied.
268    pub const fn raw_os_error(&self) -> Option<i32> {
269        match self.system_code {
270            Some(code) => Some(code.raw()),
271            None => None,
272        }
273    }
274
275    /// Converts this error into [`io::Error`], preserving its portable kind.
276    pub fn into_io_error(self) -> io::Error {
277        io::Error::new(self.kind.into(), self)
278    }
279}
280
281impl From<io::Error> for Error {
282    fn from(error: io::Error) -> Self {
283        // An error that crossed an `io` interface, such as the cursor traits,
284        // comes back as itself rather than as its `io` kind.
285        let error = match error.downcast::<Self>() {
286            Ok(error) => return error,
287            Err(error) => error,
288        };
289        let kind = error.kind().into();
290        let message = error.to_string();
291        match error.raw_os_error() {
292            Some(raw) => Self::from_system_code(kind, message, OperatingSystem::current(), raw),
293            None => Self::new(kind, message),
294        }
295    }
296}
297
298impl From<dolang_rpc::Error> for Error {
299    fn from(error: dolang_rpc::Error) -> Self {
300        match error {
301            dolang_rpc::Error::Io(error) => error.into(),
302            dolang_rpc::Error::Serialize(_)
303            | dolang_rpc::Error::Deserialize(_)
304            | dolang_rpc::Error::Protocol(_) => Self::new(ErrorKind::InvalidData, error),
305            dolang_rpc::Error::Auth(_) => Self::new(ErrorKind::PermissionDenied, error),
306            dolang_rpc::Error::ConnectionClosed => Self::new(ErrorKind::ConnectionReset, error),
307            dolang_rpc::Error::Cancelled => Self::new(ErrorKind::Interrupted, error),
308            dolang_rpc::Error::UnsupportedCapability => Self::new(ErrorKind::Unsupported, error),
309        }
310    }
311}
312
313impl From<std::ffi::NulError> for Error {
314    fn from(error: std::ffi::NulError) -> Self {
315        io::Error::from(error).into()
316    }
317}
318
319#[cfg(unix)]
320impl From<nix::errno::Errno> for Error {
321    fn from(error: nix::errno::Errno) -> Self {
322        Self::from_raw_os_error(error as i32)
323    }
324}
325
326impl From<Error> for io::Error {
327    fn from(error: Error) -> Self {
328        error.into_io_error()
329    }
330}
331
332impl fmt::Display for Error {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        f.write_str(&self.message)
335    }
336}
337
338impl std::error::Error for Error {}
339
340/// The result type returned by VFS operations.
341pub type Result<T> = std::result::Result<T, Error>;
342
343#[cfg(test)]
344mod tests {
345    use super::{Error, ErrorKind, OperatingSystem};
346    use std::io;
347
348    #[test]
349    fn io_error_preserves_formatted_message_and_origin() {
350        #[cfg(unix)]
351        let raw = libc::ENOENT;
352        #[cfg(windows)]
353        let raw = windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND as i32;
354
355        let io_error = io::Error::from_raw_os_error(raw);
356        let message = io_error.to_string();
357        let error = Error::from(io_error);
358        assert_eq!(error.kind(), ErrorKind::NotFound);
359        assert_eq!(error.message(), message);
360        let code = error.system_code().unwrap();
361        assert_eq!(code.operating_system(), OperatingSystem::current());
362        assert_eq!(code.raw(), raw);
363    }
364
365    #[test]
366    fn foreign_system_code_keeps_supplied_message() {
367        let error = Error::from_system_code(
368            ErrorKind::PermissionDenied,
369            "access is denied",
370            OperatingSystem::Windows,
371            5,
372        );
373        assert_eq!(error.message(), "access is denied");
374        assert_eq!(
375            error.system_code().unwrap().operating_system(),
376            OperatingSystem::Windows
377        );
378    }
379
380    #[test]
381    fn raw_os_error_with_message_derives_kind_and_preserves_details() {
382        #[cfg(unix)]
383        let raw = libc::ENOENT;
384        #[cfg(windows)]
385        let raw = windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND as i32;
386
387        let error = Error::from_raw_os_error_with_message(raw, "custom message");
388        assert_eq!(error.kind(), ErrorKind::NotFound);
389        assert_eq!(error.message(), "custom message");
390        let code = error.system_code().unwrap();
391        assert_eq!(code.operating_system(), OperatingSystem::current());
392        assert_eq!(code.raw(), raw);
393    }
394}