textfile_metrics/
labels.rs1use std::fmt;
10
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct Labels {
14 pairs: Vec<(String, String)>,
16}
17
18impl Labels {
19 pub fn new() -> Self {
21 Self { pairs: Vec::new() }
22 }
23
24 pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
26 self.pairs.push((key.into(), value.into()));
27 self.pairs.sort_by(|a, b| a.0.cmp(&b.0));
28 self
29 }
30
31 pub fn len(&self) -> usize {
33 self.pairs.len()
34 }
35
36 pub fn is_empty(&self) -> bool {
38 self.pairs.is_empty()
39 }
40
41 pub fn contains_key(&self, key: &str) -> bool {
43 self.pairs.iter().any(|(k, _)| k == key)
44 }
45
46 pub fn get(&self, key: &str) -> Option<&str> {
48 self.pairs
49 .iter()
50 .find(|(k, _)| k == key)
51 .map(|(_, v)| v.as_str())
52 }
53
54 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
56 self.pairs.iter().map(|(k, v)| (k.as_str(), v.as_str()))
57 }
58}
59
60impl From<Vec<(String, String)>> for Labels {
61 fn from(mut pairs: Vec<(String, String)>) -> Self {
62 pairs.sort_by(|a, b| a.0.cmp(&b.0));
64 Self { pairs }
65 }
66}
67
68impl From<Vec<(&str, &str)>> for Labels {
69 fn from(pairs: Vec<(&str, &str)>) -> Self {
70 let mut converted: Vec<(String, String)> = pairs
71 .into_iter()
72 .map(|(k, v)| (k.to_string(), v.to_string()))
73 .collect();
74 converted.sort_by(|a, b| a.0.cmp(&b.0));
75 Self { pairs: converted }
76 }
77}
78
79impl fmt::Display for Labels {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 if self.pairs.is_empty() {
83 return Ok(());
84 }
85
86 write!(f, "{{")?;
87
88 for (i, (key, value)) in self.pairs.iter().enumerate() {
89 if i > 0 {
90 write!(f, ",")?;
91 }
92 let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
94 write!(f, "{}=\"{}\"", key, escaped)?;
95 }
96
97 write!(f, "}}")
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_labels_from_vec_of_strings() {
107 let labels = Labels::from(vec![
108 ("method".to_string(), "GET".to_string()),
109 ("status".to_string(), "200".to_string()),
110 ]);
111
112 assert_eq!(labels.len(), 2);
113 assert_eq!(labels.get("method"), Some("GET"));
114 assert_eq!(labels.get("status"), Some("200"));
115 }
116
117 #[test]
118 fn test_labels_from_vec_of_refs() {
119 let labels = Labels::from(vec![("method", "GET"), ("status", "200")]);
120
121 assert_eq!(labels.len(), 2);
122 assert_eq!(labels.get("method"), Some("GET"));
123 }
124
125 #[test]
126 fn test_labels_sorting() {
127 let labels = Labels::from(vec![
128 ("z".to_string(), "last".to_string()),
129 ("a".to_string(), "first".to_string()),
130 ("m".to_string(), "middle".to_string()),
131 ]);
132
133 let formatted = labels.to_string();
134 let a_pos = formatted.find("a=").unwrap();
135 let m_pos = formatted.find("m=").unwrap();
136 let z_pos = formatted.find("z=").unwrap();
137
138 assert!(a_pos < m_pos && m_pos < z_pos, "Labels should be sorted");
139 }
140
141 #[test]
142 fn test_labels_empty() {
143 let labels = Labels::new();
144 assert!(labels.is_empty());
145 assert_eq!(labels.to_string(), "");
146 }
147
148 #[test]
149 fn test_labels_with_quotes() {
150 let labels = Labels::from(vec![("key".to_string(), "value\"quoted\"".to_string())]);
151
152 let formatted = labels.to_string();
153 assert!(formatted.contains("\\\""));
154 }
155
156 #[test]
157 fn test_labels_with_backslash() {
158 let labels = Labels::from(vec![("path".to_string(), "C:\\Users\\test".to_string())]);
159
160 let formatted = labels.to_string();
161 assert!(formatted.contains("\\\\"));
162 }
163
164 #[test]
165 fn test_labels_contains_key() {
166 let labels = Labels::from(vec![("method".to_string(), "GET".to_string())]);
167
168 assert!(labels.contains_key("method"));
169 assert!(!labels.contains_key("status"));
170 }
171
172 #[test]
173 fn test_labels_iterator() {
174 let labels = Labels::from(vec![
175 ("a".to_string(), "1".to_string()),
176 ("b".to_string(), "2".to_string()),
177 ]);
178
179 let pairs: Vec<_> = labels.iter().collect();
180 assert_eq!(pairs, vec![("a", "1"), ("b", "2")]);
181 }
182}