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

nordisk/holger

plugin/handler-skidbladnir/src/lib.rs

//! **The Skidbladnir airgap-bundle package handler for holger.**
//!
//! A Skidbladnir bundle is the Tillsynia/Njord deploy payload: one sealed
//! `.znippy` holding a pg_dump, S3 document blobs, container images, infra
//! operators, a Talos ISO, declarative config and age-encrypted secrets.
//! holger's job is to *serve* those bundles; znippy's job is to build and index
//! them.
//!
//! # This crate owns the logic; its wasm sibling owns none
//!
//! `holger-handler-skidbladnir-wasm` depends on this crate and exports only the
//! eight ABI symbols. Everything a request is answered with comes from here and
//! from [`holger_handler_bundle_core`], so the two surfaces cannot diverge —
//! `tests/native_wasm_agree.rs` in the wasm crate asserts field-by-field that
//! they do not.
//!
//! # Where the logic actually comes from
//!
//! | piece | owner |
//! |---|---|
//! | store key · coordinate parse · HTTP routes · fetch/put/list | [`holger_handler_bundle_core`] — shared verbatim with the rust-toolchain handler, which is the same repository with a different classifier |
//! | member classification | `znippy_plugin_skidbladnir::member`, taken `default-features = false`. That leaves the pure `path → MemberKind` classifier and compiles **out** the `ArchiveTypePlugin` impl with its `znippy-common` dependency — arrow, io_uring and the gatling engine, none of which links on `wasm32-unknown-unknown`. |
//! | the bytes | nowhere here. Storage is a `BlobStore` the caller supplies. |
//!
//! That second row is the seam. It is the same one `znippy-plugin-maven` uses for
//! its wasm build (`host-decompressors` off ⇒ the threaded `ljar` fan-out is
//! compiled out, the single-threaded `linflate` both sides share remains).
//! Unlike maven there is no *threading* to compile out here — a bundle
//! coordinate scheme was never parallel — so what the feature removes is the
//! Arrow index writer, which this handler does not use either way. Measured
//! 2026-08-05: `cargo check -p znippy-plugin-skidbladnir --no-default-features
//! --target wasm32-unknown-unknown` builds the crate alone, with nothing else in
//! the graph.

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_skidbladnir::member::{classify, component_of, is_encrypted, MemberKind};

/// The handler's stable name — what a `.wasm` reports in its manifest and what
/// an operator writes in a repository config.
pub const HANDLER_NAME: &str = "skidbladnir";

/// The `holger_traits::ArtifactFormat` this handler reports, as its
/// serde-lowercase spelling.
///
/// **This is a finding, not a preference.** holger has no `Skidbladnir` variant:
/// `ALL_ARTIFACT_FORMATS` is Maven3, Pip, Rust, Go,
/// Nuget, Npm, Gem, Deb, Rpm, Helm, Docker, Conda, Composer, Znippy, Raw. Adding
/// one touches `ArtifactFormat`, `ArtifactFormat::home`,
/// `znippy_type_id` and every exhaustive match over the enum across sixteen
/// backend crates — the owner's call, so it is reported rather than smuggled in.
///
/// `znippy` is meanwhile the *honest* answer and not a placeholder: a Skidbladnir
/// bundle **is** a sealed `.znippy` served as the artifact itself, which is
/// exactly what `ArtifactFormat::Znippy` documents itself to mean. What is lost
/// is only telling a bundle from any other `.znippy` **by format alone** — the
/// handler's own identity survives, because the plugin manifest carries
/// `handler = "skidbladnir"` independently of `format`.
pub const HANDLER_FORMAT: &str = "znippy";

/// Skidbladnir's [`BundleKind`]: the three things that are not shared.
pub struct Skidbladnir;

impl BundleKind for Skidbladnir {
    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(),
            component: component_of(kind, path).unwrap_or("-").to_string(),
            encrypted: is_encrypted(kind, path),
        })
    }

    /// Any sealed `.znippy` in the store is a candidate. A Skidbladnir bundle
    /// carries no name prefix — `tillsynia-20260706.znippy` is a product name,
    /// not a marker — so narrowing further would silently hide bundles for a
    /// product nobody hardcoded. (Contrast the rust-toolchain handler, whose
    /// bundles genuinely do carry a `rust-dev-` prefix.)
    fn is_bundle_name(file_name: &str) -> bool {
        file_name.ends_with(BUNDLE_EXT)
    }
}

/// The handler instance holger loads.
pub type SkidbladnirHandler = BundleHandler<Skidbladnir>;

/// Classify one bundle-relative path — the shared ownership rule, re-exported so
/// a caller need not name the znippy crate.
pub fn describe_member(path: &str) -> Option<MemberDescription> {
    Skidbladnir::describe_member(path)
}

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

    #[test]
    fn the_manifest_names_this_handler() {
        let m = SkidbladnirHandler::new().manifest();
        assert_eq!(m.handler, "skidbladnir");
        assert_eq!(m.format, "znippy");
        assert!(m.writable);
    }

    /// The classifier really is znippy's, reached through the wasm-safe seam —
    /// asserted on values it would be impossible to produce by accident.
    #[test]
    fn the_shared_classifier_is_reached() {
        let d = describe_member("s3/2026/07/protokoll-123.pdf").expect("claimed");
        assert_eq!(d.kind, "s3-blob");
        assert_eq!(d.component, "minio");
        assert!(!d.encrypted);

        let s = describe_member("secrets/secrets.age").expect("claimed");
        assert_eq!(s.kind, "secret");
        assert!(s.encrypted, "an .age member was not flagged encrypted");

        assert_eq!(
            describe_member("README.md"),
            None,
            "a foreign path was claimed"
        );
    }

    #[test]
    fn bundle_level_members_report_a_dash_component() {
        let d = describe_member("lakespec.json").expect("claimed");
        assert_eq!(d.kind, "lakespec");
        assert_eq!(d.component, "-");
    }
}