astarte_interfaces/mapping/
path.rs1use std::fmt::Display;
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct MappingPath<'a> {
30 pub(crate) path: &'a str,
31 pub(crate) levels: Vec<&'a str>,
32}
33
34impl MappingPath<'_> {
35 #[must_use]
37 pub fn as_str(&self) -> &str {
38 self.path
39 }
40
41 #[must_use]
43 pub fn len(&self) -> usize {
44 self.levels.len()
45 }
46
47 #[must_use]
49 pub fn is_empty(&self) -> bool {
50 self.levels.is_empty()
51 }
52}
53
54impl Display for MappingPath<'_> {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "{}", self.path)
57 }
58}
59
60impl<'a> TryFrom<&'a str> for MappingPath<'a> {
61 type Error = MappingPathError;
62
63 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
64 parse_mapping(value)
65 }
66}
67
68#[non_exhaustive]
70#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
71pub enum MappingPathError {
72 #[error("path missing prefix: {0}")]
74 Prefix(String),
75 #[error("path should have at least one level")]
77 Empty,
78 #[error("path has an empty level: {0}")]
80 EmptyLevel(String),
81}
82
83fn parse_mapping(input: &str) -> Result<MappingPath<'_>, MappingPathError> {
85 let path = input
86 .strip_prefix('/')
87 .ok_or_else(|| MappingPathError::Prefix(input.to_string()))?;
88
89 let levels: Vec<&str> = path
91 .split('/')
92 .map(|level| {
93 if level.is_empty() {
94 return Err(MappingPathError::EmptyLevel(input.to_string()));
95 }
96
97 Ok(level)
98 })
99 .collect::<Result<_, _>>()?;
100
101 if levels.is_empty() {
102 return Err(MappingPathError::Empty);
103 }
104
105 Ok(MappingPath {
106 path: input,
107 levels,
108 })
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn empty_endpoint() {
117 let path = MappingPath::try_from("/").unwrap_err();
118
119 assert_eq!(path, MappingPathError::EmptyLevel("/".into()));
120 }
121
122 #[test]
123 fn getters_success() {
124 let value = "/some/path";
125 let path = MappingPath::try_from(value).unwrap();
126
127 assert_eq!(path.as_str(), value);
128 assert_eq!(path.len(), 2);
129 assert!(!path.is_empty());
130 assert_eq!(path.to_string(), value);
131 }
132
133 #[test]
134 fn parse_mappings_success() {
135 let cases = [
136 "/foo/value",
137 "/bar/value",
138 "/value",
139 "/foo/bar/valu",
140 "/foo/value/ba",
141 ];
142
143 for case in cases {
144 MappingPath::try_from(case).unwrap_or_else(|err| panic!("failed for {case}: {err}"));
145 }
146 }
147
148 #[test]
149 fn parse_mappings_error() {
150 let err = MappingPath::try_from("/").unwrap_err();
151
152 assert!(matches!(err, MappingPathError::EmptyLevel(_)), "{err:?}");
153 }
154}