spin_variables_static/
source.rs1use 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}