spin_variables_static/
source.rs

1use spin_common::ui::quoted_path;
2use spin_factors::anyhow::{self, bail, Context as _};
3use std::{collections::HashMap, path::PathBuf, str::FromStr};
4
5#[derive(Clone, Debug)]
6pub enum VariableSource {
7    Literal(String, String),
8    JsonFile(PathBuf),
9    TomlFile(PathBuf),
10}
11
12impl VariableSource {
13    pub fn get_variables(&self) -> anyhow::Result<HashMap<String, String>> {
14        match self {
15            VariableSource::Literal(key, val) => Ok([(key.to_string(), val.to_string())].into()),
16            VariableSource::JsonFile(path) => {
17                let json_bytes = std::fs::read(path)
18                    .with_context(|| format!("Failed to read {}.", quoted_path(path)))?;
19                let json_vars: HashMap<String, String> = serde_json::from_slice(&json_bytes)
20                    .with_context(|| format!("Failed to parse JSON from {}.", quoted_path(path)))?;
21                Ok(json_vars)
22            }
23            VariableSource::TomlFile(path) => {
24                let toml_str = std::fs::read_to_string(path)
25                    .with_context(|| format!("Failed to read {}.", quoted_path(path)))?;
26                let toml_vars: HashMap<String, String> = toml::from_str(&toml_str)
27                    .with_context(|| format!("Failed to parse TOML from {}.", quoted_path(path)))?;
28                Ok(toml_vars)
29            }
30        }
31    }
32}
33
34impl FromStr for VariableSource {
35    type Err = anyhow::Error;
36
37    fn from_str(s: &str) -> Result<Self, Self::Err> {
38        if let Some(path) = s.strip_prefix('@') {
39            let path = PathBuf::from(path);
40            match path.extension().and_then(|s| s.to_str()) {
41                Some("json") => Ok(VariableSource::JsonFile(path)),
42                Some("toml") => Ok(VariableSource::TomlFile(path)),
43                _ => bail!("variable files must end in .json or .toml"),
44            }
45        } else if let Some((key, val)) = s.split_once('=') {
46            Ok(VariableSource::Literal(key.to_string(), val.to_string()))
47        } else {
48            bail!("variables must be in the form 'key=value' or '@file'")
49        }
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use std::io::Write;
56
57    use super::*;
58
59    #[test]
60    fn source_from_str() {
61        match "k=v".parse() {
62            Ok(VariableSource::Literal(key, val)) => {
63                assert_eq!(key, "k");
64                assert_eq!(val, "v");
65            }
66            Ok(other) => panic!("wrong variant {other:?}"),
67            Err(err) => panic!("{err:?}"),
68        }
69        match "@file.json".parse() {
70            Ok(VariableSource::JsonFile(_)) => {}
71            Ok(other) => panic!("wrong variant {other:?}"),
72            Err(err) => panic!("{err:?}"),
73        }
74        match "@file.toml".parse() {
75            Ok(VariableSource::TomlFile(_)) => {}
76            Ok(other) => panic!("wrong variant {other:?}"),
77            Err(err) => panic!("{err:?}"),
78        }
79    }
80
81    #[test]
82    fn source_from_str_errors() {
83        assert!(VariableSource::from_str("nope").is_err());
84        assert!(VariableSource::from_str("@whatami").is_err());
85        assert!(VariableSource::from_str("@wrong.kind").is_err());
86    }
87
88    #[test]
89    fn literal_get_variables() {
90        let vars = VariableSource::Literal("k".to_string(), "v".to_string())
91            .get_variables()
92            .unwrap();
93        assert_eq!(vars["k"], "v");
94    }
95
96    #[test]
97    fn json_get_variables() {
98        let mut json_file = tempfile::NamedTempFile::with_suffix(".json").unwrap();
99        json_file.write_all(br#"{"k": "v"}"#).unwrap();
100        let json_path = json_file.into_temp_path();
101        let vars = VariableSource::JsonFile(json_path.to_path_buf())
102            .get_variables()
103            .unwrap();
104        assert_eq!(vars["k"], "v");
105    }
106
107    #[test]
108    fn toml() {
109        let mut toml_file = tempfile::NamedTempFile::with_suffix(".toml").unwrap();
110        toml_file.write_all(br#"k = "v""#).unwrap();
111        let toml_path = toml_file.into_temp_path();
112        let vars = VariableSource::TomlFile(toml_path.to_path_buf())
113            .get_variables()
114            .unwrap();
115        assert_eq!(vars["k"], "v");
116    }
117}