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}