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());
}
}