plugin/handler-rust-toolchain/src/lib.rs
//! **The airgapped Rust-toolchain bundle package handler for holger.**
//!
//! A `rust-dev-rhel8-<ver>.znippy` holds a standalone rustup-dist toolchain, a
//! `cargo vendor` crate set, a `.cargo/config` and a pinned integrity manifest —
//! everything a disconnected box needs to `cargo build` offline. holger serves
//! those bundles; znippy builds and indexes them.
//!
//! # This crate owns the logic; its wasm sibling owns none
//!
//! `holger-handler-rust-toolchain-wasm` depends on this crate and exports only
//! the eight ABI symbols, and `tests/native_wasm_agree.rs` there asserts the two
//! surfaces answer identically field by field.
//!
//! # Sibling of the Skidbladnir handler, and shares its machinery
//!
//! The two are the same repository — one sealed `.znippy` per
//! `(name, version)` — differing only in the member classifier, the bundle-name
//! predicate and the handler name. Those three are this file; the store key,
//! coordinate parse, HTTP routes and `fetch`/`put`/`list` come from
//! [`holger_handler_bundle_core`], written once (LAW 5).
//!
//! The classifier is `znippy_plugin_rust_toolchain::member`, taken
//! `default-features = false` so the `ArchiveTypePlugin` impl and its
//! `znippy-common` dependency (arrow · io_uring · gatling) are compiled out and
//! the pure `path → MemberKind` function crosses to `wasm32-unknown-unknown`
//! unchanged.
use holger_handler_bundle_core::{BundleHandler, BundleKind, MemberDescription, BUNDLE_EXT};
pub use holger_handler_bundle_core::{coordinate_from_key, store_key, BUNDLE_CONTENT_TYPE};
pub use znippy_plugin_rust_toolchain::member::{
classify, component_of, is_rust_dev_bundle, MemberKind,
};
/// The handler's stable name.
pub const HANDLER_NAME: &str = "rust-toolchain";
/// The `holger_traits::ArtifactFormat` this handler reports.
///
/// Same finding as the Skidbladnir handler, and it lands the same way: holger has
/// no `RustToolchain` variant, and `ArtifactFormat::Rust` would be **wrong** —
/// that is the crates.io/registry format served by `holger-rust-znippy-repository`,
/// not a sealed toolchain bundle. A client asking for `Rust` expects a `.crate`
/// and a registry index. `znippy` is the truthful answer for a `.znippy` served
/// as the artifact itself; the manifest's `handler = "rust-toolchain"` carries the
/// real identity. Adding a variant is the owner's call — see the report.
pub const HANDLER_FORMAT: &str = "znippy";
/// The prefix every rust-dev bundle carries.
pub const BUNDLE_PREFIX: &str = "rust-dev-";
/// The rust-toolchain [`BundleKind`].
pub struct RustToolchain;
impl BundleKind for RustToolchain {
const HANDLER: &'static str = HANDLER_NAME;
const FORMAT: &'static str = HANDLER_FORMAT;
const WRITABLE: bool = true;
const SUPPLIER: &'static str = "nordisk";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn describe_member(path: &str) -> Option<MemberDescription> {
let kind = classify(path)?;
Some(MemberDescription {
kind: kind.as_str().to_string(),
// This classifier's components are DERIVED from the path (a crate
// name, a target triple), not a fixed set — which is why
// `MemberDescription.component` is owned rather than `&'static str`.
component: component_of(kind, path).unwrap_or_else(|| "-".to_string()),
// A rust-dev bundle has no encrypted-at-rest member: there are no
// secrets in a toolchain. Reporting `false` is the truth for this
// bundle kind, not an unimplemented field.
encrypted: false,
})
}
/// Unlike a Skidbladnir bundle, a rust-dev bundle carries a real marker in
/// its name, so `is_rust_dev_bundle` — znippy's own predicate, shared rather
/// than restated — is the filter.
fn is_bundle_name(file_name: &str) -> bool {
debug_assert!(BUNDLE_EXT == ".znippy");
is_rust_dev_bundle(file_name)
}
}
/// The handler instance holger loads.
pub type RustToolchainHandler = BundleHandler<RustToolchain>;
/// Classify one bundle-relative path — the shared ownership rule, re-exported.
pub fn describe_member(path: &str) -> Option<MemberDescription> {
RustToolchain::describe_member(path)
}
#[cfg(test)]
mod tests {
use super::*;
use holger_plugin_abi::PackageHandler;
#[test]
fn the_manifest_names_this_handler() {
let m = RustToolchainHandler::new().manifest();
assert_eq!(m.handler, "rust-toolchain");
assert_eq!(m.format, "znippy");
assert!(m.writable);
}
#[test]
fn the_shared_classifier_is_reached_with_a_derived_component() {
let d = describe_member("vendor/serde-1.0.203/src/lib.rs").expect("claimed");
assert_eq!(d.kind, "vendor-crate");
assert_eq!(
d.component, "serde-1.0.203",
"the crate dir was not derived from the path"
);
let std = describe_member("toolchain/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd.rlib")
.expect("claimed");
assert_eq!(std.kind, "rust-std");
assert_eq!(
std.component, "x86_64-unknown-linux-gnu",
"the target triple was not derived"
);
assert_eq!(
describe_member("README.md"),
None,
"a foreign path was claimed"
);
}
/// The name predicate is what keeps a Skidbladnir bundle out of a
/// rust-toolchain repository listing.
#[test]
fn only_rust_dev_bundles_are_claimed_by_name() {
assert!(RustToolchain::is_bundle_name(
"rust-dev-rhel8-1.97.1.znippy"
));
assert!(!RustToolchain::is_bundle_name("tillsynia-20260706.znippy"));
assert!(!RustToolchain::is_bundle_name("rust-dev-rhel8-1.97.1.tar"));
}
}