Skip to main content

keyring_core/
attributes.rs

1/*!
2
3Utility functions for attribute maps
4
5 */
6use std::collections::HashMap;
7
8use crate::{Error::Invalid, Result};
9
10/// Parse an optional key-value &str map for allowed keys, returning a map of owned strings.
11///
12/// If a key is prefixed with a `*`, it is required to have a boolean value,
13/// and the `*` is stripped from the key name when parsing and returning the map.
14///
15/// If a key is prefixed with a `+`, it is required to have a non-empty value,
16/// and the `+` is stripped from the key name when parsing and returning the map.
17///
18/// Returns an [Invalid] error if not all keys are allowed, or if one of the keys
19/// marked as boolean has a value other than `true` or `false`.
20pub fn parse_attributes(
21    keys: &[&str],
22    attrs: Option<&HashMap<&str, &str>>,
23) -> Result<HashMap<String, String>> {
24    let mut result: HashMap<String, String> = HashMap::new();
25    if attrs.is_none() {
26        return Ok(result);
27    }
28    let key_map: HashMap<String, char> = keys
29        .iter()
30        .map(|k| {
31            if k.starts_with("*") {
32                (k.split_at(1).1.to_string(), '*')
33            } else if k.starts_with("+") {
34                (k.split_at(1).1.to_string(), '+')
35            } else {
36                (k.to_string(), ' ')
37            }
38        })
39        .collect();
40    for (key, value) in attrs.unwrap() {
41        if let Some(prefix) = key_map.get(*key) {
42            match *prefix {
43                '*' if *value != "true" && *value != "false" => {
44                    let msg = "must be 'true' or 'false'";
45                    return Err(Invalid(key.to_string(), msg.to_string()));
46                }
47                '+' if value.is_empty() => {
48                    let msg = "must not be empty";
49                    return Err(Invalid(key.to_string(), msg.to_string()));
50                }
51                _ => {}
52            }
53            result.insert(key.to_string(), value.to_string());
54        } else {
55            return Err(Invalid(key.to_string(), "unknown key".to_string()));
56        }
57    }
58    Ok(result)
59}
60
61/// Convert a borrowed key-value map of borrowed strings to an owned map of owned strings.
62pub fn externalize_attributes(attrs: &HashMap<&str, &str>) -> HashMap<String, String> {
63    attrs
64        .iter()
65        .map(|(k, v)| (k.to_string(), v.to_string()))
66        .collect()
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_parse_attributes() {
75        let attrs = HashMap::from([("key1", "value1"), ("key2", "true"), ("key3", "false")]);
76        assert_eq!(parse_attributes(&["key1"], None).unwrap().len(), 0);
77        assert_eq!(
78            parse_attributes(&["key1", "key2", "key3"], Some(&attrs))
79                .unwrap()
80                .len(),
81            3
82        );
83        let parsed = parse_attributes(&["+key1", "*key2", "*key3"], Some(&attrs)).unwrap();
84        assert_eq!(parsed.len(), 3);
85        assert_eq!(parsed.get("key1"), Some(&"value1".to_string()));
86        assert_eq!(parsed.get("key2"), Some(&"true".to_string()));
87        assert_eq!(parsed.get("key3"), Some(&"false".to_string()));
88        let bad_attrs = HashMap::from([("key1", "t")]);
89        match parse_attributes(&["*key1"], Some(&bad_attrs)) {
90            Err(Invalid(key, msg)) => {
91                assert_eq!(key, "key1");
92                assert_eq!(msg, "must be 'true' or 'false'");
93            }
94            _ => panic!("Incorrect error for invalid boolean attribute"),
95        }
96        let bad_attrs = HashMap::from([("key1", "")]);
97        match parse_attributes(&["+key1"], Some(&bad_attrs)) {
98            Err(Invalid(key, msg)) => {
99                assert_eq!(key, "key1");
100                assert_eq!(msg, "must not be empty");
101            }
102            _ => panic!("Incorrect error for empty string attribute"),
103        }
104        match parse_attributes(&["other_key"], Some(&bad_attrs)) {
105            Err(Invalid(key, msg)) => {
106                assert_eq!(key, "key1");
107                assert_eq!(msg, "unknown key");
108            }
109            _ => panic!("Incorrect error for unknown attribute"),
110        }
111    }
112
113    #[test]
114    fn test_externalize_attributes() {
115        let attrs = HashMap::from([("key1", "value1"), ("key2", "true"), ("key3", "false")]);
116        let externalized = externalize_attributes(&attrs);
117        assert_eq!(externalized.len(), 3);
118        assert_eq!(externalized.get("key1"), Some(&"value1".to_string()));
119        assert_eq!(externalized.get("key2"), Some(&"true".to_string()));
120        assert_eq!(externalized.get("key3"), Some(&"false".to_string()));
121    }
122}