app_path/
app_path.rs

1use std::path::{Path, PathBuf};
2
3use crate::error::AppPathError;
4use crate::functions::try_exe_dir;
5
6/// Creates paths relative to the executable location for portable applications.
7///
8/// **AppPath** enables building truly portable applications where configuration, data,
9/// and executable stay together as a deployable unit. Perfect for USB drives, network
10/// shares, or any directory without installation.
11///
12/// ## Key Features
13///
14/// - **Portable**: Relative paths resolve to executable directory
15/// - **System integration**: Absolute paths work as-is  
16/// - **Zero-cost**: Implements `Deref<Target=Path>` and all path traits
17/// - **Thread-safe**: Static caching with proper synchronization
18/// - **Memory efficient**: Only stores the final resolved path
19///
20/// ## API Overview
21///
22/// - [`Self::new()`] - **Primary API**: Simple, infallible construction
23/// - [`Self::try_new()`] - **Libraries**: Fallible version for error handling  
24/// - [`Self::with_override()`] - **Deployment**: Environment-configurable paths
25/// - [`Self::path()`] - **Access**: Get the resolved `&Path`
26///
27/// # Panics
28///
29/// Methods panic if executable location cannot be determined (extremely rare).
30/// After first successful call, methods never panic (uses cached result).
31///
32/// # Examples
33///
34/// ```rust
35/// use app_path::AppPath;
36///
37/// // Basic usage - most common pattern
38/// let config = AppPath::new("config.toml");
39/// let data = AppPath::new("data/users.db");
40///
41/// // Works like standard paths
42/// if config.exists() {
43///     let content = std::fs::read_to_string(&config);
44/// }
45/// data.ensure_parent_dirs(); // Creates data/ directory for the file
46///
47/// // Mixed portable and system paths
48/// let portable = AppPath::new("app.conf");           // → exe_dir/app.conf
49/// let system = AppPath::new("/var/log/app.log");     // → /var/log/app.log
50///
51/// // Override for deployment flexibility
52/// let config = AppPath::with_override(
53///     "config.toml",
54///     std::env::var("CONFIG_PATH").ok()
55/// );
56/// ```
57#[derive(Clone, Debug)]
58pub struct AppPath {
59    full_path: PathBuf,
60}
61
62impl AppPath {
63    /// Creates file paths relative to the executable location.
64    ///
65    /// **Recommended for most applications.** This is the simple, infallible API that handles
66    /// the common case cleanly without error handling boilerplate.
67    ///
68    /// ## Path Resolution
69    ///
70    /// - **Relative paths**: `"config.toml"` → `exe_dir/config.toml` (portable)
71    /// - **Absolute paths**: `"/etc/config"` → `/etc/config` (system integration)
72    ///
73    /// ## Performance
74    ///
75    /// - **Static caching**: Executable location determined once, reused forever
76    /// - **Zero allocations**: Efficient path resolution
77    /// - **Thread-safe**: Safe to call from multiple threads
78    ///
79    /// # Panics
80    ///
81    /// Panics if executable location cannot be determined (extremely rare):
82    /// - `std::env::current_exe()` fails
83    /// - Executable path is empty (system corruption)
84    ///
85    /// After first successful call, this method never panics (uses cached result).
86    ///
87    /// # Examples
88    ///
89    /// ```rust
90    /// use app_path::AppPath;
91    ///
92    /// // Most common usage
93    /// let config = AppPath::new("config.toml");
94    /// let data = AppPath::new("data/users.db");
95    /// let logs = AppPath::new("logs/app.log");
96    ///
97    /// // Mixed portable and system paths
98    /// let app_config = AppPath::new("config.toml");           // → exe_dir/config.toml
99    /// let system_log = AppPath::new("/var/log/myapp.log");    // → /var/log/myapp.log
100    ///
101    /// // Use like normal paths
102    /// if config.exists() {
103    ///     let content = std::fs::read_to_string(&config);
104    /// }
105    /// data.ensure_parent_dirs(); // Creates data/ directory for the file
106    /// ```
107    ///
108    /// # Panics
109    ///
110    /// Panics on first use if the executable location cannot be determined.
111    /// This is extremely rare and indicates fundamental system issues.
112    /// See [`AppPathError`] for details on the possible failure conditions.
113    ///
114    /// After the first successful call, this method will never panic as it uses the cached result.
115    ///
116    /// # Performance
117    ///
118    /// This method is highly optimized:
119    /// - **Static caching**: Executable location determined once, reused forever
120    /// - **Zero allocations**: Uses `AsRef<Path>` to avoid unnecessary conversions
121    /// - **Minimal memory**: Only stores the final resolved path
122    /// - **Thread-safe**: Safe to call from multiple threads concurrently
123    ///
124    /// # Examples
125    ///
126    /// ```rust
127    /// use app_path::AppPath;
128    ///
129    /// // Most common usage
130    /// let config = AppPath::new("config.toml");
131    /// let data = AppPath::new("data/users.db");
132    /// let logs = AppPath::new("logs/app.log");
133    ///
134    /// // Mixed portable and system paths
135    /// let app_config = AppPath::new("config.toml");           // → exe_dir/config.toml
136    /// let system_log = AppPath::new("/var/log/myapp.log");    // → /var/log/myapp.log
137    ///
138    /// // Use like normal paths
139    /// if config.exists() {
140    ///     let content = std::fs::read_to_string(&config);
141    /// }
142    /// data.ensure_parent_dirs(); // Creates data/ directory for the file
143    /// ```
144    #[inline]
145    pub fn new(path: impl AsRef<Path>) -> Self {
146        match Self::try_new(path) {
147            Ok(app_path) => app_path,
148            Err(e) => panic!("Failed to create AppPath: {e}"),
149        }
150    }
151
152    /// Creates file paths relative to the executable location (fallible).
153    ///
154    /// **Use this only for libraries or specialized applications requiring explicit error handling.**
155    /// Most applications should use [`Self::new()`] instead for simpler, cleaner code.
156    ///
157    /// ## When to Use
158    ///
159    /// **Use `try_new()` for:**
160    /// - Reusable libraries that shouldn't panic
161    /// - System tools with fallback strategies
162    /// - Applications running in unusual environments
163    ///
164    /// **Use [`Self::new()`] for:**
165    /// - Desktop, web, server, CLI applications
166    /// - When you want simple, clean code (recommended)
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// use app_path::{AppPath, AppPathError};
172    ///
173    /// // Library with graceful error handling
174    /// fn load_config() -> Result<String, AppPathError> {
175    ///     let config_path = AppPath::try_new("config.toml")?;
176    ///     // Load configuration...
177    ///     Ok("config loaded".to_string())
178    /// }
179    ///
180    /// // Better: Use override API for environment variables
181    /// fn load_config_with_override() -> Result<String, AppPathError> {
182    ///     let config_path = AppPath::try_with_override(
183    ///         "config.toml",
184    ///         std::env::var("APP_CONFIG").ok()
185    ///     )?;
186    ///     // Load configuration...
187    ///     Ok("config loaded".to_string())
188    /// }
189    ///
190    /// // Multiple environment variable fallback (better approach)
191    /// fn get_config_with_fallback() -> Result<AppPath, AppPathError> {
192    ///     AppPath::try_with_override_fn("config.toml", || {
193    ///         std::env::var("APP_CONFIG").ok()
194    ///             .or_else(|| std::env::var("CONFIG_FILE").ok())
195    ///             .or_else(|| std::env::var("XDG_CONFIG_HOME").ok().map(|dir| format!("{}/myapp/config.toml", dir)))
196    ///     })
197    /// }
198    /// ```
199    ///
200    /// **Reality check:** Executable location determination failing is extremely rare:
201    /// - It requires fundamental system issues or unusual deployment scenarios
202    /// - When it happens, it usually indicates unrecoverable system problems
203    /// - Most applications can't meaningfully continue without knowing their location
204    /// - The error handling overhead isn't worth it for typical applications
205    ///
206    /// **Better approaches for most applications:**
207    /// ```rust
208    /// use app_path::AppPath;
209    /// use std::env;
210    ///
211    /// // Use our override API for environment variables (recommended)
212    /// fn get_config_path() -> AppPath {
213    ///     AppPath::with_override("config.toml", env::var("MYAPP_CONFIG_DIR").ok())
214    /// }
215    ///
216    /// // Or fallible version for libraries
217    /// fn try_get_config_path() -> Result<AppPath, app_path::AppPathError> {
218    ///     AppPath::try_with_override("config.toml", env::var("MYAPP_CONFIG_DIR").ok())
219    /// }
220    /// ```
221    ///
222    /// ## Global Caching Behavior
223    ///
224    /// Once the executable directory is successfully determined by either this method or [`AppPath::new()`],
225    /// the result is cached globally and all subsequent calls to both methods will use the cached value.
226    /// This means that after the first successful call, `try_new()` will never return an error.
227    ///
228    /// # Arguments
229    ///
230    /// * `path` - A path that will be resolved according to AppPath's resolution strategy.
231    ///   Accepts any type implementing [`AsRef<Path>`].
232    ///
233    /// # Returns
234    ///
235    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
236    /// * `Err(AppPathError)` - Failed to determine executable location (extremely rare)
237    ///
238    /// # Examples
239    ///
240    /// ## Library Error Handling
241    ///
242    /// ```rust
243    /// use app_path::{AppPath, AppPathError};
244    ///
245    /// // Library function that returns Result instead of panicking
246    /// pub fn create_config_manager() -> Result<ConfigManager, AppPathError> {
247    ///     let config_path = AppPath::try_new("config.toml")?;
248    ///     Ok(ConfigManager::new(config_path))
249    /// }
250    ///
251    /// pub struct ConfigManager {
252    ///     config_path: AppPath,
253    /// }
254    ///
255    /// impl ConfigManager {
256    ///     fn new(config_path: AppPath) -> Self {
257    ///         Self { config_path }
258    ///     }
259    /// }
260    /// ```
261    ///
262    /// ## Error Propagation Pattern
263    ///
264    /// ```rust
265    /// use app_path::{AppPath, AppPathError};
266    ///
267    /// fn initialize_app() -> Result<(), Box<dyn std::error::Error>> {
268    ///     let config = AppPath::try_new("config.toml")?;
269    ///     let data = AppPath::try_new("data/app.db")?;
270    ///     
271    ///     // Initialize application with these paths
272    ///     println!("Config: {}", config.path().display());
273    ///     println!("Data: {}", data.path().display());
274    ///     
275    ///     Ok(())
276    /// }
277    /// ```
278    #[inline]
279    pub fn try_new(path: impl AsRef<Path>) -> Result<Self, AppPathError> {
280        let exe_dir = try_exe_dir()?;
281        let full_path = exe_dir.join(path);
282        Ok(Self { full_path })
283    }
284
285    /// Get the full resolved path.
286    ///
287    /// This is the primary method for getting the actual filesystem path
288    /// where your file or directory is located.
289    ///
290    /// # Examples
291    ///
292    /// ```rust
293    /// use app_path::AppPath;
294    ///
295    /// let config = AppPath::new("config.toml");
296    ///
297    /// // Get the path for use with standard library functions
298    /// println!("Config path: {}", config.path().display());
299    ///
300    /// // The path is always absolute
301    /// assert!(config.path().is_absolute());
302    /// ```
303    #[inline]
304    pub fn path(&self) -> &Path {
305        &self.full_path
306    }
307
308    /// Check if the path exists.
309    ///
310    /// # Examples
311    ///
312    /// ```rust
313    /// use app_path::AppPath;
314    ///
315    /// let config = AppPath::new("config.toml");
316    ///
317    /// if config.exists() {
318    ///     println!("Config file found!");
319    /// } else {
320    ///     println!("Config file not found, using defaults.");
321    /// }
322    /// ```
323    #[inline]
324    pub fn exists(&self) -> bool {
325        self.full_path.exists()
326    }
327
328    /// Creates parent directories needed for this file path.
329    ///
330    /// This method creates all parent directories for a file path, making it ready
331    /// for file creation. It does not create the file itself.
332    ///
333    /// **Use this when you know the path represents a file and you want to prepare
334    /// the directory structure for writing the file.**
335    ///
336    /// # Examples
337    ///
338    /// ```rust
339    /// use app_path::AppPath;
340    /// use std::env;
341    ///
342    /// let temp_dir = env::temp_dir().join("app_path_example");
343    ///
344    /// // Prepare directories for a config file
345    /// let config_file = AppPath::new(temp_dir.join("config/app.toml"));
346    /// config_file.ensure_parent_dirs()?; // Creates config/ directory
347    ///
348    /// // Now you can write the file
349    /// std::fs::write(config_file.path(), "key = value")?;
350    /// assert!(config_file.exists());
351    ///
352    /// // Prepare directories for a log file
353    /// let log_file = AppPath::new(temp_dir.join("logs/2024/app.log"));
354    /// log_file.ensure_parent_dirs()?; // Creates logs/2024/ directories
355    ///
356    /// # std::fs::remove_dir_all(&temp_dir).ok();
357    /// # Ok::<(), Box<dyn std::error::Error>>(())
358    /// ```
359    #[inline]
360    pub fn ensure_parent_dirs(&self) -> std::io::Result<()> {
361        if let Some(parent) = self.parent() {
362            std::fs::create_dir_all(parent.path())
363        } else {
364            Ok(())
365        }
366    }
367
368    /// Creates this path as a directory, including all parent directories.
369    ///
370    /// This method treats the path as a directory and creates it along with
371    /// all necessary parent directories. The created directory will exist
372    /// after this call succeeds.
373    ///
374    /// **Use this when you know the path represents a directory that should be created.**
375    ///
376    /// # Behavior
377    ///
378    /// - **Creates the directory itself**: Unlike `ensure_parent_dirs()`, this creates the full path as a directory
379    /// - **Creates all parents**: Any missing parent directories are created automatically
380    /// - **Idempotent**: Safe to call multiple times - won't fail if directory already exists
381    /// - **Atomic-like**: Either all directories are created or the operation fails
382    ///
383    /// # Examples
384    ///
385    /// ## Basic Directory Creation
386    ///
387    /// ```rust
388    /// use app_path::AppPath;
389    /// use std::env;
390    ///
391    /// let temp_dir = env::temp_dir().join("app_path_dir_example");
392    ///
393    /// // Create a cache directory
394    /// let cache_dir = AppPath::new(temp_dir.join("cache"));
395    /// cache_dir.ensure_dir_exists()?; // Creates cache/ directory
396    /// assert!(cache_dir.exists());
397    /// assert!(cache_dir.is_dir());
398    ///
399    /// # std::fs::remove_dir_all(&temp_dir).ok();
400    /// # Ok::<(), Box<dyn std::error::Error>>(())
401    /// ```
402    ///
403    /// ## Nested Directory Structures
404    ///
405    /// ```rust
406    /// use app_path::AppPath;
407    /// use std::env;
408    ///
409    /// let temp_dir = env::temp_dir().join("app_path_nested_example");
410    ///
411    /// // Create deeply nested directories
412    /// let deep_dir = AppPath::new(temp_dir.join("data/backups/daily"));
413    /// deep_dir.ensure_dir_exists()?; // Creates data/backups/daily/ directories
414    /// assert!(deep_dir.exists());
415    /// assert!(deep_dir.is_dir());
416    ///
417    /// // All parent directories are also created
418    /// let backups_dir = AppPath::new(temp_dir.join("data/backups"));
419    /// assert!(backups_dir.exists());
420    /// assert!(backups_dir.is_dir());
421    ///
422    /// # std::fs::remove_dir_all(&temp_dir).ok();
423    /// # Ok::<(), Box<dyn std::error::Error>>(())
424    /// ```
425    ///
426    /// ## Practical Application Setup
427    ///
428    /// ```rust
429    /// use app_path::AppPath;
430    /// use std::env;
431    ///
432    /// let temp_dir = env::temp_dir().join("app_setup_example");
433    ///
434    /// // Set up application directory structure
435    /// let config_dir = AppPath::new(temp_dir.join("config"));
436    /// let data_dir = AppPath::new(temp_dir.join("data"));
437    /// let cache_dir = AppPath::new(temp_dir.join("cache"));
438    /// let logs_dir = AppPath::new(temp_dir.join("logs"));
439    ///
440    /// // Create all directories
441    /// config_dir.ensure_dir_exists()?;
442    /// data_dir.ensure_dir_exists()?;
443    /// cache_dir.ensure_dir_exists()?;
444    /// logs_dir.ensure_dir_exists()?;
445    ///
446    /// // Now create subdirectories
447    /// let daily_logs = logs_dir.join("daily");
448    /// daily_logs.ensure_dir_exists()?;
449    ///
450    /// // Verify structure
451    /// assert!(config_dir.is_dir());
452    /// assert!(data_dir.is_dir());
453    /// assert!(cache_dir.is_dir());
454    /// assert!(logs_dir.is_dir());
455    /// assert!(daily_logs.is_dir());
456    ///
457    /// # std::fs::remove_dir_all(&temp_dir).ok();
458    /// # Ok::<(), Box<dyn std::error::Error>>(())
459    /// ```
460    ///
461    /// ## Comparison with `ensure_parent_dirs()`
462    ///
463    /// ```rust
464    /// use app_path::AppPath;
465    /// use std::env;
466    ///
467    /// let temp_dir = env::temp_dir().join("app_comparison_example");
468    ///
469    /// let file_path = AppPath::new(temp_dir.join("logs/app.log"));
470    /// let dir_path = AppPath::new(temp_dir.join("logs"));
471    ///
472    /// // For files: prepare parent directories
473    /// file_path.ensure_parent_dirs()?; // Creates logs/ directory
474    /// assert!(dir_path.exists()); // logs/ directory exists
475    /// assert!(!file_path.exists()); // app.log file does NOT exist
476    ///
477    /// // For directories: create the directory itself  
478    /// dir_path.ensure_dir_exists()?; // Creates logs/ directory (idempotent)
479    /// assert!(dir_path.exists()); // logs/ directory exists
480    /// assert!(dir_path.is_dir()); // and it's definitely a directory
481    ///
482    /// # std::fs::remove_dir_all(&temp_dir).ok();
483    /// # Ok::<(), Box<dyn std::error::Error>>(())
484    /// ```
485    #[inline]
486    pub fn ensure_dir_exists(&self) -> std::io::Result<()> {
487        std::fs::create_dir_all(self.path())
488    }
489
490    /// Creates all directories needed for this path.
491    ///
492    /// **DEPRECATED**: Use [`ensure_parent_dirs()`](Self::ensure_parent_dirs) for file paths
493    /// or [`ensure_dir_exists()`](Self::ensure_dir_exists) for directory paths instead.
494    /// This method name was confusing as it didn't always create directories for the path itself.
495    ///
496    /// This method intelligently determines whether the path represents a file
497    /// or directory and creates the appropriate directories:
498    /// - **For existing directories**: does nothing (already exists)
499    /// - **For existing files**: creates parent directories if needed
500    /// - **For non-existing paths**: treats as file path and creates parent directories
501    ///
502    /// # Migration Guide
503    ///
504    /// ```rust
505    /// use app_path::AppPath;
506    ///
507    /// let file_path = AppPath::new("logs/app.log");
508    /// let dir_path = AppPath::new("cache");
509    ///
510    /// // Old (deprecated):
511    /// // file_path.create_dir_all()?;
512    /// // dir_path.create_dir_all()?; // This was confusing!
513    ///
514    /// // New (clear):
515    /// file_path.ensure_parent_dirs()?; // Creates logs/ for the file
516    /// dir_path.ensure_dir_exists()?;   // Creates cache/ directory
517    /// # Ok::<(), Box<dyn std::error::Error>>(())
518    /// ```
519    #[deprecated(
520        since = "0.2.2",
521        note = "Use `ensure_parent_dirs()` for file paths or `ensure_dir_exists()` for directory paths instead"
522    )]
523    #[inline]
524    pub fn create_dir_all(&self) -> std::io::Result<()> {
525        if let Some(parent) = self.full_path.parent() {
526            std::fs::create_dir_all(parent)?;
527        }
528        Ok(())
529    }
530
531    /// Creates a path with override support (infallible).
532    ///
533    /// This method provides a one-line solution for creating paths that can be overridden
534    /// by external configuration. If an override is provided, it takes precedence over
535    /// the default path. Otherwise, the default path is used with normal AppPath resolution.
536    ///
537    /// **This is the primary method for implementing configurable paths in applications.**
538    /// It combines the simplicity of [`AppPath::new()`] with the flexibility of external
539    /// configuration overrides.
540    ///
541    /// ## Common Use Cases
542    ///
543    /// - **Environment variable overrides**: Allow users to customize file locations
544    /// - **Command-line argument overrides**: CLI tools with configurable paths
545    ///
546    /// ## How It Works
547    ///
548    /// **If override is provided**: Use the override path directly (can be relative or absolute)
549    /// **If override is `None`**: Use the default path with normal AppPath resolution
550    ///
551    /// # Examples
552    ///
553    /// ```rust
554    /// use app_path::AppPath;
555    /// use std::env;
556    ///
557    /// // Environment variable override
558    /// let config = AppPath::with_override(
559    ///     "config.toml",
560    ///     env::var("APP_CONFIG").ok()
561    /// );
562    ///
563    /// // CLI argument override
564    /// fn get_config(cli_override: Option<&str>) -> AppPath {
565    ///     AppPath::with_override("config.toml", cli_override)
566    /// }
567    ///
568    /// // Configuration file override
569    /// struct Config {
570    ///     data_dir: Option<String>,
571    /// }
572    ///
573    /// let config = load_config();
574    /// let data_dir = AppPath::with_override("data", config.data_dir.as_deref());
575    /// # fn load_config() -> Config { Config { data_dir: None } }
576    /// ```
577    #[inline]
578    pub fn with_override(
579        default: impl AsRef<Path>,
580        override_option: Option<impl AsRef<Path>>,
581    ) -> Self {
582        match override_option {
583            Some(override_path) => Self::new(override_path),
584            None => Self::new(default),
585        }
586    }
587
588    /// Creates a path with dynamic override support.
589    ///
590    /// **Use this for complex override logic or lazy evaluation.** The closure is called once
591    /// to determine if an override should be applied.
592    ///
593    /// # Examples
594    ///
595    /// ```rust
596    /// use app_path::AppPath;
597    /// use std::env;
598    ///
599    /// // Multiple fallback sources
600    /// let config = AppPath::with_override_fn("config.toml", || {
601    ///     env::var("APP_CONFIG").ok()
602    ///         .or_else(|| env::var("CONFIG_FILE").ok())
603    ///         .or_else(|| {
604    ///             // Only check expensive operations if needed
605    ///             if env::var("USE_SYSTEM_CONFIG").is_ok() {
606    ///                 Some("/etc/myapp/config.toml".to_string())
607    ///             } else {
608    ///                 None
609    ///             }
610    ///         })
611    /// });
612    ///
613    /// // Development mode override
614    /// let data_dir = AppPath::with_override_fn("data", || {
615    ///     if env::var("DEVELOPMENT").is_ok() {
616    ///         Some("dev_data".to_string())
617    ///     } else {
618    ///         None
619    ///     }
620    /// });
621    /// ```
622    #[inline]
623    pub fn with_override_fn<F, P>(default: impl AsRef<Path>, override_fn: F) -> Self
624    where
625        F: FnOnce() -> Option<P>,
626        P: AsRef<Path>,
627    {
628        match override_fn() {
629            Some(override_path) => Self::new(override_path),
630            None => Self::new(default),
631        }
632    }
633
634    /// Creates a path with override support (fallible).
635    ///
636    /// **Fallible version of [`Self::with_override()`].** Most applications should use the
637    /// infallible version instead for cleaner code.
638    ///
639    /// # Examples
640    ///
641    /// ```rust
642    /// use app_path::{AppPath, AppPathError};
643    /// use std::env;
644    ///
645    /// fn get_config() -> Result<AppPath, AppPathError> {
646    ///     AppPath::try_with_override("config.toml", env::var("CONFIG").ok())
647    /// }
648    /// ```
649    /// cleaner, more idiomatic code.
650    ///
651    /// ## When to Use This Method
652    ///
653    /// - **Reusable libraries** that should handle errors gracefully
654    /// - **System-level tools** that need to handle broken environments
655    /// - **Applications with custom fallback strategies** for rare edge cases
656    ///
657    /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
658    ///
659    /// # Arguments
660    ///
661    /// * `default` - The default path to use if no override is provided
662    /// * `override_option` - Optional override path that takes precedence if provided
663    ///
664    /// # Returns
665    ///
666    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
667    /// * `Err(AppPathError)` - Failed to determine executable location
668    ///
669    /// # Examples
670    ///
671    /// ## Library with Error Handling
672    ///
673    /// ```rust
674    /// use app_path::{AppPath, AppPathError};
675    /// use std::env;
676    ///
677    /// fn create_config_path() -> Result<AppPath, AppPathError> {
678    ///     let config_override = env::var("MYAPP_CONFIG").ok();
679    ///     AppPath::try_with_override("config.toml", config_override.as_deref())
680    /// }
681    /// ```
682    ///
683    /// ## Error Propagation
684    ///
685    /// ```rust
686    /// use app_path::{AppPath, AppPathError};
687    ///
688    /// fn setup_paths(config_override: Option<&str>) -> Result<(AppPath, AppPath), AppPathError> {
689    ///     let config = AppPath::try_with_override("config.toml", config_override)?;
690    ///     let data = AppPath::try_with_override("data", None::<&str>)?;
691    ///     Ok((config, data))
692    /// }
693    /// ```
694    #[inline]
695    pub fn try_with_override(
696        default: impl AsRef<Path>,
697        override_option: Option<impl AsRef<Path>>,
698    ) -> Result<Self, AppPathError> {
699        match override_option {
700            Some(override_path) => Self::try_new(override_path),
701            None => Self::try_new(default),
702        }
703    }
704
705    /// Creates a path with dynamic override support (fallible).
706    ///
707    /// This is the fallible version of [`AppPath::with_override_fn()`]. Use this method
708    /// when you need explicit error handling combined with dynamic override logic.
709    ///
710    /// **Most applications should use [`AppPath::with_override_fn()`] instead** for
711    /// cleaner, more idiomatic code.
712    ///
713    /// ## When to Use This Method
714    ///
715    /// - **Reusable libraries** with complex override logic that should handle errors gracefully
716    /// - **System-level tools** with dynamic configuration that need to handle broken environments
717    /// - **Applications with custom fallback strategies** for rare edge cases
718    ///
719    /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
720    ///
721    /// # Arguments
722    ///
723    /// * `default` - The default path to use if the override function returns `None`
724    /// * `override_fn` - A function that returns an optional override path
725    ///
726    /// # Returns
727    ///
728    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
729    /// * `Err(AppPathError)` - Failed to determine executable location
730    ///
731    /// # Examples
732    ///
733    /// ## Library with Complex Override Logic
734    ///
735    /// ```rust
736    /// use app_path::{AppPath, AppPathError};
737    /// use std::env;
738    ///
739    /// fn create_data_path() -> Result<AppPath, AppPathError> {
740    ///     AppPath::try_with_override_fn("data", || {
741    ///         // Complex override logic with multiple sources
742    ///         env::var("DATA_DIR").ok()
743    ///             .or_else(|| env::var("MYAPP_DATA_DIR").ok())
744    ///             .or_else(|| {
745    ///                 if env::var("DEVELOPMENT").is_ok() {
746    ///                     Some("dev_data".to_string())
747    ///                 } else {
748    ///                     None
749    ///                 }
750    ///             })
751    ///     })
752    /// }
753    /// ```
754    ///
755    /// ## Error Propagation with Dynamic Logic
756    ///
757    /// ```rust
758    /// use app_path::{AppPath, AppPathError};
759    ///
760    /// fn setup_logging() -> Result<AppPath, AppPathError> {
761    ///     AppPath::try_with_override_fn("logs/app.log", || {
762    ///         // Dynamic override based on multiple conditions
763    ///         if std::env::var("SYSLOG").is_ok() {
764    ///             Some("/var/log/myapp.log".to_string())
765    ///         } else if std::env::var("LOG_TO_TEMP").is_ok() {
766    ///             Some(std::env::temp_dir().join("myapp.log").to_string_lossy().into_owned())
767    ///         } else {
768    ///             None
769    ///         }
770    ///     })
771    /// }
772    /// ```
773    #[inline]
774    pub fn try_with_override_fn<F, P>(
775        default: impl AsRef<Path>,
776        override_fn: F,
777    ) -> Result<Self, AppPathError>
778    where
779        F: FnOnce() -> Option<P>,
780        P: AsRef<Path>,
781    {
782        match override_fn() {
783            Some(override_path) => Self::try_new(override_path),
784            None => Self::try_new(default),
785        }
786    }
787
788    /// Joins additional path segments to create a new AppPath.
789    ///
790    /// This creates a new `AppPath` by joining the current path with additional segments.
791    /// The new path inherits the same resolution behavior as the original.
792    ///
793    /// # Examples
794    ///
795    /// ```rust
796    /// use app_path::AppPath;
797    ///
798    /// let data_dir = AppPath::new("data");
799    /// let users_db = data_dir.join("users.db");
800    /// let backups = data_dir.join("backups").join("daily");
801    ///
802    /// // Chain operations for complex paths
803    /// let log_file = AppPath::new("logs")
804    ///     .join("2024")
805    ///     .join("app.log");
806    /// ```
807    #[inline]
808    pub fn join(&self, path: impl AsRef<Path>) -> Self {
809        Self::new(self.full_path.join(path))
810    }
811
812    /// Returns the parent directory as an AppPath, if it exists.
813    ///
814    /// Returns `None` if this path is a root directory or has no parent.
815    ///
816    /// # Examples
817    ///
818    /// ```rust
819    /// use app_path::AppPath;    ///
820    /// let config = AppPath::new("config/app.toml");
821    /// let config_dir = config.parent().unwrap();
822    ///
823    /// let logs_dir = AppPath::new("logs");
824    /// let _app_dir = logs_dir.parent(); // Points to exe directory
825    /// ```
826    #[inline]
827    pub fn parent(&self) -> Option<Self> {
828        self.full_path.parent().map(Self::new)
829    }
830
831    /// Creates a new AppPath with the specified file extension.
832    ///
833    /// If the path has an existing extension, it will be replaced.
834    /// If no extension exists, the new extension will be added.
835    ///
836    /// # Examples
837    ///
838    /// ```rust
839    /// use app_path::AppPath;
840    ///
841    /// let config = AppPath::new("config");
842    /// let config_toml = config.with_extension("toml");
843    /// let config_json = config.with_extension("json");
844    ///
845    /// let log_file = AppPath::new("app.log");
846    /// let backup_file = log_file.with_extension("bak");
847    /// ```
848    #[inline]
849    pub fn with_extension(&self, ext: &str) -> Self {
850        Self::new(self.full_path.with_extension(ext))
851    }
852
853    /// Returns the file name of this path as an `OsStr`, if it exists.
854    ///
855    /// This is a convenience method that delegates to the underlying `Path`.
856    ///
857    /// # Examples
858    ///
859    /// ```rust
860    /// use app_path::AppPath;
861    ///
862    /// let config = AppPath::new("config/app.toml");
863    /// assert_eq!(config.file_name().unwrap(), "app.toml");
864    /// ```
865    #[inline]
866    pub fn file_name(&self) -> Option<&std::ffi::OsStr> {
867        self.full_path.file_name()
868    }
869
870    /// Returns the file stem of this path as an `OsStr`, if it exists.
871    ///
872    /// This is a convenience method that delegates to the underlying `Path`.
873    ///
874    /// # Examples
875    ///
876    /// ```rust
877    /// use app_path::AppPath;
878    ///
879    /// let config = AppPath::new("config/app.toml");
880    /// assert_eq!(config.file_stem().unwrap(), "app");
881    /// ```
882    #[inline]
883    pub fn file_stem(&self) -> Option<&std::ffi::OsStr> {
884        self.full_path.file_stem()
885    }
886
887    /// Returns the extension of this path as an `OsStr`, if it exists.
888    ///
889    /// This is a convenience method that delegates to the underlying `Path`.
890    ///
891    /// # Examples
892    ///
893    /// ```rust
894    /// use app_path::AppPath;
895    ///
896    /// let config = AppPath::new("config/app.toml");
897    /// assert_eq!(config.extension().unwrap(), "toml");
898    /// ```
899    #[inline]
900    pub fn extension(&self) -> Option<&std::ffi::OsStr> {
901        self.full_path.extension()
902    }
903
904    /// Returns `true` if this path points to a directory.
905    ///
906    /// This is a convenience method that delegates to the underlying `Path`.
907    ///
908    /// # Examples
909    ///
910    /// ```rust
911    /// use app_path::AppPath;
912    ///
913    /// let data_dir = AppPath::new("data");
914    /// if data_dir.is_dir() {
915    ///     println!("Data directory exists");
916    /// }
917    /// ```
918    #[inline]
919    pub fn is_dir(&self) -> bool {
920        self.full_path.is_dir()
921    }
922
923    /// Returns `true` if this path points to a regular file.
924    ///
925    /// This is a convenience method that delegates to the underlying `Path`.
926    ///
927    /// # Examples
928    ///
929    /// ```rust
930    /// use app_path::AppPath;
931    ///
932    /// let config = AppPath::new("config.toml");
933    /// if config.is_file() {
934    ///     println!("Config file exists");
935    /// }
936    /// ```
937    #[inline]
938    pub fn is_file(&self) -> bool {
939        self.full_path.is_file()
940    }
941}