batpak-macros-support 0.7.0

Shared support types for batpak macros. Not intended for direct use.
Documentation
//! Runtime support types for `#[derive(EventPayload)]`.
//!
//! This crate is an implementation detail of the batpak macro surface.
//! Its public items are re-exported from `batpak::__private`; do not
//! depend on it directly.

/// Re-exported so generated code can reference `batpak::__private::inventory`
/// without requiring users to add `inventory` to their own Cargo.toml.
pub extern crate inventory;

/// Registration item emitted once per `#[derive(EventPayload)]` type.
///
/// `inventory::submit!` calls that produce these items are emitted
/// unconditionally by generated code. That keeps dependency-crate payloads
/// visible to a downstream binary's explicit registry validator instead of
/// losing them behind each dependency crate's own `cfg(test)` boundary.
pub struct EventPayloadRegistration {
    /// Packed (category << 12 | type_id) as stored in EventKind.
    pub kind_bits: u16,
    /// `std::any::type_name::<T>()` for the derived type.
    pub type_name: &'static str,
}

inventory::collect!(EventPayloadRegistration);

/// One duplicate `EventKind` registration discovered in the current binary.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EventKindCollision {
    /// Packed `(category << 12) | type_id` representation.
    pub kind_bits: u16,
    /// First registered type name for `kind_bits`.
    pub first_type_name: &'static str,
    /// Second registered type name for `kind_bits`.
    pub second_type_name: &'static str,
}

/// Return all `EventPayload` kind collisions visible in the current binary.
pub fn find_kind_collisions() -> Vec<EventKindCollision> {
    let mut seen: std::collections::HashMap<u16, &'static str> = std::collections::HashMap::new();
    let mut collisions = Vec::new();
    for item in inventory::iter::<EventPayloadRegistration>() {
        if let Some(prior) = seen.insert(item.kind_bits, item.type_name) {
            collisions.push(EventKindCollision {
                kind_bits: item.kind_bits,
                first_type_name: prior,
                second_type_name: item.type_name,
            });
        }
    }
    collisions
}

/// Panic if the current binary has duplicate `EventPayload` kind registrations.
pub fn assert_no_kind_collisions() {
    if let Some(collision) = find_kind_collisions().into_iter().next() {
        panic!(
            "batpak EventKind collision detected.\n\
             Types `{prior}` and `{ty}` share the same (category, type_id) \
             pair (kind_bits = 0x{bits:04X}).\n\
             Each EventPayload type must have a unique kind within a binary.",
            prior = collision.first_type_name,
            ty = collision.second_type_name,
            bits = collision.kind_bits,
        );
    }
}

/// Scan all `EventPayload` registrations in the current binary for kind collisions.
///
/// Called by the `#[test]` function generated by `#[derive(EventPayload)]`.
/// Detection scope is per-binary: two separate binaries can reuse the same
/// `(category, type_id)` without triggering a panic here.
///
/// Safe to call in non-test binaries. Prefer [`find_kind_collisions`] for a
/// structured result.
pub fn scan_for_kind_collisions() {
    assert_no_kind_collisions();
}

// ─── Validation ───────────────────────────────────────────────────────────────

/// Minimum valid category value (inclusive).
pub const CATEGORY_MIN: u8 = 1;
/// Maximum valid category value (inclusive, 4 bits).
pub const CATEGORY_MAX: u8 = 15;
/// Maximum valid type_id value (inclusive, 12 bits).
pub const TYPE_ID_MAX: u16 = 0x0FFF;

/// Validate an EventKind category value.
///
/// Valid range: 0x1–0xF, excluding 0xD (reserved for effects).
pub fn validate_category(cat: u8) -> Result<(), &'static str> {
    match cat {
        0x0 => Err("category 0x0 is reserved for system events"),
        0xD => Err("category 0xD is reserved for effect events"),
        1..=15 => Ok(()),
        _ => Err("category must fit in 4 bits (0x1–0xF, excluding 0x0 and 0xD)"),
    }
}

/// Validate an EventKind type_id value.
///
/// Valid range: 0x000–0xFFF (12 bits).
pub fn validate_type_id(tid: u16) -> Result<(), &'static str> {
    if tid > TYPE_ID_MAX {
        Err("type_id must fit in 12 bits (0x000–0xFFF)")
    } else {
        Ok(())
    }
}