Skip to main content

bare_config/
key.rs

1//! Key type for configuration path representation.
2//!
3//! This module provides the `Key` and `KeySegment` types for
4//! representing paths to configuration values across all
5//! supported configuration formats.
6
7use core::fmt::Write;
8
9use crate::error::{ConfigError, ConfigResult};
10use nom::IResult;
11use nom::Parser;
12use nom::bytes::complete::take_while1;
13use nom::character::complete::{char, digit1};
14use nom::combinator::map_res;
15
16fn parse_index_inner(input: &str) -> IResult<&str, usize> {
17    map_res(digit1, |s: &str| s.parse::<usize>()).parse(input)
18}
19
20fn parse_key(input: &str) -> IResult<&str, String> {
21    let (input, key) =
22        take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_' || c == '-')(input)?;
23    Ok((input, key.to_string()))
24}
25
26fn parse_segment(input: &str) -> IResult<&str, KeySegment> {
27    if let Ok((input, _)) = char::<&str, nom::error::Error<&str>>('[').parse(input) {
28        let (input, idx) = parse_index_inner(input)?;
29        let (input, _) = char::<&str, nom::error::Error<&str>>(']').parse(input)?;
30        return Ok((input, KeySegment::Index(idx)));
31    }
32
33    if let Ok((input, _)) = char::<&str, nom::error::Error<&str>>('@').parse(input) {
34        let (input, name) = parse_key(input)?;
35        return Ok((input, KeySegment::Attribute(name)));
36    }
37
38    let (input, key) = parse_key(input)?;
39    Ok((input, KeySegment::Key(key)))
40}
41
42fn parse_key_segments(input: &str) -> IResult<&str, Vec<KeySegment>> {
43    // Pre-allocate: estimate capacity based on input length
44    // Average segment length is ~5 chars, so divide by 5 as a rough estimate
45    let estimated = std::cmp::max(1, input.len() / 5);
46    let (input, first) = parse_segment(input)?;
47    let mut segments = Vec::with_capacity(estimated);
48    segments.push(first);
49    let mut rest = input;
50
51    while !rest.is_empty() {
52        if let Ok((new_rest, _)) = char::<&str, nom::error::Error<&str>>('.').parse(rest) {
53            let (new_rest, seg) = parse_segment(new_rest)?;
54            segments.push(seg);
55            rest = new_rest;
56        } else if let Ok((new_rest, _)) = char::<&str, nom::error::Error<&str>>('[').parse(rest) {
57            let (new_rest, idx) = parse_index_inner(new_rest)?;
58            let (new_rest, _) = char::<&str, nom::error::Error<&str>>(']').parse(new_rest)?;
59            segments.push(KeySegment::Index(idx));
60            rest = new_rest;
61        } else if let Ok((new_rest, _)) = char::<&str, nom::error::Error<&str>>('@').parse(rest) {
62            let (new_rest, name) = parse_key(new_rest)?;
63            segments.push(KeySegment::Attribute(name));
64            rest = new_rest;
65        } else {
66            break;
67        }
68    }
69
70    Ok((rest, segments))
71}
72
73fn parse_key_path(input: &str) -> IResult<&str, Vec<KeySegment>> {
74    let input = if let Some(rest) = input.strip_prefix('.') {
75        if rest.is_empty() {
76            return Err(nom::Err::Error(nom::error::Error::new(
77                input,
78                nom::error::ErrorKind::Char,
79            )));
80        }
81        rest
82    } else {
83        return Err(nom::Err::Error(nom::error::Error::new(
84            input,
85            nom::error::ErrorKind::Char,
86        )));
87    };
88    parse_key_segments(input)
89}
90
91/// A segment of a configuration key path.
92///
93/// Key segments represent individual components of a key path:
94/// - `Key`: A named field (e.g., `.database.url`)
95/// - `Index`: An array index (e.g., `.items[0]`)
96/// - `Attribute`: An attribute name (e.g., `@type`)
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98pub enum KeySegment {
99    /// A named key segment.
100    Key(String),
101    /// An array index segment.
102    Index(usize),
103    /// An attribute name segment.
104    Attribute(String),
105}
106
107impl KeySegment {
108    /// Returns the key value if this is a key segment.
109    #[must_use]
110    pub fn as_key(&self) -> Option<&str> {
111        match self {
112            Self::Key(s) => Some(s),
113            _ => None,
114        }
115    }
116
117    /// Returns the index value if this is an index segment.
118    #[must_use]
119    pub const fn as_index(&self) -> Option<usize> {
120        match self {
121            Self::Index(i) => Some(*i),
122            _ => None,
123        }
124    }
125
126    /// Returns the attribute name if this is an attribute segment.
127    #[must_use]
128    pub fn as_attribute(&self) -> Option<&str> {
129        match self {
130            Self::Attribute(s) => Some(s),
131            _ => None,
132        }
133    }
134
135    /// Consumes this segment and returns the key value if it was a key segment.
136    #[must_use]
137    pub fn into_key(self) -> Option<String> {
138        match self {
139            Self::Key(s) => Some(s),
140            _ => None,
141        }
142    }
143
144    /// Consumes this segment and returns the index value if it was an index segment.
145    #[must_use]
146    pub fn into_index(self) -> Option<usize> {
147        match self {
148            Self::Index(i) => Some(i),
149            _ => None,
150        }
151    }
152
153    /// Consumes this segment and returns the attribute name if it was an attribute segment.
154    #[must_use]
155    pub fn into_attribute(self) -> Option<String> {
156        match self {
157            Self::Attribute(s) => Some(s),
158            _ => None,
159        }
160    }
161}
162
163impl From<String> for KeySegment {
164    fn from(s: String) -> Self {
165        Self::Key(s)
166    }
167}
168
169impl From<&str> for KeySegment {
170    fn from(s: &str) -> Self {
171        Self::Key(s.to_string())
172    }
173}
174
175impl From<usize> for KeySegment {
176    fn from(i: usize) -> Self {
177        Self::Index(i)
178    }
179}
180
181/// A configuration key path.
182///
183/// Keys represent paths to values in configuration files using
184/// a dot-separated notation with support for array indices and attributes.
185///
186/// # Examples
187///
188/// ```
189/// use bare_config::key::Key;
190/// use std::str::FromStr;
191///
192/// let key = Key::from_str(".database.host").unwrap();
193/// assert_eq!(key.segments().len(), 2);
194/// ```
195#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
196pub struct Key {
197    segments: Vec<KeySegment>,
198}
199
200impl Key {
201    #[allow(clippy::should_implement_trait)]
202    /// Parse a key from a string path.
203    ///
204    /// # Errors
205    ///
206    /// Returns [`ConfigError::InvalidKey`] if the key format is invalid.
207    pub fn from_str(s: &str) -> ConfigResult<Self> {
208        let (_, segments) = parse_key_path(s)
209            .map_err(|e| ConfigError::InvalidKey(format!("Parse error: {e:?}")))?;
210
211        if segments.is_empty() {
212            return Err(ConfigError::InvalidKey(
213                "Key must have at least one segment".to_string(),
214            ));
215        }
216
217        Ok(Self { segments })
218    }
219
220    /// Creates a key from a vector of segments.
221    #[must_use]
222    pub const fn from_segments(segments: Vec<KeySegment>) -> Self {
223        Self { segments }
224    }
225
226    /// Returns the segments that make up this key.
227    #[must_use]
228    pub fn segments(&self) -> &[KeySegment] {
229        &self.segments
230    }
231
232    /// Returns the number of segments in this key.
233    #[must_use]
234    #[allow(clippy::incompatible_msrv)]
235    pub const fn len(&self) -> usize {
236        self.segments.len()
237    }
238
239    /// Returns `true` if this key has no segments.
240    #[must_use]
241    #[allow(clippy::incompatible_msrv)]
242    pub const fn is_empty(&self) -> bool {
243        self.segments.is_empty()
244    }
245
246    /// Returns the parent key (all segments except the last).
247    ///
248    /// Returns `None` if this key has only one segment.
249    #[must_use]
250    pub fn parent(&self) -> Option<Self> {
251        if self.segments.len() > 1 {
252            Some(Self {
253                segments: self.segments[..self.segments.len() - 1].to_vec(),
254            })
255        } else {
256            None
257        }
258    }
259
260    /// Returns the last segment of this key.
261    #[must_use]
262    pub fn last_segment(&self) -> Option<&KeySegment> {
263        self.segments.last()
264    }
265
266    /// Returns the first segment of this key.
267    #[must_use]
268    pub fn first_segment(&self) -> Option<&KeySegment> {
269        self.segments.first()
270    }
271
272    /// Appends a segment to the end of this key.
273    #[must_use]
274    pub fn append(mut self, segment: KeySegment) -> Self {
275        self.segments.push(segment);
276        self
277    }
278
279    /// Converts this key back to a string representation.
280    #[must_use]
281    pub fn to_key_string(&self) -> String {
282        let mut result = String::new();
283
284        for (i, segment) in self.segments.iter().enumerate() {
285            if i > 0 {
286                match segment {
287                    KeySegment::Attribute(_) | KeySegment::Index(_) => {}
288                    _ => result.push('.'),
289                }
290            }
291
292            match segment {
293                KeySegment::Key(s) => {
294                    result.push_str(s);
295                }
296                KeySegment::Index(i) => {
297                    result.push('[');
298                    let _ = write!(&mut result, "{i}");
299                    result.push(']');
300                }
301                KeySegment::Attribute(s) => {
302                    result.push('@');
303                    result.push_str(s);
304                }
305            }
306        }
307
308        result
309    }
310
311    /// Traverse the key segments with a function.
312    ///
313    /// # Errors
314    ///
315    /// Returns [`ConfigError::InvalidKey`] if the key is empty.
316    pub fn traverse<F, T>(&self, mut f: F) -> ConfigResult<T>
317    where
318        F: FnMut(&KeySegment) -> ConfigResult<T>,
319    {
320        let mut last = None;
321        for segment in &self.segments {
322            last = Some(f(segment)?);
323        }
324        last.ok_or_else(|| ConfigError::InvalidKey("Cannot traverse empty key".to_string()))
325    }
326
327    /// Walk through all segments with a function.
328    ///
329    /// # Errors
330    ///
331    /// Returns [`ConfigError::InvalidKey`] if any segment traversal fails.
332    pub fn walk<F>(&self, mut f: F) -> ConfigResult<()>
333    where
334        F: FnMut(usize, &KeySegment) -> ConfigResult<()>,
335    {
336        for (i, segment) in self.segments.iter().enumerate() {
337            f(i, segment)?;
338        }
339        Ok(())
340    }
341
342    /// Returns the first `n` segments of this key.
343    ///
344    /// Returns `None` if `n` is 0 or greater than the number of segments.
345    #[must_use]
346    pub fn head(&self, n: usize) -> Option<Self> {
347        if n > 0 && n <= self.segments.len() {
348            Some(Self {
349                segments: self.segments[..n].to_vec(),
350            })
351        } else {
352            None
353        }
354    }
355
356    /// Returns the last `n` segments of this key.
357    ///
358    /// Returns `None` if `n` is 0 or greater than the number of segments.
359    #[must_use]
360    pub fn tail(&self, n: usize) -> Option<Self> {
361        if n > 0 && n <= self.segments.len() {
362            Some(Self {
363                segments: self.segments[self.segments.len() - n..].to_vec(),
364            })
365        } else {
366            None
367        }
368    }
369
370    /// Returns a new key with the given segment appended.
371    #[must_use]
372    pub fn child(&self, segment: KeySegment) -> Self {
373        let mut new_segments = Vec::with_capacity(self.segments.len() + 1);
374        new_segments.extend(self.segments.iter().cloned());
375        new_segments.push(segment);
376        Self {
377            segments: new_segments,
378        }
379    }
380}
381
382impl From<Vec<KeySegment>> for Key {
383    fn from(segments: Vec<KeySegment>) -> Self {
384        Self { segments }
385    }
386}
387
388impl std::fmt::Display for Key {
389    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390        write!(f, ".{}", self.to_key_string())
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397
398    #[test]
399    fn test_key_from_str_simple() {
400        let key = Key::from_str(".database").expect("test should succeed");
401        assert_eq!(key.len(), 1);
402        assert_eq!(key.segments()[0].as_key(), Some("database"));
403    }
404
405    #[test]
406    fn test_key_from_str_nested() {
407        let key = Key::from_str(".database.url").expect("test should succeed");
408        assert_eq!(key.len(), 2);
409        assert_eq!(key.segments()[0].as_key(), Some("database"));
410        assert_eq!(key.segments()[1].as_key(), Some("url"));
411    }
412
413    #[test]
414    fn test_key_from_str_index() {
415        let key = Key::from_str(".users[0]").expect("test should succeed");
416        assert_eq!(key.len(), 2);
417        assert_eq!(key.segments()[0].as_key(), Some("users"));
418        assert_eq!(key.segments()[1].as_index(), Some(0));
419    }
420
421    #[test]
422    fn test_key_from_str_attribute() {
423        let key = Key::from_str(".server@port").expect("test should succeed");
424        assert_eq!(key.len(), 2);
425        assert_eq!(key.segments()[0].as_key(), Some("server"));
426        assert_eq!(key.segments()[1].as_attribute(), Some("port"));
427    }
428
429    #[test]
430    fn test_key_from_str_complex() {
431        let key = Key::from_str(".users[0].name@text").expect("test should succeed");
432        assert_eq!(key.len(), 4);
433        assert_eq!(key.segments()[0].as_key(), Some("users"));
434        assert_eq!(key.segments()[1].as_index(), Some(0));
435        assert_eq!(key.segments()[2].as_key(), Some("name"));
436        assert_eq!(key.segments()[3].as_attribute(), Some("text"));
437    }
438
439    #[test]
440    fn test_key_from_str_invalid_empty() {
441        let result = Key::from_str("");
442        assert!(result.is_err());
443    }
444
445    #[test]
446    fn test_key_from_str_invalid_no_dot() {
447        let result = Key::from_str("database");
448        assert!(result.is_err());
449    }
450
451    #[test]
452    fn test_key_parent() {
453        let key = Key::from_str(".a.b.c").expect("test should succeed");
454        let parent = key.parent().expect("test should succeed");
455        assert_eq!(parent.len(), 2);
456    }
457
458    #[test]
459    fn test_key_to_string() {
460        let key = Key::from_str(".database.url").expect("test should succeed");
461        assert_eq!(key.to_key_string(), "database.url");
462    }
463
464    #[test]
465    fn test_key_display() {
466        let key = Key::from_str(".database").expect("test should succeed");
467        assert_eq!(format!("{}", key), ".database");
468    }
469
470    #[test]
471    fn test_key_append() {
472        let key = Key::from_str(".database").expect("test should succeed");
473        let key = key.append(KeySegment::Key("url".to_string()));
474        assert_eq!(key.len(), 2);
475    }
476
477    #[test]
478    fn test_key_traverse() {
479        let key = Key::from_str(".a.b.c").expect("test should succeed");
480        let values: Vec<&str> = key.segments().iter().filter_map(|s| s.as_key()).collect();
481        assert_eq!(values, vec!["a", "b", "c"]);
482    }
483
484    #[test]
485    fn test_key_walk() {
486        let key = Key::from_str(".a[0].b").expect("test should succeed");
487        let mut count = 0;
488        key.walk(|i, _segment| {
489            count = i;
490            Ok(())
491        })
492        .expect("test should succeed");
493        assert_eq!(count, 2);
494    }
495
496    #[test]
497    fn test_key_head_tail() {
498        let key = Key::from_str(".a.b.c.d").expect("test should succeed");
499        let head = key.head(2).expect("test should succeed");
500        let tail = key.tail(2).expect("test should succeed");
501        assert_eq!(head.len(), 2);
502        assert_eq!(tail.len(), 2);
503    }
504
505    #[test]
506    fn test_key_child() {
507        let key = Key::from_str(".a").expect("test should succeed");
508        let child = key.child(KeySegment::Key("b".to_string()));
509        assert_eq!(child.len(), 2);
510    }
511}