tree-type 0.4.5

Rust macros for creating type-safe filesystem tree structures
Documentation
#![allow(deprecated)]

use tempfile::TempDir;

use tree_type::tree_type;

tree_type! {
    TestProject {
        // Test 1: Directory with custom type and required attribute
        #[required]
        tasks/ as TasksDir,

        // Test 2: Directory with required attribute and children (children default to optional)
        #[required]
        backlog/ {
            p1/,
            p2/
        },

        // Test 3: File with custom type and required attribute
        #[required]
        config("config.toml") as ConfigFile
    }
}

#[test]
fn test_directory_with_custom_type_and_required() {
    let tempdir = TempDir::new().unwrap();
    let project = TestProject::new(tempdir.path()).unwrap();

    // Validate should fail - tasks/ is required but doesn't exist
    let report = project.validate();

    // V1/V2 API: ValidationReport with .errors field
    assert!(!report.is_ok(), "Should have validation errors");
    assert!(
        report
            .errors
            .iter()
            .any(|e| e.path == project.tasks().as_path()),
        "Should have error for missing tasks/"
    );

    // Create tasks/ and validate again
    std::fs::create_dir(project.tasks().as_path()).unwrap();
    let report = project.validate();

    // Debug: print all errors
    if !report.is_ok() {
        eprintln!("Validation errors after creating tasks/:");
        for err in &report.errors {
            eprintln!("  - {:?}: {}", err.path, err.message);
        }
    }

    // Check that tasks/ error is gone
    assert!(
        !report
            .errors
            .iter()
            .any(|e| e.path == project.tasks().as_path()),
        "Should NOT have error for tasks/ after creating it"
    );
}

#[test]
fn test_directory_with_children_parent_required_children_optional() {
    let tempdir = TempDir::new().unwrap();
    let project = TestProject::new(tempdir.path()).unwrap();

    // Validate should fail - backlog/ is required but doesn't exist
    let report = project.validate();

    assert!(!report.is_ok(), "Should have validation errors");
    assert!(
        report
            .errors
            .iter()
            .any(|e| e.path == project.backlog().as_path()),
        "Should have error for missing backlog/"
    );

    // Create backlog/ but not children
    std::fs::create_dir(project.backlog().as_path()).unwrap();

    // Should pass - backlog/ exists, children are optional
    let report = project.validate();
    // Note: Will still fail due to other required paths, but backlog/ error should be gone

    assert!(
        !report
            .errors
            .iter()
            .any(|e| e.path == project.backlog().as_path()),
        "Should NOT have error for backlog/ after creating it"
    );

    // p1/ and p2/ should not cause errors even though they don't exist
    assert!(
        !report
            .errors
            .iter()
            .any(|e| e.path == project.backlog().p1().as_path()),
        "Should NOT have error for optional p1/"
    );
    assert!(
        !report
            .errors
            .iter()
            .any(|e| e.path == project.backlog().p2().as_path()),
        "Should NOT have error for optional p2/"
    );
}

#[test]
fn test_file_with_custom_type_and_required() {
    let tempdir = TempDir::new().unwrap();
    let project = TestProject::new(tempdir.path()).unwrap();

    // Validate should fail - config is required but doesn't exist
    let report = project.validate();

    assert!(!report.is_ok(), "Should have validation errors");
    assert!(
        report
            .errors
            .iter()
            .any(|e| e.path == project.config().as_path()),
        "Should have error for missing config"
    );

    // Create config and validate again
    std::fs::write(project.config().as_path(), "# config").unwrap();
    let report = project.validate();
    // Note: Will still fail due to other required paths, but config error should be gone

    assert!(
        !report
            .errors
            .iter()
            .any(|e| e.path == project.config().as_path()),
        "Should NOT have error for config after creating it"
    );
}

#[test]
fn test_ensure_creates_only_required_paths() {
    let tempdir = TempDir::new().unwrap();
    let project = TestProject::new(tempdir.path()).unwrap();

    // Call ensure() - NEW BEHAVIOR: calls setup() first, then validate()
    // setup() creates ALL directories but NOT files without #[default]
    let result = project.ensure();
    assert!(result.is_ok());

    let report = result.unwrap();
    assert!(
        !report.is_ok(),
        "Validation should fail - config file missing"
    );

    // Create the missing config file
    std::fs::write(project.config().as_path(), "# config").unwrap();

    // Now ensure should pass
    let result = project.ensure();
    assert!(result.is_ok());

    let report = result.unwrap();
    assert!(
        report.is_ok(),
        "Validation should pass: {:?}",
        report.errors
    );

    // Verify ALL directories were created by setup() (including optional)
    assert!(project.tasks().exists(), "Required tasks/ created");
    assert!(project.backlog().exists(), "Required backlog/ created");
    assert!(project.backlog().p1().exists(), "Optional p1/ also created");
    assert!(project.backlog().p2().exists(), "Optional p2/ also created");
    assert!(
        project.config().exists(),
        "Required config created manually"
    );
}