Skip to main content

vanta_provider/
lib.rs

1//! `vanta-provider` — the declarative provider model.
2//!
3//! A provider describes how to turn a `version` + `Platform` into a concrete
4//! [`Artifact`]: a URL template (with `{version}`/`{os}`/`{arch}`/`{ext}`
5//! placeholders), the archive kind, the bin paths, and per-token name maps that
6//! translate Vanta's canonical platform tokens into the upstream's spelling
7//! (e.g. `macos`→`darwin`, `aarch64`→`arm64`). See `docs/07-providers.md` and
8//! `docs/22-provider-sdk.md`.
9//!
10//! This is the declarative path (no code). Providers that need custom logic use a
11//! sandboxed WASM hook ([`Sandbox`], see `docs/22-provider-sdk.md`).
12#![forbid(unsafe_code)]
13
14pub mod wasm;
15pub use wasm::Sandbox;
16
17use serde::{Deserialize, Serialize};
18use std::collections::BTreeMap;
19use vanta_core::{Artifact, Checksum, Platform};
20
21/// A declarative provider definition (one tool).
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct ProviderDef {
24    /// Provider id, e.g. `"official/node"`.
25    pub id: String,
26    /// The tool this provider serves.
27    pub tool: String,
28    /// URL template with `{version}`/`{os}`/`{arch}`/`{ext}` placeholders.
29    pub url_template: String,
30    /// Archive kind: `tar.gz` / `tgz` / `zip` / `raw`.
31    pub archive: String,
32    /// Components to strip when materializing (recorded for the store layout).
33    #[serde(default)]
34    pub strip: u32,
35    /// Executables to expose (paths relative to the laid-out tree).
36    #[serde(default)]
37    pub bin: Vec<String>,
38    /// Map a canonical OS token to the upstream spelling (`macos` → `darwin`).
39    #[serde(default)]
40    pub os_map: BTreeMap<String, String>,
41    /// Map a canonical arch token to the upstream spelling (`aarch64` → `arm64`).
42    #[serde(default)]
43    pub arch_map: BTreeMap<String, String>,
44}
45
46impl ProviderDef {
47    /// Render the artifact for `version` on `platform`, attaching `checksum`.
48    /// `size` is optional metadata carried into the lock.
49    pub fn render_artifact(
50        &self,
51        version: &str,
52        platform: &Platform,
53        checksum: Checksum,
54        size: Option<u64>,
55    ) -> Artifact {
56        let os = self.map_os(platform);
57        let arch = self.map_arch(platform);
58        let url = self
59            .url_template
60            .replace("{version}", version)
61            .replace("{os}", &os)
62            .replace("{arch}", &arch)
63            .replace("{ext}", ext_for(&self.archive));
64        Artifact {
65            url,
66            mirrors: Vec::new(),
67            archive: self.archive.clone(),
68            size,
69            checksum,
70            signature: None,
71            signature_key: None,
72            bin: self.bin.clone(),
73            strip: self.strip,
74            store_key: None,
75        }
76    }
77
78    fn map_os(&self, platform: &Platform) -> String {
79        let key = platform.os.as_str();
80        self.os_map
81            .get(key)
82            .cloned()
83            .unwrap_or_else(|| key.to_string())
84    }
85
86    fn map_arch(&self, platform: &Platform) -> String {
87        let key = platform.arch.as_str();
88        self.arch_map
89            .get(key)
90            .cloned()
91            .unwrap_or_else(|| key.to_string())
92    }
93}
94
95/// The file extension implied by an archive kind (for `{ext}` substitution).
96pub fn ext_for(archive: &str) -> &'static str {
97    match archive {
98        "tar.gz" | "tgz" => "tar.gz",
99        "tar.xz" => "tar.xz",
100        "zip" => "zip",
101        _ => "",
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use vanta_core::{Arch, Libc, Os};
109
110    fn node_provider() -> ProviderDef {
111        let mut os_map = BTreeMap::new();
112        os_map.insert("macos".into(), "darwin".into());
113        let mut arch_map = BTreeMap::new();
114        arch_map.insert("aarch64".into(), "arm64".into());
115        ProviderDef {
116            id: "official/node".into(),
117            tool: "node".into(),
118            url_template: "https://nodejs.org/dist/v{version}/node-v{version}-{os}-{arch}.{ext}"
119                .into(),
120            archive: "tar.gz".into(),
121            strip: 1,
122            bin: vec!["bin/node".into()],
123            os_map,
124            arch_map,
125        }
126    }
127
128    #[test]
129    fn renders_url_with_token_maps() {
130        let p = node_provider();
131        let plat = Platform {
132            os: Os::Macos,
133            arch: Arch::Aarch64,
134            libc: Libc::None,
135        };
136        let art = p.render_artifact(
137            "24.6.0",
138            &plat,
139            Checksum {
140                algo: "sha256".into(),
141                value: "abc".into(),
142            },
143            Some(100),
144        );
145        assert_eq!(
146            art.url,
147            "https://nodejs.org/dist/v24.6.0/node-v24.6.0-darwin-arm64.tar.gz"
148        );
149        assert_eq!(art.archive, "tar.gz");
150        assert_eq!(art.bin, vec!["bin/node".to_string()]);
151        assert_eq!(art.checksum.value, "abc");
152    }
153}