xidl-parser 0.65.0

A IDL codegen.
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

fn unique_temp_dir(name: &str) -> PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_nanos();
    let path = std::env::temp_dir().join(format!(
        "xidl-parser-include-{name}-{}-{nanos}",
        std::process::id()
    ));
    fs::create_dir_all(&path).expect("create temp dir");
    path
}

fn write_file(path: &Path, source: &str) {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).expect("create parent dir");
    }
    fs::write(path, source).expect("write fixture");
}

fn parse_hir(path: &Path) -> xidl_parser::error::ParserResult<xidl_parser::hir::Specification> {
    let source = fs::read_to_string(path).expect("read fixture");
    let typed = xidl_parser::parser::parser_text_with_resolver(
        &source,
        Some(path.to_str().unwrap()),
        &mut xidl_parser::hir::FsIncludeResolver,
    )?;
    xidl_parser::hir::Specification::from_typed_ast_with_path(typed, path)
}

#[test]
fn test_hir_include_inserts_definitions_in_order() {
    let root = unique_temp_dir("ordered");
    let main = root.join("main.idl");
    let shared = root.join("shared.idl");

    write_file(
        &main,
        r#"
        struct Before {
            int32 value;
        };

        #include "shared.idl"

        struct After {
            int32 value;
        };
        "#,
    );
    write_file(
        &shared,
        r#"
        struct Shared {
            int32 value;
        };
        "#,
    );

    let hir = parse_hir(&main).unwrap();
    insta::assert_debug_snapshot!("hir__include_ordered", hir);
}

#[test]
fn test_hir_nested_include_preserves_depth_first_order() {
    let root = unique_temp_dir("nested");
    let main = root.join("main.idl");
    let middle = root.join("parts/middle.idl");
    let leaf = root.join("parts/leaf.idl");

    write_file(
        &main,
        r#"
        struct Start {
            int32 value;
        };

        #include "parts/middle.idl"

        struct End {
            int32 value;
        };
        "#,
    );
    write_file(
        &middle,
        r#"
        struct Middle {
            int32 value;
        };

        #include "leaf.idl"
        "#,
    );
    write_file(
        &leaf,
        r#"
        struct Leaf {
            int32 value;
        };
        "#,
    );

    let hir = parse_hir(&main).unwrap();
    insta::assert_debug_snapshot!("hir__include_nested", hir);
}

#[test]
fn test_hir_include_missing_file_errors() {
    let root = unique_temp_dir("missing");
    let main = root.join("main.idl");

    write_file(&main, "#include \"missing.idl\"\n");

    let err = parse_hir(&main).expect_err("missing include should fail");
    assert!(err.to_string().contains("does not exist"));
    assert!(err.to_string().contains("missing.idl"));
}

#[test]
fn test_hir_include_invalid_ast_errors() {
    let root = unique_temp_dir("invalid");
    let main = root.join("main.idl");
    let invalid = root.join("invalid.idl");

    write_file(&main, "#include \"invalid.idl\"\n");
    write_file(&invalid, "struct Broken { int32 value \n");

    let err = parse_hir(&main).expect_err("invalid include should fail");
    assert!(err.to_string().contains("failed to parse include"));
    assert!(err.to_string().contains("invalid.idl"));
}

#[test]
fn test_hir_include_cycle_errors() {
    let root = unique_temp_dir("cycle");
    let a = root.join("a.idl");
    let b = root.join("b.idl");

    write_file(&a, "#include \"b.idl\"\n");
    write_file(&b, "#include \"a.idl\"\n");

    let err = parse_hir(&a).expect_err("cyclic include should fail");
    assert!(err.to_string().contains("cyclic include detected"));
    assert!(err.to_string().contains("a.idl"));
    assert!(err.to_string().contains("b.idl"));
}