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! {
    RequiredParentTest {
        #[required]
        projects/ as ProjectsDir {
            [project: String]/ as ProjectDir {
                #[required]
                tasks/ as TasksDir {
                    #[required]
                    active/,
                    #[required]
                    backlog/ {
                        p1/ {},
                        p2/ {}
                    },
                    #[required]
                    completed/
                },
                #[optional]
                implementation/ {}
            }
        }
    }
}

#[test]
fn test_required_parent_created_during_setup() {
    let temp_dir = TempDir::new().unwrap();
    let root = RequiredParentTest::new(temp_dir.path()).unwrap();

    // Sync should create required parent directories
    let result = root.sync();
    assert!(result.is_ok(), "Sync should succeed: {:?}", result);

    // Check that required parent directories were created
    assert!(
        root.projects().as_path().exists(),
        "projects/ should be created"
    );

    // Dynamic children shouldn't be created yet (no default)
    let project_dir = root.projects().as_path().join("test-project");
    assert!(
        !project_dir.exists(),
        "Dynamic project dir should not be created without default"
    );
}

#[test]
fn test_required_parent_validated() {
    let temp_dir = TempDir::new().unwrap();
    let root = RequiredParentTest::new(temp_dir.path()).unwrap();

    // Without setup, validation should fail for required parents
    let report = root.validate();

    // V1/V2 API: ValidationReport with .errors field
    assert!(
        !report.errors.is_empty(),
        "Validation should fail without setup"
    );
    // Should have error for missing projects/ directory
    let has_projects_error = report.errors.iter().any(|e| {
        e.path == root.projects().as_path() && e.message.contains("Required path does not exist")
    });
    assert!(
        has_projects_error,
        "Should have error for missing projects/ directory"
    );
}

#[test]
fn test_required_nested_parent_validated() {
    let temp_dir = TempDir::new().unwrap();
    let root = RequiredParentTest::new(temp_dir.path()).unwrap();

    // Create projects/ and a project, but not tasks/
    std::fs::create_dir_all(root.projects().as_path()).unwrap();
    let project_path = root.projects().as_path().join("test-project");
    std::fs::create_dir_all(&project_path).unwrap();

    // Validation should fail because tasks/ is required within each project
    let report = root.validate();

    // V1/V2 API: ValidationReport with .errors field
    assert!(
        !report.errors.is_empty(),
        "Validation should fail without tasks/ directory"
    );

    // Should have error for missing tasks/ directory
    let tasks_path = project_path.join("tasks");
    let has_tasks_error = report
        .errors
        .iter()
        .any(|e| e.path == tasks_path && e.message.contains("Required path does not exist"));
    assert!(
        has_tasks_error,
        "Should have error for missing tasks/ directory: {:?}",
        report.errors
    );
}

#[test]
fn test_optional_parent_not_required() {
    let temp_dir = TempDir::new().unwrap();
    let root = RequiredParentTest::new(temp_dir.path()).unwrap();

    // Create required structure
    std::fs::create_dir_all(root.projects().as_path()).unwrap();
    let project_path = root.projects().as_path().join("test-project");
    std::fs::create_dir_all(&project_path).unwrap();
    std::fs::create_dir_all(project_path.join("tasks")).unwrap();
    std::fs::create_dir_all(project_path.join("tasks/active")).unwrap();
    std::fs::create_dir_all(project_path.join("tasks/backlog")).unwrap();
    std::fs::create_dir_all(project_path.join("tasks/completed")).unwrap();

    // Don't create implementation/ (it's optional)

    // Validation should pass even without implementation/
    let report = root.validate();

    // V1/V2 API: ValidationReport with .errors field
    assert!(
        report.errors.is_empty(),
        "Validation should pass without optional implementation/: {:?}",
        report.errors
    );
}

#[test]
fn test_required_parent_with_children_setup() {
    let temp_dir = TempDir::new().unwrap();
    let root = RequiredParentTest::new(temp_dir.path()).unwrap();

    // Sync should create projects/ even though it contains dynamic children
    root.sync().unwrap();

    assert!(
        root.projects().as_path().exists(),
        "projects/ should exist after setup"
    );

    // Now create a project manually
    let project_path = root.projects().as_path().join("test-project");
    std::fs::create_dir_all(&project_path).unwrap();

    // Access ProjectDir through parent and setup its tasks
    let project = root.projects().project("test-project");
    project.tasks().sync().unwrap();

    // All required subdirectories should be created
    assert!(project.tasks().as_path().exists(), "tasks/ should exist");
    assert!(
        project.tasks().active().as_path().exists(),
        "tasks/active/ should exist"
    );
    assert!(
        project.tasks().backlog().as_path().exists(),
        "tasks/backlog/ should exist"
    );
    assert!(
        project.tasks().completed().as_path().exists(),
        "tasks/completed/ should exist"
    );

    // Optional directory should not be created
    assert!(
        !project.implementation().as_path().exists(),
        "implementation/ should not be created (optional)"
    );
}