app_path/app_path/
constructors.rs

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