cfg_rs/
key.rs

1use std::{cell::RefCell, collections::HashSet};
2
3use crate::{impl_cache, ConfigError};
4
5/// Config key, [ConfigSource](source/trait.ConfigSource.html) use this key to access config properties.
6///
7/// It's designed for better querying sources.
8///
9/// Config key has a normalized string representation, it is composed by
10/// multiple partial keys, which are [`&str`] or [`usize`]. We use dot(`.`) and
11/// square bracket `[]` to separate partial keys in a config key.
12///
13/// # Partial Key
14///
15/// ## String Partial Key
16///
17/// String partial key has regex pattern `[a-z][_a-z0-9]`, usually string partial keys are separated by dot(`.`).
18/// For example: `cfg.k1`.
19///
20/// ## Index Partial Key
21///
22/// Index partial keys are [`usize`] values, which is around by a pair of square bracket.
23/// For example: `[0]`, `[1]`.
24///
25/// # Config Key
26///
27/// Config key is composed by partial keys. String partial key can be followed by index partial key for zero or more times.
28/// If the string config key is not in head, then it should after a dot(`.`).
29/// For example:
30///
31///   * `cfg.v1`
32///   * `cfg.v2[0]`
33///   * `cfg.v3[0][1]`
34///   * `cfg.v4.key`
35///   * `cfg.v5.arr[0]`
36///   * `[0]`
37///
38/// Please notice that `cfg.[0]` is invalid key.
39///
40pub type ConfigKey<'a> = CacheKey<'a>;
41
42#[derive(Debug)]
43pub(crate) struct CacheString {
44    current: String,
45    mark: usize,
46}
47thread_local! {
48    static BUG: RefCell<CacheString> = RefCell::new(CacheString::new());
49}
50impl CacheString {
51    pub(crate) fn new() -> CacheString {
52        Self {
53            current: String::with_capacity(10),
54            mark: 0,
55        }
56    }
57
58    #[inline]
59    #[allow(single_use_lifetimes)]
60    pub(crate) fn push<'a, I: IntoIterator<Item = PartialKey<'a>>>(&mut self, iter: I) -> usize {
61        let mark = self.mark;
62        self.mark = self.current.len();
63        for i in iter {
64            i.update_string(&mut self.current);
65        }
66        mark
67    }
68
69    #[inline]
70    fn pop(&mut self, mark: usize) {
71        self.current.truncate(self.mark);
72        self.mark = mark;
73    }
74
75    #[inline]
76    fn clear(&mut self) {
77        self.current.clear();
78        self.mark = 0;
79    }
80
81    #[inline]
82    pub(crate) fn new_key(&mut self) -> CacheKey<'_> {
83        CacheKey { cache: self }
84    }
85
86    #[inline]
87    pub(crate) fn with_key_place<T, F: FnMut(&mut Self) -> Result<T, ConfigError>>(
88        f: F,
89    ) -> Result<T, ConfigError> {
90        BUG.with(move |buf| Self::with_key_buf(buf, f))
91    }
92}
93
94impl_cache!(CacheString);
95
96/// The implementation of [`ConfigKey`].
97#[derive(Debug)]
98pub struct CacheKey<'a> {
99    cache: &'a mut CacheString,
100}
101
102impl Drop for CacheKey<'_> {
103    fn drop(&mut self) {
104        self.cache.clear();
105    }
106}
107
108impl<'a> CacheKey<'a> {
109    pub(crate) fn push<I: Into<PartialKeyIter<'a>>>(&mut self, iter: I) -> usize {
110        self.cache.push(iter.into())
111    }
112    pub(crate) fn pop(&mut self, mark: usize) {
113        self.cache.pop(mark);
114    }
115
116    /// As string
117    pub(crate) fn as_str(&self) -> &str {
118        &self.cache.current
119    }
120}
121impl std::fmt::Display for CacheKey<'_> {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        write!(f, "{}", self.as_str())
124    }
125}
126/// Partial key, plese refer to [`ConfigKey`].
127#[allow(single_use_lifetimes)]
128#[derive(Debug, PartialEq, Eq)]
129pub enum PartialKey<'a> {
130    /// String Partial Key.
131    Str(&'a str),
132    /// Index Partial Key.
133    Int(usize),
134}
135
136impl PartialKey<'_> {
137    #[inline]
138    pub(crate) fn update_string(&self, key_long: &mut String) {
139        match self {
140            PartialKey::Int(i) => {
141                key_long.push('[');
142                key_long.push_str(&i.to_string());
143                key_long.push(']');
144            }
145            PartialKey::Str(v) => {
146                if !key_long.is_empty() {
147                    key_long.push('.');
148                }
149                key_long.push_str(v);
150            }
151        }
152    }
153}
154
155#[derive(Debug)]
156#[allow(variant_size_differences)]
157pub enum PartialKeyIter<'a> {
158    Str(std::str::Split<'a, &'a [char]>),
159    Int(Option<usize>),
160}
161
162impl<'a> Iterator for PartialKeyIter<'a> {
163    type Item = PartialKey<'a>;
164    fn next(&mut self) -> Option<Self::Item> {
165        match self {
166            PartialKeyIter::Str(s) => {
167                for v in s {
168                    if v.is_empty() {
169                        continue;
170                    }
171                    return Some(if let Ok(i) = v.parse() {
172                        PartialKey::Int(i)
173                    } else {
174                        PartialKey::Str(v)
175                    });
176                }
177                None
178            }
179            PartialKeyIter::Int(x) => x.take().map(|x| x.into()),
180        }
181    }
182}
183
184/// Partial key collector.
185#[derive(Debug)]
186pub struct PartialKeyCollector<'a> {
187    pub(crate) str_key: HashSet<&'a str>,
188    pub(crate) int_key: Option<usize>,
189}
190
191#[allow(single_use_lifetimes)]
192impl PartialKeyCollector<'_> {
193    pub(crate) fn new() -> Self {
194        Self {
195            str_key: HashSet::new(),
196            int_key: None,
197        }
198    }
199
200    /// Add index of array.
201    pub(crate) fn insert_int(&mut self, key: usize) {
202        if let Some(u) = self.int_key {
203            if u > key {
204                return;
205            }
206        }
207        self.int_key = Some(key + 1);
208    }
209}
210
211impl<'a> From<&'a str> for PartialKeyIter<'a> {
212    fn from(s: &'a str) -> Self {
213        PartialKeyIter::Str(s.split(&['.', '[', ']'][..]))
214    }
215}
216
217impl From<usize> for PartialKey<'_> {
218    fn from(s: usize) -> Self {
219        PartialKey::Int(s)
220    }
221}
222
223impl From<usize> for PartialKeyIter<'_> {
224    fn from(s: usize) -> Self {
225        PartialKeyIter::Int(Some(s))
226    }
227}
228
229impl<'a> From<&'a String> for PartialKeyIter<'a> {
230    fn from(s: &'a String) -> Self {
231        s.as_str().into()
232    }
233}
234
235#[cfg_attr(coverage_nightly, coverage(off))]
236#[cfg(test)]
237mod test {
238    use super::*;
239
240    macro_rules! should_eq {
241        ($origin:expr => $norm:expr) => {
242            let mut che = CacheString::new();
243            let mut key = che.new_key();
244            key.push($origin);
245            assert_eq!(&key.to_string(), $norm);
246            let mut chd = CacheString::new();
247            let mut kez = chd.new_key();
248            kez.push($norm);
249            assert_eq!(true, key.as_str() == kez.as_str());
250        };
251    }
252
253    #[test]
254    fn key_test() {
255        should_eq!("" => "");
256        should_eq!("." => "");
257        should_eq!(".." => "");
258        should_eq!("[" => "");
259        should_eq!("[1]" => "[1]");
260        should_eq!("1" => "[1]");
261        should_eq!("1[1]" => "[1][1]");
262        should_eq!("prefix.prop" => "prefix.prop");
263        should_eq!(".prefix.prop"=> "prefix.prop");
264        should_eq!("[]prefix.prop"=> "prefix.prop");
265        should_eq!("[0]prefix.prop"=> "[0].prefix.prop");
266        should_eq!("prefix[0].prop"=> "prefix[0].prop");
267        should_eq!("prefix.0.prop"=> "prefix[0].prop");
268        should_eq!("hello" => "hello");
269    }
270
271    macro_rules! should_ls {
272        ($($origin:literal => $norm:literal,)+) => {
273            let mut che = CacheString::new();
274            let mut key = che.new_key();
275            let mut vec = vec![];
276            let mut xxx = vec![];
277            $(
278                xxx.push(key.push($origin));
279                assert_eq!($norm, key.as_str());
280                vec.push(key.to_string());
281            )+
282            while let Some(v) = vec.pop() {
283                assert_eq!(&v, key.as_str());
284                key.pop(xxx.pop().unwrap());
285            }
286        };
287    }
288
289    #[test]
290    fn key_push_test() {
291        should_ls!(
292            "a" => "a",
293            "" => "a",
294            "b" => "a.b",
295            "1" => "a.b[1]",
296            "1" => "a.b[1][1]",
297            "a.1" => "a.b[1][1].a[1]",
298        );
299    }
300}