app_path/app_path/
constructors.rs

1use std::path::Path;
2
3use crate::{try_exe_dir, AppPath, AppPathError};
4
5impl AppPath {
6    /// Returns the application's base directory as an AppPath.
7    ///
8    /// **This is the primary method for getting the application's base directory.** It provides clean,
9    /// idiomatic code for the common case of needing the application's base location.
10    ///
11    /// By default, the application's base directory is the executable's directory, but this can
12    /// be overridden through environment variables or other configuration mechanisms using the
13    /// override methods like [`Self::with_override()`].
14    ///
15    /// Use this when you need the application's base directory itself, then use [`join()`](Self::join)
16    /// to build paths relative to it, or use [`Self::with()`] directly for one-step creation.
17    ///
18    /// ## Global Caching Behavior
19    ///
20    /// The application's base directory is determined once on first call and cached globally.
21    /// All subsequent calls use the cached value for maximum performance.
22    ///
23    /// # Panics
24    ///
25    /// Panics only if the application's base directory cannot be determined, which is extremely rare
26    /// and typically indicates fundamental system issues (corrupted installation, permission problems).
27    /// After the first successful call, this method never panics.
28    ///
29    /// # Examples
30    ///
31    /// ## Basic Usage
32    ///
33    /// ```rust
34    /// use app_path::AppPath;
35    ///
36    /// // Get the application's base directory (defaults to executable directory)
37    /// let app_base = AppPath::new();
38    ///
39    /// // Build paths relative to it
40    /// let config = app_base.join("config.toml");
41    /// let data_dir = app_base.join("data");
42    ///
43    /// // Or chain operations
44    /// let log_file = AppPath::new().join("logs").join("app.log");
45    /// ```
46    ///
47    /// ## Real Application Examples
48    ///
49    /// ```rust
50    /// use app_path::AppPath;
51    /// use std::fs;
52    ///
53    /// // Get application base directory for setup operations
54    /// let app_base = AppPath::new();
55    /// println!("Application base directory: {}", app_base.display());
56    ///
57    /// // Create application directory structure
58    /// app_base.join("data").create_dir()?;
59    /// app_base.join("logs").create_dir()?;
60    /// app_base.join("config").create_dir()?;
61    ///
62    /// # Ok::<(), Box<dyn std::error::Error>>(())
63    /// ```
64    ///
65    /// ## Portable Directory Access
66    ///
67    /// ```rust
68    /// use app_path::AppPath;
69    ///
70    /// // Get application base directory regardless of installation location:
71    /// // - C:\Program Files\MyApp\ (Windows)
72    /// // - /usr/local/bin/ (Linux)
73    /// // - /Applications/MyApp.app/Contents/MacOS/ (macOS)
74    /// // - .\target\debug\ (development)
75    ///
76    /// let exe_dir = AppPath::new();
77    /// let readme = exe_dir.join("README.txt");
78    /// let license = exe_dir.join("LICENSE");
79    ///
80    /// # Ok::<(), Box<dyn std::error::Error>>(())
81    /// ```
82    #[inline]
83    pub fn new() -> Self {
84        match Self::try_new() {
85            Ok(app_path) => app_path,
86            Err(e) => panic!("Failed to create AppPath: {e}"),
87        }
88    }
89
90    /// Creates file paths relative to the executable location (fallible).
91    ///
92    /// **Use this only for libraries or specialized applications requiring explicit error handling.**
93    /// Most applications should use [`Self::new()`] instead for simpler, cleaner code.
94    ///
95    /// ## When to Use
96    ///
97    /// **Use `try_new()` for:**
98    /// - Reusable libraries that shouldn't panic
99    /// - System tools with fallback strategies
100    /// - Applications running in unusual environments
101    ///
102    /// **Use [`Self::new()`] for:**
103    /// - Desktop, web, server, CLI applications
104    /// - When you want simple, clean code (recommended)
105    ///
106    /// # Examples
107    ///
108    /// ```rust
109    /// use app_path::{AppPath, AppPathError};
110    ///
111    /// // Library with graceful error handling
112    /// fn load_config() -> Result<String, AppPathError> {
113    ///     let config_path = AppPath::try_with("config.toml")?;
114    ///     // Load configuration...
115    ///     Ok("config loaded".to_string())
116    /// }
117    ///
118    /// // Better: Use override API for environment variables
119    /// fn load_config_with_override() -> Result<String, AppPathError> {
120    ///     let config_path = AppPath::try_with_override(
121    ///         "config.toml",
122    ///         std::env::var("APP_CONFIG").ok()
123    ///     )?;
124    ///     // Load configuration...
125    ///     Ok("config loaded".to_string())
126    /// }
127    ///
128    /// // Multiple environment variable fallback (better approach)
129    /// fn get_config_with_fallback() -> Result<AppPath, AppPathError> {
130    ///     AppPath::try_with_override_fn("config.toml", || {
131    ///         std::env::var("APP_CONFIG").ok()
132    ///             .or_else(|| std::env::var("CONFIG_FILE").ok())
133    ///             .or_else(|| std::env::var("XDG_CONFIG_HOME").ok().map(|dir| format!("{dir}/myapp/config.toml")))
134    ///     })
135    /// }
136    /// ```
137    ///
138    /// **Reality check:** Executable location determination failing is extremely rare:
139    /// - It requires fundamental system issues or unusual deployment scenarios
140    /// - When it happens, it usually indicates unrecoverable system problems
141    /// - Most applications can't meaningfully continue without knowing their location
142    /// - The error handling overhead isn't worth it for typical applications
143    ///
144    /// **Better approaches for most applications:**
145    /// ```rust
146    /// use app_path::AppPath;
147    /// use std::env;
148    ///
149    /// // Use our override API for environment variables (recommended)
150    /// fn get_config_path() -> AppPath {
151    ///     AppPath::with_override("config.toml", env::var("MYAPP_CONFIG_DIR").ok())
152    /// }
153    ///
154    /// // Or fallible version for libraries
155    /// fn try_get_config_path() -> Result<AppPath, app_path::AppPathError> {
156    ///     AppPath::try_with_override("config.toml", env::var("MYAPP_CONFIG_DIR").ok())
157    /// }
158    /// ```
159    ///
160    /// ## Global Caching Behavior
161    ///
162    /// Once the application's base directory is successfully determined by either this method or [`AppPath::new()`],
163    /// the result is cached globally and all subsequent calls to both methods will use the cached value.
164    /// This means that after the first successful call, `try_new()` will never return an error.
165    ///
166    /// # Arguments
167    ///
168    /// * `path` - A path that will be resolved according to AppPath's resolution strategy.
169    ///   Accepts any type implementing [`AsRef<Path>`].
170    ///
171    /// # Returns
172    ///
173    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
174    /// * `Err(AppPathError)` - Failed to determine executable location (extremely rare)
175    ///
176    /// # Examples
177    ///
178    /// ## Library Error Handling
179    ///
180    /// ```rust
181    /// use app_path::{AppPath, AppPathError};
182    ///
183    /// // Library function that returns Result instead of panicking
184    /// pub fn create_config_manager() -> Result<ConfigManager, AppPathError> {
185    ///     let config_path = AppPath::try_with("config.toml")?;
186    ///     Ok(ConfigManager::new(config_path))
187    /// }
188    ///
189    /// pub struct ConfigManager {
190    ///     config_path: AppPath,
191    /// }
192    ///
193    /// impl ConfigManager {
194    ///     fn new(config_path: AppPath) -> Self {
195    ///         Self { config_path }
196    ///     }
197    /// }
198    /// ```
199    ///
200    /// ## Error Propagation Pattern
201    ///
202    /// ```rust
203    /// use app_path::{AppPath, AppPathError};
204    ///
205    /// fn initialize_app() -> Result<(), Box<dyn std::error::Error>> {
206    ///     let config = AppPath::try_with("config.toml")?;
207    ///     let data = AppPath::try_with("data/app.db")?;
208    ///     
209    ///     // Initialize application with these paths
210    ///     println!("Config: {}", config.display());
211    ///     println!("Data: {}", data.display());
212    ///     
213    ///     Ok(())
214    /// }
215    /// ```
216    ///
217    /// # Errors
218    ///
219    /// Returns [`AppPathError`] if the executable location cannot be determined:
220    /// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)
221    /// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
222    ///
223    /// These errors represent unrecoverable system failures that occur at application startup.
224    /// After the first successful call, the application's base directory is cached and this method
225    /// will never return an error.
226    #[inline]
227    pub fn try_new() -> Result<Self, AppPathError> {
228        let exe_dir = try_exe_dir()?;
229        Ok(Self {
230            full_path: exe_dir.to_path_buf(),
231        })
232    }
233
234    /// Creates file paths relative to the application's base directory (fallible).
235    ///
236    /// **Use this only for libraries or specialized applications requiring explicit error handling.**
237    /// Most applications should use [`Self::with()`] instead for simpler, cleaner code.
238    ///
239    /// ## When to Use
240    ///
241    /// **Use `try_with()` for:**
242    /// - Reusable libraries that shouldn't panic
243    /// - System tools with fallback strategies
244    /// - Applications running in unusual environments
245    ///
246    /// **Use [`Self::with()`] for:**
247    /// - Desktop, web, server, CLI applications
248    /// - When you want simple, clean code (recommended)
249    ///
250    /// # Examples
251    ///
252    /// ```rust
253    /// use app_path::{AppPath, AppPathError};
254    ///
255    /// // Library with graceful error handling
256    /// fn load_config() -> Result<String, AppPathError> {
257    ///     let config_path = AppPath::try_with("config.toml")?;
258    ///     // Load configuration...
259    ///     Ok("config loaded".to_string())
260    /// }
261    ///
262    /// // Better: Use override API for environment variables
263    /// fn load_config_with_override() -> Result<String, AppPathError> {
264    ///     let config_path = AppPath::try_with_override(
265    ///         "config.toml",
266    ///         std::env::var("APP_CONFIG").ok()
267    ///     )?;
268    ///     // Load configuration...
269    ///     Ok("config loaded".to_string())
270    /// }
271    /// ```
272    ///
273    /// After the first successful call, the application's base directory is cached and this method
274    /// will never return an error.
275    #[inline]
276    pub fn try_with(path: impl AsRef<Path>) -> Result<Self, AppPathError> {
277        let exe_dir = try_exe_dir()?;
278        let full_path = exe_dir.join(path);
279        Ok(Self { full_path })
280    }
281
282    /// Creates file paths relative to the application's base directory.
283    ///
284    /// **This is the primary method for creating paths relative to your application's base directory.**
285    /// It provides clean, idiomatic code for the 99% of applications that don't need explicit error handling.
286    ///
287    /// AppPath automatically resolves relative paths based on your application's base directory,
288    /// making file access portable and predictable across different deployment scenarios.
289    ///
290    /// ## When to Use
291    ///
292    /// **Use `with()` for:**
293    /// - Desktop, web, server, CLI applications (recommended)
294    /// - When you want simple, clean code
295    /// - When application location issues should halt the application
296    ///
297    /// **Use [`Self::try_with()`] for:**
298    /// - Reusable libraries that shouldn't panic  
299    /// - System tools with fallback strategies
300    /// - Applications running in unusual environments
301    ///
302    /// ## Path Resolution
303    ///
304    /// - **Relative paths**: Resolved relative to application's base directory
305    /// - **Absolute paths**: Used as-is (not recommended - defeats portability)
306    /// - **Path separators**: Automatically normalized for current platform
307    ///
308    /// ## Global Caching Behavior
309    ///
310    /// The application's base directory is determined once on first call and cached globally.
311    /// All subsequent calls use the cached value for maximum performance.
312    ///
313    /// # Panics
314    ///
315    /// Panics only if the application's base directory cannot be determined, which is extremely rare
316    /// and typically indicates fundamental system issues (corrupted installation, permission problems).
317    /// After the first successful call, this method never panics.
318    ///
319    /// # Arguments
320    ///
321    /// * `path` - A path that will be resolved according to AppPath's resolution strategy.
322    ///   Accepts any type implementing [`AsRef<Path>`] (strings, Path, PathBuf, etc.).
323    ///
324    /// # Examples
325    ///
326    /// ## Basic Usage
327    ///
328    /// ```rust
329    /// use app_path::AppPath;
330    ///
331    /// // Configuration file next to application
332    /// let config = AppPath::with("config.toml");
333    ///
334    /// // Data directory relative to application
335    /// let data_dir = AppPath::with("data");
336    ///
337    /// // Nested paths work naturally
338    /// let user_profile = AppPath::with("data/users/profile.json");
339    /// let log_file = AppPath::with("logs/app.log");
340    /// ```
341    ///
342    /// ## Real Application Examples
343    ///
344    /// ```rust
345    /// use app_path::AppPath;
346    /// use std::fs;
347    ///
348    /// // Load configuration with fallback to defaults
349    /// let config_path = AppPath::with("config.toml");
350    /// let config = if config_path.exists() {
351    ///     fs::read_to_string(&config_path)?
352    /// } else {
353    ///     "default_config = true".to_string() // Use defaults
354    /// };
355    ///
356    /// // Set up application data directory
357    /// let data_dir = AppPath::with("data");
358    /// data_dir.create_dir()?; // Creates directory if needed
359    ///
360    /// // Prepare for log file creation
361    /// let log_file = AppPath::with("logs/app.log");
362    /// log_file.create_parents()?; // Ensures logs/ directory exists
363    ///
364    /// # Ok::<(), Box<dyn std::error::Error>>(())
365    /// ```
366    ///
367    /// ## Portable File Access
368    ///
369    /// ```rust
370    /// use app_path::AppPath;
371    /// use std::fs;
372    ///
373    /// // These paths work regardless of where the application is installed:
374    /// // - C:\Program Files\MyApp\config.toml (Windows)
375    /// // - /usr/local/bin/config.toml (Linux)
376    /// // - /Applications/MyApp.app/Contents/MacOS/config.toml (macOS)
377    /// // - .\myapp\config.toml (development)
378    ///
379    /// let settings = AppPath::with("settings.json");
380    /// let cache = AppPath::with("cache");
381    /// let templates = AppPath::with("templates/default.html");
382    ///
383    /// // Use with standard library functions
384    /// if settings.exists() {
385    ///     let content = fs::read_to_string(&settings)?;
386    /// }
387    /// cache.create_dir()?; // Creates cache/ directory
388    ///
389    /// # Ok::<(), Box<dyn std::error::Error>>(())
390    /// ```
391    #[inline]
392    pub fn with(path: impl AsRef<Path>) -> Self {
393        match Self::try_with(path) {
394            Ok(app_path) => app_path,
395            Err(e) => panic!("Failed to create AppPath: {e}"),
396        }
397    }
398
399    /// Creates a path with override support (infallible).
400    ///
401    /// This method provides a one-line solution for creating paths that can be overridden
402    /// by external configuration. If an override is provided, it takes precedence over
403    /// the default path. Otherwise, the default path is used with normal AppPath resolution.
404    ///
405    /// **This is the primary method for implementing configurable paths in applications.**
406    /// It combines the simplicity of [`AppPath::new()`] with the flexibility of external
407    /// configuration overrides.
408    ///
409    /// ## Common Use Cases
410    ///
411    /// - **Environment variable overrides**: Allow users to customize file locations
412    /// - **Command-line argument overrides**: CLI tools with configurable paths
413    ///
414    /// ## How It Works
415    ///
416    /// **If override is provided**: Use the override path directly (can be relative or absolute)
417    /// **If override is `None`**: Use the default path with normal AppPath resolution
418    ///
419    /// # Examples
420    ///
421    /// ```rust
422    /// use app_path::AppPath;
423    /// use std::env;
424    ///
425    /// // Environment variable override
426    /// let config = AppPath::with_override(
427    ///     "config.toml",
428    ///     env::var("APP_CONFIG").ok()
429    /// );
430    ///
431    /// // CLI argument override
432    /// fn get_config(cli_override: Option<&str>) -> AppPath {
433    ///     AppPath::with_override("config.toml", cli_override)
434    /// }
435    ///
436    /// // Configuration file override
437    /// struct Config {
438    ///     data_dir: Option<String>,
439    /// }
440    ///
441    /// let config = load_config();
442    /// let data_dir = AppPath::with_override("data", config.data_dir.as_deref());
443    /// # fn load_config() -> Config { Config { data_dir: None } }
444    /// ```
445    #[inline]
446    pub fn with_override(
447        default: impl AsRef<Path>,
448        override_option: Option<impl AsRef<Path>>,
449    ) -> Self {
450        match override_option {
451            Some(override_path) => Self::with(override_path),
452            None => Self::with(default),
453        }
454    }
455
456    /// Creates a path with dynamic override support.
457    ///
458    /// **Use this for complex override logic or lazy evaluation.** The closure is called once
459    /// to determine if an override should be applied.
460    ///
461    /// # Examples
462    ///
463    /// ```rust
464    /// use app_path::AppPath;
465    /// use std::env;
466    ///
467    /// // Multiple fallback sources
468    /// let config = AppPath::with_override_fn("config.toml", || {
469    ///     env::var("APP_CONFIG").ok()
470    ///         .or_else(|| env::var("CONFIG_FILE").ok())
471    ///         .or_else(|| {
472    ///             // Only check expensive operations if needed
473    ///             if env::var("USE_SYSTEM_CONFIG").is_ok() {
474    ///                 Some("/etc/myapp/config.toml".to_string())
475    ///             } else {
476    ///                 None
477    ///             }
478    ///         })
479    /// });
480    ///
481    /// // Development mode override
482    /// let data_dir = AppPath::with_override_fn("data", || {
483    ///     if env::var("DEVELOPMENT").is_ok() {
484    ///         Some("dev_data".to_string())
485    ///     } else {
486    ///         None
487    ///     }
488    /// });
489    /// ```
490    #[inline]
491    pub fn with_override_fn<P: AsRef<Path>>(
492        default: impl AsRef<Path>,
493        override_fn: impl FnOnce() -> Option<P>,
494    ) -> Self {
495        match override_fn() {
496            Some(override_path) => Self::with(override_path),
497            None => Self::with(default),
498        }
499    }
500
501    /// Creates a path with override support (fallible).
502    ///
503    /// **Fallible version of [`Self::with_override()`].** Most applications should use the
504    /// infallible version instead for cleaner code.
505    ///
506    /// # Examples
507    ///
508    /// ```rust
509    /// use app_path::{AppPath, AppPathError};
510    /// use std::env;
511    ///
512    /// fn get_config() -> Result<AppPath, AppPathError> {
513    ///     AppPath::try_with_override("config.toml", env::var("CONFIG").ok())
514    /// }
515    /// ```
516    /// cleaner, more idiomatic code.
517    ///
518    /// ## When to Use This Method
519    ///
520    /// - **Reusable libraries** that should handle errors gracefully
521    /// - **System-level tools** that need to handle broken environments
522    /// - **Applications with custom fallback strategies** for rare edge cases
523    ///
524    /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
525    ///
526    /// # Arguments
527    ///
528    /// * `default` - The default path to use if no override is provided
529    /// * `override_option` - Optional override path that takes precedence if provided
530    ///
531    /// # Returns
532    ///
533    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
534    /// * `Err(AppPathError)` - Failed to determine executable location
535    ///
536    /// # Examples
537    ///
538    /// ## Library with Error Handling
539    ///
540    /// ```rust
541    /// use app_path::{AppPath, AppPathError};
542    /// use std::env;
543    ///
544    /// fn create_config_path() -> Result<AppPath, AppPathError> {
545    ///     let config_override = env::var("MYAPP_CONFIG").ok();
546    ///     AppPath::try_with_override("config.toml", config_override.as_deref())
547    /// }
548    /// ```
549    ///
550    /// ## Error Propagation
551    ///
552    /// ```rust
553    /// use app_path::{AppPath, AppPathError};
554    ///
555    /// fn setup_paths(config_override: Option<&str>) -> Result<(AppPath, AppPath), AppPathError> {
556    ///     let config = AppPath::try_with_override("config.toml", config_override)?;
557    ///     let data = AppPath::try_with_override("data", None::<&str>)?;
558    ///     Ok((config, data))
559    /// }
560    /// ```
561    ///
562    /// # Errors
563    ///
564    /// Returns [`AppPathError`] if the executable location cannot be determined:
565    /// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)
566    /// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
567    ///
568    /// See [`AppPath::try_new()`] for detailed error conditions. After the first successful call
569    /// to any AppPath method, this method will never return an error (uses cached result).
570    #[inline]
571    pub fn try_with_override(
572        default: impl AsRef<Path>,
573        override_option: Option<impl AsRef<Path>>,
574    ) -> Result<Self, AppPathError> {
575        match override_option {
576            Some(override_path) => Self::try_with(override_path),
577            None => Self::try_with(default),
578        }
579    }
580
581    /// Creates a path with dynamic override support (fallible).
582    ///
583    /// This is the fallible version of [`AppPath::with_override_fn()`]. Use this method
584    /// when you need explicit error handling combined with dynamic override logic.
585    ///
586    /// **Most applications should use [`AppPath::with_override_fn()`] instead** for
587    /// cleaner, more idiomatic code.
588    ///
589    /// ## When to Use This Method
590    ///
591    /// - **Reusable libraries** with complex override logic that should handle errors gracefully
592    /// - **System-level tools** with dynamic configuration that need to handle broken environments
593    /// - **Applications with custom fallback strategies** for rare edge cases
594    ///
595    /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
596    ///
597    /// # Arguments
598    ///
599    /// * `default` - The default path to use if the override function returns `None`
600    /// * `override_fn` - A function that returns an optional override path
601    ///
602    /// # Returns
603    ///
604    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
605    /// * `Err(AppPathError)` - Failed to determine executable location
606    ///
607    /// # Examples
608    ///
609    /// ## Library with Complex Override Logic
610    ///
611    /// ```rust
612    /// use app_path::{AppPath, AppPathError};
613    /// use std::env;
614    ///
615    /// fn create_data_path() -> Result<AppPath, AppPathError> {
616    ///     AppPath::try_with_override_fn("data", || {
617    ///         // Complex override logic with multiple sources
618    ///         env::var("DATA_DIR").ok()
619    ///             .or_else(|| env::var("MYAPP_DATA_DIR").ok())
620    ///             .or_else(|| {
621    ///                 if env::var("DEVELOPMENT").is_ok() {
622    ///                     Some("dev_data".to_string())
623    ///                 } else {
624    ///                     None
625    ///                 }
626    ///             })
627    ///     })
628    /// }
629    /// ```
630    ///
631    /// ## Error Propagation with Dynamic Logic
632    ///
633    /// ```rust
634    /// use app_path::{AppPath, AppPathError};
635    ///
636    /// fn setup_logging() -> Result<AppPath, AppPathError> {
637    ///     AppPath::try_with_override_fn("logs/app.log", || {
638    ///         // Dynamic override based on multiple conditions
639    ///         if std::env::var("SYSLOG").is_ok() {
640    ///             Some("/var/log/myapp.log".to_string())
641    ///         } else if std::env::var("LOG_TO_TEMP").is_ok() {
642    ///             Some(std::env::temp_dir().join("myapp.log").to_string_lossy().into_owned())
643    ///         } else {
644    ///             None
645    ///         }
646    ///     })
647    /// }
648    /// ```
649    ///
650    /// # Errors
651    ///
652    /// Returns [`AppPathError`] if the executable location cannot be determined:
653    /// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)  
654    /// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
655    ///
656    /// See [`AppPath::try_new()`] for detailed error conditions. After the first successful call
657    /// to any AppPath method, this method will never return an error (uses cached result).
658    #[inline]
659    pub fn try_with_override_fn<P: AsRef<Path>>(
660        default: impl AsRef<Path>,
661        override_fn: impl FnOnce() -> Option<P>,
662    ) -> Result<Self, AppPathError> {
663        match override_fn() {
664            Some(override_path) => Self::try_with(override_path),
665            None => Self::try_with(default),
666        }
667    }
668}