oxideav-core 0.1.36

Core types and registries for oxideav — timestamps, packets, frames, codec/container/source/filter registries (pure Rust, no C deps)
Documentation
//! Sibling registration entry-point macro.
//!
//! Each sibling crate that ships a codec / container / filter / source
//! invokes [`crate::register!`] at module scope to declare its
//! `register(ctx)` function as the canonical entry point. The
//! [`oxideav-meta`](https://crates.io/crates/oxideav-meta) crate's
//! build script enumerates the enabled sibling deps in its Cargo.toml
//! and emits a `register_all(ctx)` body that calls the macro-generated
//! entry point of each.
//!
//! The macro is the dispatch contract between sibling and meta. Today
//! it expands to a thin `pub fn __oxideav_entry(ctx)` wrapper around
//! the user-supplied register fn. The macro body is the only place
//! that needs to change if the dispatch transport switches in the
//! future (e.g. add a metadata arg, defer init, async-init, audit
//! hook) — sibling call sites stay untouched.
//!
//! # Dispatch contract
//!
//! The contract has three fixed parts; the macro body is free to
//! change as long as they hold:
//!
//! 1. **Name and signature.** The expansion is exactly
//!    `pub fn __oxideav_entry(ctx: &mut RuntimeContext)`. Callers
//!    (today: `oxideav-meta`'s generated `register_all`) invoke it by
//!    that name and nothing else.
//! 2. **Path.** Callers resolve it at the sibling's **crate root** —
//!    `oxideav_<crate>::__oxideav_entry`. The macro defines the fn in
//!    the module it is invoked from, so a sibling that invokes it
//!    inside a submodule (`pub mod registry { … }`) **must re-export
//!    it from the root**:
//!
//!    ```ignore
//!    // lib.rs
//!    pub mod registry;
//!    #[cfg(feature = "registry")]
//!    pub use registry::__oxideav_entry;
//!    ```
//!
//!    Without the re-export the entry point only resolves at
//!    `oxideav_<crate>::registry::__oxideav_entry`, and the meta crate
//!    has to carry a per-sibling path override — a contract leak, not
//!    a supported layout.
//! 3. **Effect.** One call installs the sibling's claims into `ctx`
//!    exactly as the sibling's own `register(ctx)` would; calling it
//!    twice on one context is the same as calling `register` twice
//!    (codec ids gain a second implementation entry, container names
//!    and schemes are replaced in place — see the registry docs).
//!    Callers invoke it at most once per context.
//!
//! The wrapper is `#[doc(hidden)]`: it is dispatch plumbing, not a
//! sibling's public API, and semver tooling ignores it accordingly.
//!
//! # Standalone opt-out
//!
//! Each sibling's `register!()` call lives behind that crate's
//! default-on `registry` cargo feature. Consumers that want the
//! standalone (no-`oxideav-core`-dep) build path turn the feature off
//! and the macro call disappears.

/// Declare the canonical sibling entry point.
///
/// Place at module scope inside the sibling crate, gated behind the
/// crate's `registry` cargo feature so the standalone build path
/// stays decoupled from `oxideav-core`:
///
/// ```ignore
/// pub fn register(ctx: &mut oxideav_core::RuntimeContext) {
///     /* install factories */
/// }
///
/// #[cfg(feature = "registry")]
/// oxideav_core::register!("aac", register);
/// ```
///
/// The macro expands to a `pub fn __oxideav_entry(ctx)` wrapper that
/// invokes the supplied function. `oxideav-meta`'s `register_all`
/// calls `oxideav_<crate>::__oxideav_entry(ctx)` for each enabled
/// sibling dep — the fn must therefore be reachable at the crate
/// root; when the macro is invoked in a submodule, add
/// `pub use <module>::__oxideav_entry;` to `lib.rs` (see the module
/// docs, "Dispatch contract").
///
/// The display-name argument (first literal) is reserved for future
/// dispatch-transport changes; it is currently ignored by the
/// expansion but kept in the macro signature for forward
/// compatibility.
#[macro_export]
macro_rules! register {
    ($name:literal, $func:path) => {
        /// Canonical entry point invoked by `oxideav_meta::register_all`.
        ///
        /// Generated by the [`oxideav_core::register!`] macro; do not
        /// invoke directly.
        #[doc(hidden)]
        pub fn __oxideav_entry(ctx: &mut $crate::RuntimeContext) {
            let _ = $name;
            $func(ctx);
        }
    };
}

#[cfg(test)]
mod tests {
    //! The dispatch contract, exercised from inside this crate: the
    //! macro is invoked in a nested module (the layout that bit
    //! `oxideav-meta`), re-exported one level up, and the entry point
    //! is called through the re-exported path on a fresh context.

    use crate::{CodecId, CodecInfo, RuntimeContext};

    mod sibling {
        //! Stand-in for a sibling crate that keeps its registration in
        //! a `registry` submodule.
        pub mod registry {
            use crate::{CodecId, CodecInfo, RuntimeContext};

            pub fn register(ctx: &mut RuntimeContext) {
                ctx.codecs
                    .register(CodecInfo::new(CodecId::new("synthetic-entry")));
                ctx.containers
                    .register_extension("synth", "synthetic-container");
            }

            crate::register!("synthetic", register);
        }

        // The contract's root re-export.
        pub use registry::__oxideav_entry;
    }

    #[test]
    fn entry_point_resolves_at_root_and_installs_claims() {
        let mut ctx = RuntimeContext::new();
        // Called exactly the way `register_all` does: by root path.
        sibling::__oxideav_entry(&mut ctx);
        assert_eq!(
            ctx.containers.container_for_extension("synth"),
            Some("synthetic-container")
        );
        // A tag-less, factory-less registration still lands one
        // registration record (visible via the tag surface being empty
        // but the id being known to the resolver's bookkeeping).
        assert!(ctx.codecs.all_tag_registrations().next().is_none());
        // Same fn object reachable at the nested path — the re-export
        // aliases, it does not copy.
        let a: fn(&mut RuntimeContext) = sibling::__oxideav_entry;
        let b: fn(&mut RuntimeContext) = sibling::registry::__oxideav_entry;
        assert_eq!(a as usize, b as usize);
    }

    #[test]
    fn calling_entry_twice_equals_registering_twice() {
        fn dec(_p: &crate::CodecParameters) -> crate::Result<Box<dyn crate::Decoder>> {
            Err(crate::Error::unsupported("dummy"))
        }
        pub fn register(ctx: &mut RuntimeContext) {
            ctx.codecs
                .register(CodecInfo::new(CodecId::new("twice")).decoder(dec));
        }
        crate::register!("twice", register);

        let mut ctx = RuntimeContext::new();
        __oxideav_entry(&mut ctx);
        __oxideav_entry(&mut ctx);
        // Codec ids accumulate implementations (multi-impl contract);
        // nothing is deduplicated by the wrapper.
        assert_eq!(ctx.codecs.implementations(&CodecId::new("twice")).len(), 2);
    }
}