use std::fmt;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Labels {
pairs: Vec<(String, String)>,
}
impl Labels {
pub fn new() -> Self {
Self { pairs: Vec::new() }
}
pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.pairs.push((key.into(), value.into()));
self.pairs.sort_by(|a, b| a.0.cmp(&b.0));
self
}
pub fn len(&self) -> usize {
self.pairs.len()
}
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
pub fn contains_key(&self, key: &str) -> bool {
self.pairs.iter().any(|(k, _)| k == key)
}
pub fn get(&self, key: &str) -> Option<&str> {
self.pairs
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.pairs.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
}
impl From<Vec<(String, String)>> for Labels {
fn from(mut pairs: Vec<(String, String)>) -> Self {
pairs.sort_by(|a, b| a.0.cmp(&b.0));
Self { pairs }
}
}
impl From<Vec<(&str, &str)>> for Labels {
fn from(pairs: Vec<(&str, &str)>) -> Self {
let mut converted: Vec<(String, String)> = pairs
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
converted.sort_by(|a, b| a.0.cmp(&b.0));
Self { pairs: converted }
}
}
impl fmt::Display for Labels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.pairs.is_empty() {
return Ok(());
}
write!(f, "{{")?;
for (i, (key, value)) in self.pairs.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
write!(f, "{}=\"{}\"", key, escaped)?;
}
write!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_labels_from_vec_of_strings() {
let labels = Labels::from(vec![
("method".to_string(), "GET".to_string()),
("status".to_string(), "200".to_string()),
]);
assert_eq!(labels.len(), 2);
assert_eq!(labels.get("method"), Some("GET"));
assert_eq!(labels.get("status"), Some("200"));
}
#[test]
fn test_labels_from_vec_of_refs() {
let labels = Labels::from(vec![("method", "GET"), ("status", "200")]);
assert_eq!(labels.len(), 2);
assert_eq!(labels.get("method"), Some("GET"));
}
#[test]
fn test_labels_sorting() {
let labels = Labels::from(vec![
("z".to_string(), "last".to_string()),
("a".to_string(), "first".to_string()),
("m".to_string(), "middle".to_string()),
]);
let formatted = labels.to_string();
let a_pos = formatted.find("a=").unwrap();
let m_pos = formatted.find("m=").unwrap();
let z_pos = formatted.find("z=").unwrap();
assert!(a_pos < m_pos && m_pos < z_pos, "Labels should be sorted");
}
#[test]
fn test_labels_empty() {
let labels = Labels::new();
assert!(labels.is_empty());
assert_eq!(labels.to_string(), "");
}
#[test]
fn test_labels_with_quotes() {
let labels = Labels::from(vec![("key".to_string(), "value\"quoted\"".to_string())]);
let formatted = labels.to_string();
assert!(formatted.contains("\\\""));
}
#[test]
fn test_labels_with_backslash() {
let labels = Labels::from(vec![("path".to_string(), "C:\\Users\\test".to_string())]);
let formatted = labels.to_string();
assert!(formatted.contains("\\\\"));
}
#[test]
fn test_labels_contains_key() {
let labels = Labels::from(vec![("method".to_string(), "GET".to_string())]);
assert!(labels.contains_key("method"));
assert!(!labels.contains_key("status"));
}
#[test]
fn test_labels_iterator() {
let labels = Labels::from(vec![
("a".to_string(), "1".to_string()),
("b".to_string(), "2".to_string()),
]);
let pairs: Vec<_> = labels.iter().collect();
assert_eq!(pairs, vec![("a", "1"), ("b", "2")]);
}
}