alef 0.62.0

Opinionated polyglot binding generator for Rust libraries
Documentation
//! Shared test assertion for the opaque-handle ABI stamp.
//!
//! Lives under the FFI backend because that backend owns the authoritative
//! handle representation; the binding backends that hand-copy it (C#, Java,
//! Dart `gen_ffi`, and — pending — Zig) assert against the same contract, so a
//! single definition keeps them from drifting apart.

use crate::core::hash::{
    HANDLE_ABI_STAMP_KEY, compute_file_hash, extract_hash, extract_stamp, inject_hash_line, strip_hash_line,
};
use crate::core::template_versions::abi::HANDLE_ABI_VERSION;

/// Assert that `content`, as emitted by a backend, carries the handle-ABI stamp
/// **and** that the stamp is part of the body the pipeline's post-generation
/// hash pass hashes.
///
/// Replays `cli::pipeline::generate::write::finalize_hashes` (strip → hash →
/// inject) and then re-derives the hash the way `alef verify` does. A stamp
/// injected *after* `inject_hash_line` would leave the embedded hash covering
/// unstamped content, so the final equality here is what makes the ordering
/// constraint testable rather than merely documented. ~keep
pub(crate) fn assert_stamped_before_hashing(content: &str, what: &str) {
    assert_eq!(
        extract_stamp(content, HANDLE_ABI_STAMP_KEY).as_deref(),
        Some(HANDLE_ABI_VERSION),
        "{what}: emitted content must carry `alef:{HANDLE_ABI_STAMP_KEY}:{HANDLE_ABI_VERSION}`"
    );

    let body = strip_hash_line(content);
    assert!(
        body.contains(&format!("alef:{HANDLE_ABI_STAMP_KEY}:{HANDLE_ABI_VERSION}")),
        "{what}: the stamp must survive hash-line stripping, i.e. be part of the hashed body"
    );

    let inputs_hash = "0".repeat(64);
    let finalized = inject_hash_line(&body, &compute_file_hash(&inputs_hash, &body));
    assert_eq!(
        extract_stamp(&finalized, HANDLE_ABI_STAMP_KEY).as_deref(),
        Some(HANDLE_ABI_VERSION),
        "{what}: the stamp must stay extractable once the hash line is injected above it"
    );
    assert_eq!(
        extract_hash(&finalized),
        Some(compute_file_hash(&inputs_hash, &strip_hash_line(&finalized))),
        "{what}: the embedded hash must re-verify over the stamped content"
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    fn headered(body: &str) -> String {
        format!("// This file is auto-generated by alef. DO NOT EDIT.\n{body}")
    }

    /// Positive control: the assertion accepts correctly-ordered output.
    #[test]
    fn accepts_content_stamped_before_hashing() {
        let stamped = inject_stamped(&headered("fn main() {}\n"));
        assert_stamped_before_hashing(&stamped, "fixture");
    }

    /// Negative control: without the stamp the assertion must fail, so a
    /// backend that stops emitting it cannot pass vacuously.
    #[test]
    #[should_panic(expected = "must carry")]
    fn rejects_unstamped_content() {
        assert_stamped_before_hashing(&headered("fn main() {}\n"), "fixture");
    }

    /// Why the ordering constraint exists: `inject_stamp_line` places the stamp
    /// immediately after the header marker, which is the slot `extract_hash`
    /// requires the `alef:hash:` line to occupy. Stamping after hashing
    /// therefore displaces the hash line and `alef verify` stops seeing a hash
    /// on the file at all.
    #[test]
    fn stamping_after_the_hash_line_hides_the_hash_from_verify() {
        let unstamped = headered("fn main() {}\n");
        let inputs_hash = "0".repeat(64);
        let hashed = inject_hash_line(&unstamped, &compute_file_hash(&inputs_hash, &unstamped));
        assert!(extract_hash(&hashed).is_some(), "control: hashing alone is readable");

        assert_eq!(
            extract_hash(&inject_stamped(&hashed)),
            None,
            "a stamp injected after the hash line pushes it out of the marker-adjacent slot"
        );
    }

    fn inject_stamped(content: &str) -> String {
        crate::core::hash::inject_stamp_line(content, HANDLE_ABI_STAMP_KEY, HANDLE_ABI_VERSION)
    }
}