znippy-common 0.9.14

Core logic and data structures for Znippy, a parallel chunked compression system.
Documentation
//! Two guards over the plugin source split, both from *outside* the crate.
//!
//! 1. `no_plugin_source_sits_loose` — every handler source file lives in
//!    `src/plugins/native/` or `src/plugins/wasm/`, never loose beside
//!    `plugins/mod.rs`. That is the rule the directories exist to enforce; a
//!    new `foo_native.rs` dropped next to `mod.rs` fails here by name.
//!
//! 2. `published_handler_paths_still_resolve` — the paths out-of-workspace
//!    consumers use (`znippy_common::plugins::<x>_native::<X>Plugin`, and
//!    `plugins::skeletons`) still resolve, and still answer with the on-disk
//!    `type_id` discriminants. This file is an integration test, so it links
//!    `znippy_common` as an external crate exactly as `holger/traits` does —
//!    a `use` inside the crate would not prove the same thing.
//!
//! Neither test can skip. The first fails if the directory is missing instead
//! of quietly finding nothing; the second is a compile error before it is an
//! assertion.

use std::path::{Path, PathBuf};

use znippy_common::plugin::ArchiveTypePlugin;
use znippy_common::plugins::cargo_native::CargoPlugin;
use znippy_common::plugins::conda_native::CondaPlugin;
use znippy_common::plugins::deb_native::DebPlugin;
use znippy_common::plugins::gem_native::GemPlugin;
use znippy_common::plugins::npm_native::NpmPlugin;
use znippy_common::plugins::rpm_native::RpmPlugin;

fn plugins_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/plugins")
}

/// Every `.rs` directly inside `dir`, by file name.
fn loose_rs(dir: &Path) -> Vec<String> {
    let entries = std::fs::read_dir(dir)
        .unwrap_or_else(|e| panic!("plugin source dir {} is unreadable: {e}", dir.display()));
    let mut names: Vec<String> = entries
        .map(|e| e.expect("read_dir entry").path())
        .filter(|p| p.is_file() && p.extension().is_some_and(|x| x == "rs"))
        .map(|p| p.file_name().expect("file has a name").to_string_lossy().into_owned())
        .collect();
    names.sort();
    names
}

#[test]
fn no_plugin_source_sits_loose() {
    let dir = plugins_dir();

    // The split itself must exist — an empty/missing directory would otherwise
    // make "nothing is loose" trivially true.
    for sub in ["native", "wasm"] {
        let p = dir.join(sub);
        assert!(
            p.is_dir(),
            "the plugin split is gone: {} is not a directory",
            p.display()
        );
    }
    assert!(
        !loose_rs(&dir.join("native")).is_empty(),
        "src/plugins/native/ holds no .rs at all — the guard would pass over an empty tree"
    );
    assert!(
        !loose_rs(&dir.join("wasm")).is_empty(),
        "src/plugins/wasm/ holds no .rs at all — the guard would pass over an empty tree"
    );

    let loose = loose_rs(&dir);
    assert_eq!(
        loose,
        vec!["mod.rs".to_string()],
        "plugin source must live in src/plugins/native/ or src/plugins/wasm/, never loose \
         beside mod.rs — these are loose: {loose:?}"
    );
}

#[test]
fn published_handler_paths_still_resolve() {
    // The on-disk pkg_type discriminants, per .nornir/plugins-design.md §f3.
    // Asserting the VALUE, not merely that the path compiles: a re-export
    // pointing at the wrong module would still compile.
    let ids: Vec<(&str, i8)> = vec![
        ("cargo", CargoPlugin::new().type_id()),
        ("npm", NpmPlugin.type_id()),
        ("rpm", RpmPlugin.type_id()),
        ("deb", DebPlugin.type_id()),
        ("gem", GemPlugin.type_id()),
        ("conda", CondaPlugin.type_id()),
    ];
    assert_eq!(
        ids,
        vec![("cargo", 1), ("npm", 6), ("rpm", 8), ("deb", 9), ("gem", 11), ("conda", 14)],
        "a published handler path resolved to the wrong handler after the source move"
    );

    // `plugins::skeletons` is a published path too — holger/traits reaches both
    // the stub structs (`skeletons::GoPlugin`) and the register fn through it.
    use znippy_common::plugins::skeletons;
    assert_eq!(
        (skeletons::GoPlugin.type_id(), skeletons::NugetPlugin.type_id()),
        (4, 5),
        "a skeleton stub resolved to the wrong handler after the source move"
    );
    assert!(
        !skeletons::skeleton_handlers().is_empty(),
        "znippy_common::plugins::skeletons::skeleton_handlers() returned no stubs"
    );
}