Skip to main content

rfham_config/
paths.rs

1//! Dot-separated configuration key paths for `rfham-config`.
2//!
3//! [`ConfigPath`] is a validated sequence of [`Name`](rfham_core::id::Name) segments
4//! joined by `.`. It parses from strings like `"station.callsign"` and displays back
5//! in the same form.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use rfham_config::paths::ConfigPath;
11//!
12//! let path: ConfigPath = "station.callsign".parse().unwrap();
13//! assert_eq!("station.callsign", path.to_string());
14//! assert!("".parse::<ConfigPath>().is_err());
15//! ```
16
17use core::{fmt::Display, str::FromStr};
18use rfham_core::{Name, error::CoreError};
19
20// ------------------------------------------------------------------------------------------------
21// Public Macros
22// ------------------------------------------------------------------------------------------------
23
24// ------------------------------------------------------------------------------------------------
25// Public Types
26// ------------------------------------------------------------------------------------------------
27
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct ConfigPath(Vec<Name>);
30
31// ------------------------------------------------------------------------------------------------
32// Public Functions
33// ------------------------------------------------------------------------------------------------
34
35// ------------------------------------------------------------------------------------------------
36// Private Macros
37// ------------------------------------------------------------------------------------------------
38
39// ------------------------------------------------------------------------------------------------
40// Private Types
41// ------------------------------------------------------------------------------------------------
42
43// ------------------------------------------------------------------------------------------------
44// Implementations
45// ------------------------------------------------------------------------------------------------
46
47impl Display for ConfigPath {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(
50            f,
51            "{}",
52            self.0
53                .iter()
54                .map(Name::to_string)
55                .collect::<Vec<_>>()
56                .join(".")
57        )
58    }
59}
60
61impl FromStr for ConfigPath {
62    type Err = CoreError;
63
64    fn from_str(s: &str) -> Result<Self, Self::Err> {
65        let results: Result<Vec<Name>, CoreError> = s.split('.').map(Name::from_str).collect();
66        let values = results?;
67        if !values.is_empty() {
68            Ok(Self(values))
69        } else {
70            Err(CoreError::InvalidValueFromStr(s.to_string(), "ConfigPath"))
71        }
72    }
73}
74
75impl From<Name> for ConfigPath {
76    fn from(value: Name) -> Self {
77        Self::from(vec![value])
78    }
79}
80
81impl From<Vec<Name>> for ConfigPath {
82    fn from(values: Vec<Name>) -> Self {
83        assert!(
84            !values.is_empty(),
85            "ConfigPath must have at least one component"
86        );
87        Self(values)
88    }
89}
90
91// ------------------------------------------------------------------------------------------------
92// Private Functions
93// ------------------------------------------------------------------------------------------------
94
95// ------------------------------------------------------------------------------------------------
96// Sub-Modules
97// ------------------------------------------------------------------------------------------------
98
99// ------------------------------------------------------------------------------------------------
100// Unit Tests
101// ------------------------------------------------------------------------------------------------
102
103#[cfg(test)]
104mod tests {
105    use super::ConfigPath;
106    use pretty_assertions::assert_eq;
107
108    #[test]
109    fn test_single_component() {
110        let path: ConfigPath = "station".parse().unwrap();
111        assert_eq!("station", path.to_string());
112    }
113
114    #[test]
115    fn test_multi_component() {
116        let path: ConfigPath = "station.callsign".parse().unwrap();
117        assert_eq!("station.callsign", path.to_string());
118    }
119
120    #[test]
121    fn test_empty_string_is_error() {
122        assert!("".parse::<ConfigPath>().is_err());
123    }
124
125    #[test]
126    fn test_invalid_component_is_error() {
127        // Names cannot contain spaces
128        assert!("station.has space".parse::<ConfigPath>().is_err());
129    }
130}