gglib_core/paths/
ensure.rs

1//! Directory creation and verification utilities.
2//!
3//! Provides strategies for creating directories and verifying they are writable.
4//! The `DirectoryCreationStrategy` enum does NOT include interactive/prompt variants;
5//! adapter code should handle user interaction separately.
6
7use std::fs::{self, OpenOptions};
8use std::io::Write;
9use std::path::Path;
10
11use super::error::PathError;
12
13/// Strategy for how to handle missing directories when ensuring they exist.
14///
15/// Note: This enum is intentionally non-interactive. CLI or GUI code that needs
16/// to prompt users should handle that logic separately and then pass `AutoCreate`
17/// or `Disallow` to `ensure_directory`.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum DirectoryCreationStrategy {
20    /// Create directories automatically if they are missing.
21    #[default]
22    AutoCreate,
23    /// Do not create directories; return an error if missing.
24    Disallow,
25}
26
27/// Ensure the provided directory exists and is writable according to the chosen strategy.
28///
29/// If the directory exists, verifies it's actually a directory and is writable.
30/// If the directory doesn't exist, behavior depends on `strategy`:
31/// - `AutoCreate`: Creates the directory (and parents)
32/// - `Disallow`: Returns an error
33pub fn ensure_directory(path: &Path, strategy: DirectoryCreationStrategy) -> Result<(), PathError> {
34    if path.exists() {
35        if !path.is_dir() {
36            return Err(PathError::NotADirectory(path.to_path_buf()));
37        }
38    } else {
39        match strategy {
40            DirectoryCreationStrategy::AutoCreate => {
41                fs::create_dir_all(path).map_err(|e| PathError::CreateFailed {
42                    path: path.to_path_buf(),
43                    reason: e.to_string(),
44                })?;
45            }
46            DirectoryCreationStrategy::Disallow => {
47                return Err(PathError::DirectoryNotFound(path.to_path_buf()));
48            }
49        }
50    }
51
52    verify_writable(path)?;
53    Ok(())
54}
55
56/// Verify a directory is writable by attempting to create a test file.
57pub fn verify_writable(path: &Path) -> Result<(), PathError> {
58    let test_file = path.join(".gglib_write_test");
59    let result = OpenOptions::new()
60        .create(true)
61        .write(true)
62        .truncate(true)
63        .open(&test_file);
64
65    match result {
66        Ok(mut file) => {
67            file.write_all(b"test")
68                .map_err(|e| PathError::NotWritable {
69                    path: path.to_path_buf(),
70                    reason: e.to_string(),
71                })?;
72            drop(file);
73            let _ = fs::remove_file(&test_file);
74            Ok(())
75        }
76        Err(err) => Err(PathError::NotWritable {
77            path: path.to_path_buf(),
78            reason: err.to_string(),
79        }),
80    }
81}