Skip to main content

rust_bash/
error.rs

1use std::fmt;
2use std::path::PathBuf;
3
4/// Errors arising from virtual filesystem operations.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum VfsError {
7    NotFound(PathBuf),
8    AlreadyExists(PathBuf),
9    NotADirectory(PathBuf),
10    NotAFile(PathBuf),
11    IsADirectory(PathBuf),
12    PermissionDenied(PathBuf),
13    DirectoryNotEmpty(PathBuf),
14    SymlinkLoop(PathBuf),
15    InvalidPath(String),
16    IoError(String),
17}
18
19impl fmt::Display for VfsError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            VfsError::NotFound(p) => write!(f, "No such file or directory: {}", p.display()),
23            VfsError::AlreadyExists(p) => write!(f, "Already exists: {}", p.display()),
24            VfsError::NotADirectory(p) => write!(f, "Not a directory: {}", p.display()),
25            VfsError::NotAFile(p) => write!(f, "Not a file: {}", p.display()),
26            VfsError::IsADirectory(p) => write!(f, "Is a directory: {}", p.display()),
27            VfsError::PermissionDenied(p) => write!(f, "Permission denied: {}", p.display()),
28            VfsError::DirectoryNotEmpty(p) => write!(f, "Directory not empty: {}", p.display()),
29            VfsError::SymlinkLoop(p) => {
30                write!(f, "Too many levels of symbolic links: {}", p.display())
31            }
32            VfsError::InvalidPath(msg) => write!(f, "Invalid path: {msg}"),
33            VfsError::IoError(msg) => write!(f, "I/O error: {msg}"),
34        }
35    }
36}
37
38impl std::error::Error for VfsError {}
39
40/// Top-level error type for the rust-bash interpreter.
41#[derive(Debug)]
42pub enum RustBashError {
43    Parse(String),
44    Execution(String),
45    /// An expansion-time error that aborts the current command and sets the
46    /// exit code.  When `should_exit` is true the *script* also terminates
47    /// (used by `${var:?msg}`).  When false, only the current command is
48    /// aborted (used for e.g. negative substring length).
49    ExpansionError {
50        message: String,
51        exit_code: i32,
52        should_exit: bool,
53    },
54    /// A failglob error: no glob matches found when `shopt -s failglob` is on.
55    /// Aborts the current simple command (exit code 1) but does NOT exit the script.
56    FailGlob {
57        pattern: String,
58    },
59    /// A redirect failure (e.g. nonexistent input file, empty filename).
60    /// Aborts the current command with exit code 1, reports error on stderr,
61    /// but does NOT exit the script.
62    RedirectFailed(String),
63    LimitExceeded {
64        limit_name: &'static str,
65        limit_value: usize,
66        actual_value: usize,
67    },
68    Network(String),
69    Vfs(VfsError),
70    Timeout,
71}
72
73impl fmt::Display for RustBashError {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            RustBashError::Parse(msg) => write!(f, "parse error: {msg}"),
77            RustBashError::Execution(msg) => write!(f, "execution error: {msg}"),
78            RustBashError::ExpansionError { message, .. } => {
79                write!(f, "expansion error: {message}")
80            }
81            RustBashError::FailGlob { pattern } => {
82                write!(f, "no match: {pattern}")
83            }
84            RustBashError::RedirectFailed(msg) => write!(f, "rust-bash: {msg}"),
85            RustBashError::LimitExceeded {
86                limit_name,
87                limit_value,
88                actual_value,
89            } => write!(
90                f,
91                "limit exceeded: {limit_name} ({actual_value}) exceeded limit ({limit_value})"
92            ),
93            RustBashError::Network(msg) => write!(f, "network error: {msg}"),
94            RustBashError::Vfs(e) => write!(f, "vfs error: {e}"),
95            RustBashError::Timeout => write!(f, "execution timed out"),
96        }
97    }
98}
99
100impl std::error::Error for RustBashError {
101    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
102        match self {
103            RustBashError::Vfs(e) => Some(e),
104            _ => None,
105        }
106    }
107}
108
109impl From<VfsError> for RustBashError {
110    fn from(e: VfsError) -> Self {
111        RustBashError::Vfs(e)
112    }
113}