Skip to main content

fluentci_secrets/
convert.rs

1use anyhow::{bail, Result};
2use serde_json::Value;
3
4pub fn as_valid_env_name(name: String) -> Result<String> {
5    let is_valid = |c: char| c.is_ascii_alphanumeric() || c == '_';
6    if !name.is_empty()
7        && name.chars().all(is_valid)
8        && name
9            .chars()
10            .next()
11            .map_or(false, |c| c.is_ascii_alphabetic())
12    {
13        Ok(name)
14    } else {
15        bail!("secret name '{}' is invalid", name)
16    }
17}
18
19#[allow(dead_code)]
20pub fn value_as_string(name: &str, v: Value) -> Result<String> {
21    match v {
22        Value::String(s) => Ok(s),
23        Value::Bool(b) => Ok(format!("{b}")),
24        Value::Number(n) => Ok(format!("{n}")),
25        Value::Null => Ok("null".to_string()),
26        _ => bail!("secret '{}' value is not convertible", name),
27    }
28}
29
30#[allow(dead_code)]
31pub fn convert_env_name(prefix: &str, name: &str) -> Result<String> {
32    let name = name[prefix.len()..].replace('-', "_");
33    as_valid_env_name(name)
34}
35
36#[allow(dead_code)]
37pub fn decode_env_from_json(name: &str, value: Value) -> Result<Vec<(String, String)>> {
38    match value {
39        Value::Object(m) => m
40            .into_iter()
41            .map(|(k, v)| Ok((as_valid_env_name(k)?, value_as_string(name, v)?)))
42            .collect(),
43        _ => bail!(
44            "top-level value for secret '{}' must be a JSON object",
45            name
46        ),
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use serde_json::json;
53    use std::collections::HashMap;
54
55    use super::*;
56
57    macro_rules! assert_invalid_secret {
58        ($a:expr) => {
59            assert!(matches!($a, Err(_)));
60        };
61    }
62
63    #[test]
64    fn as_valid_env_name_correct() {
65        macro_rules! assert_convert {
66            ($a:expr) => {
67                assert_eq!($a, as_valid_env_name($a.to_string()).unwrap());
68            };
69        }
70
71        assert_convert!("abc");
72        assert_convert!("abc123");
73        assert_convert!("ab_12_ab");
74    }
75
76    #[test]
77    fn as_valid_env_name_invalid() {
78        macro_rules! assert_fail {
79            ($a:expr) => {
80                assert_invalid_secret!(as_valid_env_name($a.to_string()));
81            };
82        }
83
84        assert_fail!("");
85        assert_fail!("123abc");
86        assert_fail!("ab!");
87        assert_fail!("ab-ab");
88    }
89
90    #[test]
91    fn value_as_string_for_normal_values() {
92        macro_rules! assert_convert {
93            ($a:expr, $b:expr) => {
94                assert_eq!($a, value_as_string("ignored", $b).unwrap());
95            };
96        }
97
98        assert_convert!("abcd", json!("abcd"));
99        assert_convert!("12", json!(12));
100        assert_convert!("12.123", json!(12.123));
101        assert_convert!("null", json!(null));
102        assert_convert!("false", json!(false));
103        assert_convert!("true", json!(true));
104    }
105
106    #[test]
107    fn value_as_string_for_arrays_and_objects() {
108        macro_rules! assert_fail {
109            ($a:expr) => {
110                assert_invalid_secret!(value_as_string("ignored", $a));
111            };
112        }
113
114        assert_fail!(json!({ "a": 123 }));
115        assert_fail!(json!([1, 2]));
116    }
117
118    #[test]
119    fn decode_env_from_json_correct_values() {
120        macro_rules! assert_decode {
121            ($a:expr, $($name:ident = $value:expr),*) => {
122                #[allow(unused_variables, unused_mut)]
123                {
124                    let decoded = decode_env_from_json("ignored", $a).unwrap();
125                    let len = decoded.len();
126                    let mapped = decoded.into_iter().collect::<HashMap<_, _>>();
127                    let mut total = 0;
128                    $(
129                        total += 1;
130                        let name = stringify!($name);
131                        let value = $value.to_string();
132                        assert_eq!(Some(&value), mapped.get(&name[..]));
133                    )*
134                    assert_eq!(total, len);
135                }
136            };
137        }
138
139        assert_decode!(json!({}),);
140        assert_decode!(json!({"a": 1}), a = "1");
141        assert_decode!(json!({"a": 1, "b": true}), a = "1", b = "true");
142        assert_decode!(
143            json!({"a": 1, "b": true, "c": "test"}),
144            a = "1",
145            b = "true",
146            c = "test"
147        );
148    }
149
150    #[test]
151    fn decode_env_from_json_invalid() {
152        macro_rules! assert_fail {
153            ($a:expr) => {
154                assert_invalid_secret!(decode_env_from_json("ignored", $a));
155            };
156        }
157
158        assert_fail!(json!([1, 2]));
159        assert_fail!(json!("test"));
160        assert_fail!(json!(false));
161        assert_fail!(json!(true));
162        assert_fail!(json!({"a!": 1}));
163        assert_fail!(json!({"1a": 1}));
164        assert_fail!(json!({"a": {"b": 1}}));
165    }
166
167    #[test]
168    fn convert_env_name_converts_names() {
169        macro_rules! assert_convert {
170            ($a:expr, $b:expr) => {
171                assert_convert!("", $a, $b);
172            };
173            ($prefix:expr, $a:expr, $b:expr) => {
174                assert_eq!($a, convert_env_name($prefix, $b).unwrap());
175            };
176        }
177
178        assert_convert!("abc", "abc");
179        assert_convert!("abc123", "abc123");
180        assert_convert!("abc_123", "abc-123");
181        assert_convert!("abc__123", "abc--123");
182        assert_convert!("abc_123", "abc_123");
183
184        assert_convert!("zxc", "abc", "zxcabc");
185        assert_convert!("zxc", "abc", "abcabc");
186    }
187
188    #[test]
189    fn convert_env_name_invalid() {
190        macro_rules! assert_fail {
191            ($a:expr) => {
192                assert_fail!("", $a);
193            };
194            ($prefix:expr, $a:expr) => {
195                assert_invalid_secret!(convert_env_name($prefix, $a));
196            };
197        }
198
199        assert_fail!("");
200        assert_fail!("!");
201        assert_fail!("abc!");
202        assert_fail!("123abc");
203        assert_fail!("abc", "abc");
204    }
205}