app_path/
error.rs

1use std::env::current_exe;
2use std::path::PathBuf;
3
4/// Error type for AppPath operations.
5///
6/// This enum represents the possible failures that can occur when working with
7/// AppPath instances. These include both system-level failures and I/O errors.
8///
9/// # When These Errors Occur
10///
11/// - **`ExecutableNotFound`**: When [`std::env::current_exe()`] fails
12///   - Very rare, but can happen in some embedded or heavily sandboxed environments
13///   - May occur if the executable has been deleted while running
14///   - Can happen in some containerized environments with unusual configurations
15///
16/// - **`InvalidExecutablePath`**: When the executable path is empty
17///   - Extremely rare, indicates a corrupted or broken system
18///   - May occur with custom or non-standard program loaders
19///
20/// - **`IoError`**: When I/O operations fail
21///   - Directory creation fails due to insufficient permissions
22///   - Disk space issues or filesystem errors
23///   - Invalid path characters for the target filesystem
24///   - Network filesystem problems
25///
26/// System-level errors are typically unrecoverable for portable applications,
27/// while I/O errors may be recoverable depending on the specific cause.
28///
29/// # Examples
30///
31/// ```rust
32/// use app_path::{AppPath, AppPathError};
33///
34/// // Handle errors explicitly
35/// match AppPath::try_with("config.toml") {
36///     Ok(config) => {
37///         println!("Config path: {}", config.display());
38///     }
39///     Err(AppPathError::ExecutableNotFound(msg)) => {
40///         eprintln!("Cannot find executable: {msg}");
41///         // Fallback to alternative configuration
42///     }
43///     Err(AppPathError::InvalidExecutablePath(msg)) => {
44///         eprintln!("Invalid executable path: {msg}");
45///         // Fallback to alternative configuration
46///     }
47///     Err(AppPathError::IoError(io_err)) => {
48///         eprintln!("I/O operation failed: {io_err}");
49///         // Handle specific I/O error
50///     }
51/// }
52/// ```
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum AppPathError {
55    /// Failed to determine the current executable path.
56    ///
57    /// This error occurs when [`std::env::current_exe()`] fails, which is rare
58    /// but can happen in some embedded or heavily sandboxed environments.
59    ExecutableNotFound(String),
60
61    /// Executable path is empty or invalid.
62    ///
63    /// This error occurs when the system returns an empty executable path,
64    /// which is extremely rare and indicates a corrupted or broken system.
65    InvalidExecutablePath(String),
66
67    /// An I/O operation failed.
68    ///
69    /// This error occurs when filesystem operations fail, such as:
70    /// - Creating directories fails due to permissions
71    /// - Disk space is insufficient
72    /// - Path contains invalid characters for the filesystem
73    /// - Network filesystem issues
74    IoError(String),
75}
76
77impl std::fmt::Display for AppPathError {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            AppPathError::ExecutableNotFound(msg) => {
81                write!(f, "Failed to determine executable location: {msg}")
82            }
83            AppPathError::InvalidExecutablePath(msg) => {
84                write!(f, "Invalid executable path: {msg}")
85            }
86            AppPathError::IoError(msg) => {
87                write!(f, "I/O operation failed: {msg}")
88            }
89        }
90    }
91}
92
93impl std::error::Error for AppPathError {}
94
95impl From<std::io::Error> for AppPathError {
96    fn from(err: std::io::Error) -> Self {
97        AppPathError::IoError(err.to_string())
98    }
99}
100
101/// Creates an IoError with path context for better debugging.
102///
103/// This implementation adds the file path to I/O error messages, making it easier
104/// to identify which path caused the failure in complex directory operations.
105///
106/// # Examples
107///
108/// ```rust
109/// use app_path::AppPathError;
110/// use std::path::PathBuf;
111///
112/// let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
113/// let path = PathBuf::from("/some/restricted/path");
114/// let app_error = AppPathError::from((io_error, &path));
115///
116/// // Error message includes both the original error and the path
117/// assert!(app_error.to_string().contains("access denied"));
118/// assert!(app_error.to_string().contains("/some/restricted/path"));
119/// ```
120impl From<(std::io::Error, &PathBuf)> for AppPathError {
121    fn from((err, path): (std::io::Error, &PathBuf)) -> Self {
122        AppPathError::IoError(format!("{err} (path: {})", path.display()))
123    }
124}
125
126/// Try to determine the executable directory (fallible version).
127///
128/// This is the internal fallible initialization function that both the fallible
129/// and infallible APIs use. It handles all the edge cases properly without
130/// exposing them as errors to API users.
131pub(crate) fn try_exe_dir_init() -> Result<PathBuf, AppPathError> {
132    let exe = current_exe().map_err(|e| {
133        AppPathError::ExecutableNotFound(format!(
134            "std::env::current_exe() failed: {e} (environment: {})",
135            std::env::var("OS").unwrap_or_else(|_| "unknown OS".to_string())
136        ))
137    })?;
138
139    if exe.as_os_str().is_empty() {
140        return Err(AppPathError::InvalidExecutablePath(format!(
141            "Executable path is empty - unsupported environment (process id: {})",
142            std::process::id()
143        )));
144    }
145
146    // Handle edge case: executable at filesystem root (jailed environments, etc.)
147    // This is NOT an error - it's a valid case that should be handled internally
148    let dir = match exe.parent() {
149        Some(parent) => parent.to_path_buf(),
150        None => {
151            // If exe has no parent (e.g., running as "/init" or "C:\myapp.exe"),
152            // use the root directory itself
153            exe.ancestors().last().unwrap_or(&exe).to_path_buf()
154        }
155    };
156
157    Ok(dir)
158}