Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

plugin/host/src/convert.rs

//! **The one mapping between the ABI's wire types and holger's own.**
//!
//! `holger-plugin-abi` cannot use `holger_traits::{ArtifactId, ArtifactEntry}`
//! directly — `holger-traits` depends on `znippy-common`, which carries arrow,
//! io_uring and the gatling engine, none of which links for
//! `wasm32-unknown-unknown`. So the ABI mirrors those two types, and everything
//! that projects a handler into the router passes through the four functions
//! here.
//!
//! Both the linked-handler backend and the wasm backend use them, which is what
//! makes the two indistinguishable to `fast_routes.rs` by construction rather
//! than by inspection.

use holger_plugin_abi::{WireArtifactEntry, WireArtifactId};
use traits::{ArtifactEntry, ArtifactFormat, ArtifactId};

pub fn id_to_wire(id: &ArtifactId) -> WireArtifactId {
    WireArtifactId {
        namespace: id.namespace.clone(),
        name: id.name.clone(),
        version: id.version.clone(),
    }
}

pub fn id_from_wire(id: WireArtifactId) -> ArtifactId {
    ArtifactId {
        namespace: id.namespace,
        name: id.name,
        version: id.version,
    }
}

/// **A plugin's listing is unknown-provenance, and that is a statement about
/// the ABI, not about the plugin.**
///
/// `ArtifactEntry` carries `published_unix_ms` and `sha256`;
/// [`WireArtifactEntry`] does not, because adding them to it changes the
/// layout, and the layout is what [`holger_plugin_abi::ABI_VERSION`] guards —
/// the host refuses a module whose version does not match, so growing the ABI
/// v1 record would refuse every module already compiled against it. That is a
/// deliberate trade: an honest `None` from every plugin today beats a fleet of
/// modules that stop loading.
///
/// So both fields are `None` here. `None` is "this router does not know", never
/// "there is no digest" and never "the bytes are trusted". When the ABI is next
/// bumped for another reason, the two fields go into `WireArtifactEntry` and
/// this function stops being the place where the answer is lost.
pub fn entry_from_wire(e: WireArtifactEntry) -> ArtifactEntry {
    ArtifactEntry::unknown_provenance(id_from_wire(e.id), e.size_bytes, e.content_type)
}

/// Parse the manifest's `format` string into a real [`ArtifactFormat`].
///
/// Delegates to `ArtifactFormat::from_format_str`, holger's own parser, so a
/// plugin and a RON config spell a format the same way — there is no second
/// table here to drift.
///
/// An unrecognised spelling is an **error naming the string and every accepted
/// alternative**, never a fallback to `Raw`. A module that mis-declared its
/// format and was silently registered as raw would serve real bytes under a
/// format the router did not mean, which is worse than refusing to load.
pub fn format_from_wire(s: &str) -> Result<ArtifactFormat, String> {
    ArtifactFormat::from_format_str(s).ok_or_else(|| {
        let known: Vec<String> = traits::ALL_ARTIFACT_FORMATS
            .iter()
            .map(|f| format!("{f:?}").to_lowercase())
            .collect();
        format!(
            "plugin declares format {s:?}, which is not a holger ArtifactFormat. \
             Known formats: {}",
            known.join(", ")
        )
    })
}

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

    #[test]
    fn a_coordinate_survives_both_directions() {
        let id = ArtifactId {
            namespace: Some("com.example".into()),
            name: "mylib".into(),
            version: "1.2.3".into(),
        };
        assert_eq!(id_from_wire(id_to_wire(&id)), id);

        let bare = ArtifactId {
            namespace: None,
            name: "x".into(),
            version: "0".into(),
        };
        assert_eq!(
            id_from_wire(id_to_wire(&bare)).namespace,
            None,
            "None namespace became Some"
        );
    }

    #[test]
    fn a_known_format_parses_and_an_unknown_one_names_itself() {
        assert_eq!(format_from_wire("znippy").unwrap(), ArtifactFormat::Znippy);
        assert_eq!(format_from_wire("maven3").unwrap(), ArtifactFormat::Maven3);

        let err = format_from_wire("skidbladnir").unwrap_err();
        assert!(
            err.contains("skidbladnir"),
            "the error does not name the bad value: {err}"
        );
        assert!(
            err.contains("znippy"),
            "the error does not list the known formats: {err}"
        );
    }

    /// The load-bearing half: an unknown format must NOT silently become `Raw`.
    #[test]
    fn an_unknown_format_is_never_quietly_raw() {
        assert!(format_from_wire("not-a-format").is_err());
        assert!(format_from_wire("").is_err());
    }
}