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/// Returns an [Invalid] error if not all keys are allowed, or if one of the keys
16/// marked as boolean has a value other than `true` or `false`.
17pub fn parse_attributes(
18    keys: &[&str],
19    attrs: Option<&HashMap<&str, &str>>,
20) -> Result<HashMap<String, String>> {
21    let mut result: HashMap<String, String> = HashMap::new();
22    if attrs.is_none() {
23        return Ok(result);
24    }
25    let key_map: HashMap<String, bool> = keys
26        .iter()
27        .map(|k| {
28            if k.starts_with("*") {
29                (k.split_at(1).1.to_string(), true)
30            } else {
31                (k.to_string(), false)
32            }
33        })
34        .collect();
35    for (key, value) in attrs.unwrap() {
36        if let Some(is_bool) = key_map.get(*key) {
37            if !is_bool || *value == "true" || *value == "false" {
38                result.insert(key.to_string(), value.to_string());
39            } else {
40                return Err(Invalid(
41                    key.to_string(),
42                    "must be `true` or `false`".to_string(),
43                ));
44            }
45        } else {
46            return Err(Invalid(key.to_string(), "unknown key".to_string()));
47        }
48    }
49    Ok(result)
50}
51
52/// Convert a borrowed key-value map of borrowed strings to an owned map of owned strings.
53pub fn externalize_attributes(attrs: &HashMap<&str, &str>) -> HashMap<String, String> {
54    attrs
55        .iter()
56        .map(|(k, v)| (k.to_string(), v.to_string()))
57        .collect()
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_parse_attributes() {
66        let attrs = HashMap::from([("key1", "value1"), ("key2", "true"), ("key3", "false")]);
67        assert_eq!(parse_attributes(&["key1"], None).unwrap().len(), 0);
68        let parsed = parse_attributes(&["key1", "*key2", "*key3"], Some(&attrs)).unwrap();
69        assert_eq!(parsed.len(), 3);
70        assert_eq!(parsed.get("key1"), Some(&"value1".to_string()));
71        assert_eq!(parsed.get("key2"), Some(&"true".to_string()));
72        assert_eq!(parsed.get("key3"), Some(&"false".to_string()));
73        let bad_attrs = HashMap::from([("key1", "t")]);
74        match parse_attributes(&["*key1"], Some(&bad_attrs)) {
75            Err(Invalid(key, msg)) => {
76                assert_eq!(key, "key1");
77                assert_eq!(msg, "must be `true` or `false`");
78            }
79            _ => panic!("Incorrect error for invalid boolean attribute"),
80        }
81        match parse_attributes(&["other_key"], Some(&bad_attrs)) {
82            Err(Invalid(key, msg)) => {
83                assert_eq!(key, "key1");
84                assert_eq!(msg, "unknown key");
85            }
86            _ => panic!("Incorrect error for unknown attribute"),
87        }
88    }
89
90    #[test]
91    fn test_externalize_attributes() {
92        let attrs = HashMap::from([("key1", "value1"), ("key2", "true"), ("key3", "false")]);
93        let externalized = externalize_attributes(&attrs);
94        assert_eq!(externalized.len(), 3);
95        assert_eq!(externalized.get("key1"), Some(&"value1".to_string()));
96        assert_eq!(externalized.get("key2"), Some(&"true".to_string()));
97        assert_eq!(externalized.get("key3"), Some(&"false".to_string()));
98    }
99}