Skip to main content

vanta_sdk/
lib.rs

1//! `vanta-sdk` — the provider-author SDK (`docs/22-provider-sdk.md`).
2//!
3//! Types and helpers a provider author uses to describe how to discover and
4//! resolve a tool. For declarative providers this mirrors the registry manifest;
5//! for WASM hooks these are the values a guest returns to the host. Kept
6//! dependency-light so it can target `wasm32` guests.
7#![forbid(unsafe_code)]
8
9/// What a `resolve(version, os, arch)` hook returns for one platform.
10#[derive(Debug, Clone, PartialEq)]
11pub struct ArtifactDesc {
12    pub url: String,
13    /// `tar.gz` / `tgz` / `zip` / `raw`.
14    pub archive: String,
15    pub sha256: String,
16    /// Executables to expose (relative to the laid-out tree).
17    pub bin: Vec<String>,
18    /// Leading path components to strip on extraction.
19    pub strip: u32,
20}
21
22/// The contract a provider author implements (declaratively or via a WASM hook).
23pub trait ToolProvider {
24    /// Available version strings (host applies ordering).
25    fn list_versions(&self) -> Vec<String>;
26    /// The artifact for a version on a platform, or `None` if unsupported.
27    fn resolve(&self, version: &str, os: &str, arch: &str) -> Option<ArtifactDesc>;
28}
29
30/// Substitute `{version}`/`{os}`/`{arch}`/`{ext}` in a URL template — the helper
31/// most declarative providers need.
32pub fn render_url(template: &str, version: &str, os: &str, arch: &str, ext: &str) -> String {
33    template
34        .replace("{version}", version)
35        .replace("{os}", os)
36        .replace("{arch}", arch)
37        .replace("{ext}", ext)
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    struct Demo;
45    impl ToolProvider for Demo {
46        fn list_versions(&self) -> Vec<String> {
47            vec!["1.0.0".into(), "1.1.0".into()]
48        }
49        fn resolve(&self, version: &str, os: &str, arch: &str) -> Option<ArtifactDesc> {
50            Some(ArtifactDesc {
51                url: render_url(
52                    "https://x.test/demo-{version}-{os}-{arch}.{ext}",
53                    version,
54                    os,
55                    arch,
56                    "tar.gz",
57                ),
58                archive: "tar.gz".into(),
59                sha256: "abc".into(),
60                bin: vec!["bin/demo".into()],
61                strip: 1,
62            })
63        }
64    }
65
66    #[test]
67    fn render_substitutes_all_placeholders() {
68        assert_eq!(
69            render_url(
70                "a/{version}/{os}-{arch}.{ext}",
71                "1.2.3",
72                "macos",
73                "arm64",
74                "tar.gz"
75            ),
76            "a/1.2.3/macos-arm64.tar.gz"
77        );
78    }
79
80    #[test]
81    fn author_trait_roundtrip() {
82        let p = Demo;
83        assert_eq!(p.list_versions().len(), 2);
84        let a = p.resolve("1.1.0", "linux", "x64").unwrap();
85        assert_eq!(a.url, "https://x.test/demo-1.1.0-linux-x64.tar.gz");
86        assert_eq!(a.strip, 1);
87    }
88}