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}