Skip to main content

husako_helm/
lib.rs

1mod artifacthub;
2mod file;
3mod git;
4mod registry;
5
6use std::collections::HashMap;
7use std::path::Path;
8
9use husako_config::ChartSource;
10
11#[derive(Debug, thiserror::Error)]
12pub enum HelmError {
13    #[error("chart I/O error: {0}")]
14    Io(String),
15    #[error("invalid values schema: {0}")]
16    InvalidSchema(String),
17    #[error("chart not found: {0}")]
18    NotFound(String),
19}
20
21/// Simple hash for cache directory naming (djb2).
22pub(crate) fn cache_hash(s: &str) -> String {
23    let mut hash: u64 = 5381;
24    for byte in s.bytes() {
25        hash = hash.wrapping_mul(33).wrapping_add(byte as u64);
26    }
27    format!("{hash:016x}")
28}
29
30/// Resolve a single chart source to its `values.schema.json` content.
31pub fn resolve(
32    name: &str,
33    source: &ChartSource,
34    project_root: &Path,
35    cache_dir: &Path,
36) -> Result<serde_json::Value, HelmError> {
37    match source {
38        ChartSource::File { path } => file::resolve(name, path, project_root),
39        ChartSource::Registry {
40            repo,
41            chart,
42            version,
43        } => registry::resolve(name, repo, chart, version, cache_dir),
44        ChartSource::ArtifactHub { package, version } => {
45            artifacthub::resolve(name, package, version, cache_dir)
46        }
47        ChartSource::Git { repo, tag, path } => git::resolve(name, repo, tag, path, cache_dir),
48    }
49}
50
51/// Resolve all chart sources from config.
52///
53/// Returns `chart_name → JSON Schema value`.
54pub fn resolve_all(
55    charts: &HashMap<String, ChartSource>,
56    project_root: &Path,
57    cache_dir: &Path,
58) -> Result<HashMap<String, serde_json::Value>, HelmError> {
59    let mut result = HashMap::new();
60    for (name, source) in charts {
61        let schema = resolve(name, source, project_root, cache_dir)?;
62        result.insert(name.clone(), schema);
63    }
64    Ok(result)
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn cache_hash_deterministic() {
73        let h1 = cache_hash("https://kubernetes.github.io/ingress-nginx");
74        let h2 = cache_hash("https://kubernetes.github.io/ingress-nginx");
75        assert_eq!(h1, h2);
76        assert_eq!(h1.len(), 16);
77    }
78
79    #[test]
80    fn cache_hash_different_inputs() {
81        let h1 = cache_hash("repo-a");
82        let h2 = cache_hash("repo-b");
83        assert_ne!(h1, h2);
84    }
85
86    #[test]
87    fn resolve_all_empty() {
88        let charts = HashMap::new();
89        let tmp = tempfile::tempdir().unwrap();
90        let result = resolve_all(&charts, tmp.path(), &tmp.path().join("cache")).unwrap();
91        assert!(result.is_empty());
92    }
93
94    #[test]
95    fn resolve_all_file_source() {
96        let tmp = tempfile::tempdir().unwrap();
97        std::fs::write(
98            tmp.path().join("values.schema.json"),
99            r#"{"type": "object", "properties": {"replicas": {"type": "integer"}}}"#,
100        )
101        .unwrap();
102
103        let mut charts = HashMap::new();
104        charts.insert(
105            "my-chart".to_string(),
106            ChartSource::File {
107                path: "values.schema.json".to_string(),
108            },
109        );
110
111        let result = resolve_all(&charts, tmp.path(), &tmp.path().join("cache")).unwrap();
112        assert_eq!(result.len(), 1);
113        assert!(result.contains_key("my-chart"));
114    }
115}