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 ///
250 /// # Errors
251 ///
252 /// Returns [`AppPathError`] if the executable location cannot be determined:
253 /// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)
254 /// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
255 ///
256 /// These errors represent unrecoverable system failures that occur at application startup.
257 /// After the first successful call, the executable directory is cached and this method
258 /// will never return an error.
259 #[inline]
260 pub fn try_new(path: impl AsRef<Path>) -> Result<Self, AppPathError> {
261 let exe_dir = try_exe_dir()?;
262 let full_path = exe_dir.join(path);
263 Ok(Self { full_path })
264 }
265
266 /// Creates a path with override support (infallible).
267 ///
268 /// This method provides a one-line solution for creating paths that can be overridden
269 /// by external configuration. If an override is provided, it takes precedence over
270 /// the default path. Otherwise, the default path is used with normal AppPath resolution.
271 ///
272 /// **This is the primary method for implementing configurable paths in applications.**
273 /// It combines the simplicity of [`AppPath::new()`] with the flexibility of external
274 /// configuration overrides.
275 ///
276 /// ## Common Use Cases
277 ///
278 /// - **Environment variable overrides**: Allow users to customize file locations
279 /// - **Command-line argument overrides**: CLI tools with configurable paths
280 ///
281 /// ## How It Works
282 ///
283 /// **If override is provided**: Use the override path directly (can be relative or absolute)
284 /// **If override is `None`**: Use the default path with normal AppPath resolution
285 ///
286 /// # Examples
287 ///
288 /// ```rust
289 /// use app_path::AppPath;
290 /// use std::env;
291 ///
292 /// // Environment variable override
293 /// let config = AppPath::with_override(
294 /// "config.toml",
295 /// env::var("APP_CONFIG").ok()
296 /// );
297 ///
298 /// // CLI argument override
299 /// fn get_config(cli_override: Option<&str>) -> AppPath {
300 /// AppPath::with_override("config.toml", cli_override)
301 /// }
302 ///
303 /// // Configuration file override
304 /// struct Config {
305 /// data_dir: Option<String>,
306 /// }
307 ///
308 /// let config = load_config();
309 /// let data_dir = AppPath::with_override("data", config.data_dir.as_deref());
310 /// # fn load_config() -> Config { Config { data_dir: None } }
311 /// ```
312 #[inline]
313 pub fn with_override(
314 default: impl AsRef<Path>,
315 override_option: Option<impl AsRef<Path>>,
316 ) -> Self {
317 match override_option {
318 Some(override_path) => Self::new(override_path),
319 None => Self::new(default),
320 }
321 }
322
323 /// Creates a path with dynamic override support.
324 ///
325 /// **Use this for complex override logic or lazy evaluation.** The closure is called once
326 /// to determine if an override should be applied.
327 ///
328 /// # Examples
329 ///
330 /// ```rust
331 /// use app_path::AppPath;
332 /// use std::env;
333 ///
334 /// // Multiple fallback sources
335 /// let config = AppPath::with_override_fn("config.toml", || {
336 /// env::var("APP_CONFIG").ok()
337 /// .or_else(|| env::var("CONFIG_FILE").ok())
338 /// .or_else(|| {
339 /// // Only check expensive operations if needed
340 /// if env::var("USE_SYSTEM_CONFIG").is_ok() {
341 /// Some("/etc/myapp/config.toml".to_string())
342 /// } else {
343 /// None
344 /// }
345 /// })
346 /// });
347 ///
348 /// // Development mode override
349 /// let data_dir = AppPath::with_override_fn("data", || {
350 /// if env::var("DEVELOPMENT").is_ok() {
351 /// Some("dev_data".to_string())
352 /// } else {
353 /// None
354 /// }
355 /// });
356 /// ```
357 #[inline]
358 pub fn with_override_fn<F, P>(default: impl AsRef<Path>, override_fn: F) -> Self
359 where
360 F: FnOnce() -> Option<P>,
361 P: AsRef<Path>,
362 {
363 match override_fn() {
364 Some(override_path) => Self::new(override_path),
365 None => Self::new(default),
366 }
367 }
368
369 /// Creates a path with override support (fallible).
370 ///
371 /// **Fallible version of [`Self::with_override()`].** Most applications should use the
372 /// infallible version instead for cleaner code.
373 ///
374 /// # Examples
375 ///
376 /// ```rust
377 /// use app_path::{AppPath, AppPathError};
378 /// use std::env;
379 ///
380 /// fn get_config() -> Result<AppPath, AppPathError> {
381 /// AppPath::try_with_override("config.toml", env::var("CONFIG").ok())
382 /// }
383 /// ```
384 /// cleaner, more idiomatic code.
385 ///
386 /// ## When to Use This Method
387 ///
388 /// - **Reusable libraries** that should handle errors gracefully
389 /// - **System-level tools** that need to handle broken environments
390 /// - **Applications with custom fallback strategies** for rare edge cases
391 ///
392 /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
393 ///
394 /// # Arguments
395 ///
396 /// * `default` - The default path to use if no override is provided
397 /// * `override_option` - Optional override path that takes precedence if provided
398 ///
399 /// # Returns
400 ///
401 /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
402 /// * `Err(AppPathError)` - Failed to determine executable location
403 ///
404 /// # Examples
405 ///
406 /// ## Library with Error Handling
407 ///
408 /// ```rust
409 /// use app_path::{AppPath, AppPathError};
410 /// use std::env;
411 ///
412 /// fn create_config_path() -> Result<AppPath, AppPathError> {
413 /// let config_override = env::var("MYAPP_CONFIG").ok();
414 /// AppPath::try_with_override("config.toml", config_override.as_deref())
415 /// }
416 /// ```
417 ///
418 /// ## Error Propagation
419 ///
420 /// ```rust
421 /// use app_path::{AppPath, AppPathError};
422 ///
423 /// fn setup_paths(config_override: Option<&str>) -> Result<(AppPath, AppPath), AppPathError> {
424 /// let config = AppPath::try_with_override("config.toml", config_override)?;
425 /// let data = AppPath::try_with_override("data", None::<&str>)?;
426 /// Ok((config, data))
427 /// }
428 /// ```
429 ///
430 /// # Errors
431 ///
432 /// Returns [`AppPathError`] if the executable location cannot be determined:
433 /// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)
434 /// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
435 ///
436 /// See [`AppPath::try_new()`] for detailed error conditions. After the first successful call
437 /// to any AppPath method, this method will never return an error (uses cached result).
438 #[inline]
439 pub fn try_with_override(
440 default: impl AsRef<Path>,
441 override_option: Option<impl AsRef<Path>>,
442 ) -> Result<Self, AppPathError> {
443 match override_option {
444 Some(override_path) => Self::try_new(override_path),
445 None => Self::try_new(default),
446 }
447 }
448
449 /// Creates a path with dynamic override support (fallible).
450 ///
451 /// This is the fallible version of [`AppPath::with_override_fn()`]. Use this method
452 /// when you need explicit error handling combined with dynamic override logic.
453 ///
454 /// **Most applications should use [`AppPath::with_override_fn()`] instead** for
455 /// cleaner, more idiomatic code.
456 ///
457 /// ## When to Use This Method
458 ///
459 /// - **Reusable libraries** with complex override logic that should handle errors gracefully
460 /// - **System-level tools** with dynamic configuration that need to handle broken environments
461 /// - **Applications with custom fallback strategies** for rare edge cases
462 ///
463 /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
464 ///
465 /// # Arguments
466 ///
467 /// * `default` - The default path to use if the override function returns `None`
468 /// * `override_fn` - A function that returns an optional override path
469 ///
470 /// # Returns
471 ///
472 /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
473 /// * `Err(AppPathError)` - Failed to determine executable location
474 ///
475 /// # Examples
476 ///
477 /// ## Library with Complex Override Logic
478 ///
479 /// ```rust
480 /// use app_path::{AppPath, AppPathError};
481 /// use std::env;
482 ///
483 /// fn create_data_path() -> Result<AppPath, AppPathError> {
484 /// AppPath::try_with_override_fn("data", || {
485 /// // Complex override logic with multiple sources
486 /// env::var("DATA_DIR").ok()
487 /// .or_else(|| env::var("MYAPP_DATA_DIR").ok())
488 /// .or_else(|| {
489 /// if env::var("DEVELOPMENT").is_ok() {
490 /// Some("dev_data".to_string())
491 /// } else {
492 /// None
493 /// }
494 /// })
495 /// })
496 /// }
497 /// ```
498 ///
499 /// ## Error Propagation with Dynamic Logic
500 ///
501 /// ```rust
502 /// use app_path::{AppPath, AppPathError};
503 ///
504 /// fn setup_logging() -> Result<AppPath, AppPathError> {
505 /// AppPath::try_with_override_fn("logs/app.log", || {
506 /// // Dynamic override based on multiple conditions
507 /// if std::env::var("SYSLOG").is_ok() {
508 /// Some("/var/log/myapp.log".to_string())
509 /// } else if std::env::var("LOG_TO_TEMP").is_ok() {
510 /// Some(std::env::temp_dir().join("myapp.log").to_string_lossy().into_owned())
511 /// } else {
512 /// None
513 /// }
514 /// })
515 /// }
516 /// ```
517 ///
518 /// # Errors
519 ///
520 /// Returns [`AppPathError`] if the executable location cannot be determined:
521 /// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)
522 /// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
523 ///
524 /// See [`AppPath::try_new()`] for detailed error conditions. After the first successful call
525 /// to any AppPath method, this method will never return an error (uses cached result).
526 #[inline]
527 pub fn try_with_override_fn<F, P>(
528 default: impl AsRef<Path>,
529 override_fn: F,
530 ) -> Result<Self, AppPathError>
531 where
532 F: FnOnce() -> Option<P>,
533 P: AsRef<Path>,
534 {
535 match override_fn() {
536 Some(override_path) => Self::try_new(override_path),
537 None => Self::try_new(default),
538 }
539 }
540}