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 determining
7/// the executable location. These errors are rare in practice and typically
8/// indicate fundamental system issues.
9///
10/// # When These Errors Occur
11///
12/// - **`ExecutableNotFound`**: When [`std::env::current_exe()`] fails
13/// - Very rare, but can happen in some embedded or heavily sandboxed environments
14/// - May occur if the executable has been deleted while running
15/// - Can happen in some containerized environments with unusual configurations
16///
17/// - **`InvalidExecutablePath`**: When the executable path is empty
18/// - Extremely rare, indicates a corrupted or broken system
19/// - May occur with custom or non-standard program loaders
20///
21/// These errors represent system-level failures that are typically unrecoverable
22/// for portable applications. Most applications should use the infallible API
23/// (`new()`, `exe_dir()`) and handle these rare cases through environment
24/// variables or fallback strategies.
25///
26/// # Examples
27///
28/// ```rust
29/// use app_path::{AppPath, AppPathError};
30///
31/// // Handle errors explicitly
32/// match AppPath::try_new("config.toml") {
33/// Ok(config) => {
34/// println!("Config path: {}", config.path().display());
35/// }
36/// Err(AppPathError::ExecutableNotFound(msg)) => {
37/// eprintln!("Cannot find executable: {}", msg);
38/// // Fallback to alternative configuration
39/// }
40/// Err(AppPathError::InvalidExecutablePath(msg)) => {
41/// eprintln!("Invalid executable path: {}", msg);
42/// // Fallback to alternative configuration
43/// }
44/// }
45/// ```
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum AppPathError {
48 /// Failed to determine the current executable path.
49 ///
50 /// This error occurs when [`std::env::current_exe()`] fails, which is rare
51 /// but can happen in some embedded or heavily sandboxed environments.
52 ExecutableNotFound(String),
53
54 /// Executable path is empty or invalid.
55 ///
56 /// This error occurs when the system returns an empty executable path,
57 /// which is extremely rare and indicates a corrupted or broken system.
58 InvalidExecutablePath(String),
59}
60
61impl std::fmt::Display for AppPathError {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 AppPathError::ExecutableNotFound(msg) => {
65 write!(f, "Failed to determine executable location: {msg}")
66 }
67 AppPathError::InvalidExecutablePath(msg) => {
68 write!(f, "Invalid executable path: {msg}")
69 }
70 }
71 }
72}
73
74impl std::error::Error for AppPathError {}
75
76/// Try to determine the executable directory (fallible version).
77///
78/// This is the internal fallible initialization function that both the fallible
79/// and infallible APIs use. It handles all the edge cases properly without
80/// exposing them as errors to API users.
81pub(crate) fn try_exe_dir_init() -> Result<PathBuf, AppPathError> {
82 let exe = current_exe().map_err(|e| {
83 AppPathError::ExecutableNotFound(format!("std::env::current_exe() failed: {e}"))
84 })?;
85
86 if exe.as_os_str().is_empty() {
87 return Err(AppPathError::InvalidExecutablePath(
88 "Executable path is empty - unsupported environment".to_string(),
89 ));
90 }
91
92 // Handle edge case: executable at filesystem root (jailed environments, etc.)
93 // This is NOT an error - it's a valid case that should be handled internally
94 let dir = match exe.parent() {
95 Some(parent) => parent.to_path_buf(),
96 None => {
97 // If exe has no parent (e.g., running as "/init" or "C:\myapp.exe"),
98 // use the root directory itself
99 exe.ancestors().last().unwrap_or(&exe).to_path_buf()
100 }
101 };
102
103 Ok(dir)
104}