1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use figment::providers::Serialized;
use figment::Figment;
use serde_json::Value;
use thiserror::Error;

mod private {
    pub trait Sealed {}

    impl Sealed for figment::Figment {}
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RemoveExistingKeyError<'a> {
    #[error("key {0} not found")]
    NotFound(&'a str),
}

/// Extension trait for `figment::Figment`.
pub trait FigmentExt: Sized + private::Sealed {
    /// Remove existing keys.
    ///
    /// This method error if key doesn't exist.
    fn remove_existing_keys<'a, T: AsRef<str>>(
        &self,
        keys: &'a [T],
    ) -> Result<Self, RemoveExistingKeyError<'a>>;

    /// Check for key existent.
    ///
    /// blank key return `false`.
    fn has_key(&self, key: &str) -> bool;
}

impl FigmentExt for Figment {
    fn remove_existing_keys<'a, T: AsRef<str>>(
        &self,
        keys: &'a [T],
    ) -> Result<Self, RemoveExistingKeyError<'a>> {
        let mut value = self.extract::<Value>().expect("json serializable value");
        let mut pointer = String::new();
        let mut parts = vec![];
        for key in keys {
            let key = key.as_ref();
            if !self.has_key(key) {
                return Err(RemoveExistingKeyError::NotFound(key));
            }
            pointer.clear();
            parts.clear();
            parts.extend(key.split('.'));
            // note: .expect("object") should never fail because we already check key existent
            match parts.as_slice() {
                [] => {
                    // we already check key existent
                    unreachable!("empty parts");
                }
                [field] => {
                    value.as_object_mut().expect("object").remove(*field);
                }
                [components @ .., field] => {
                    for c in components {
                        pointer.push('/');
                        pointer.push_str(c);
                    }
                    value
                        .pointer_mut(&pointer)
                        .and_then(Value::as_object_mut)
                        .expect("object")
                        .remove(*field);
                }
            }
        }
        Ok(Figment::from(Serialized::defaults(value)))
    }

    fn has_key(&self, key: &str) -> bool {
        self.find_metadata(key).is_some() && !key.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn get_test_figment() -> Figment {
        Figment::from(Serialized::defaults(serde_json::json!({
            "foo": {
                "bar": {
                    "baz": {
                        "name": "Baz",
                    }
                },
                "s": "Foo string",
            },
            "vec": ["foo", "bar", "baz"],
        })))
    }

    #[test]
    fn remove_existing_keys() {
        let figment = get_test_figment();

        #[rustfmt::skip]
        let keys = [
            "foo.bar.baz.name",
            "foo.s",
        ];
        for key in keys {
            assert!(figment.has_key(key));
        }
        let actual = figment.remove_existing_keys(&keys);
        let f = actual.expect("keys removed");
        for key in keys {
            assert!(!f.has_key(key));
        }
    }

    #[test]
    fn remove_missing_key() {
        let figment = get_test_figment();
        let key = "foo.not_exist";
        assert!(!figment.has_key(key));
        let keys = [key];
        let actual = figment.remove_existing_keys(&keys);
        let err = actual.expect_err("key doesn't exist");
        assert!(matches!(err, RemoveExistingKeyError::NotFound(_)));
    }

    #[test]
    fn has_key() {
        let figment = get_test_figment();
        assert!(!figment.has_key(""));
    }
}