1#[derive(Debug, Clone, PartialEq, Eq)]
3pub enum LabelFilter {
4 Exists(String),
6 Equals(String, String),
8}
9
10impl LabelFilter {
11 pub fn parse(s: &str) -> Self {
13 if let Some((key, value)) = s.split_once('=') {
14 Self::Equals(key.to_owned(), value.to_owned())
15 } else {
16 Self::Exists(s.to_owned())
17 }
18 }
19
20 pub fn matches(&self, labels: &hashbrown::HashMap<String, String>) -> bool {
22 match self {
23 Self::Exists(key) => labels.contains_key(key),
24 Self::Equals(key, value) => labels.get(key).map(|v| v == value).unwrap_or(false),
25 }
26 }
27}
28
29pub fn matches_all(filters: &[LabelFilter], labels: &hashbrown::HashMap<String, String>) -> bool {
32 filters.iter().all(|f| f.matches(labels))
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use hashbrown::HashMap;
39
40 fn map(pairs: &[(&str, &str)]) -> HashMap<String, String> {
41 pairs
42 .iter()
43 .map(|(k, v)| (k.to_string(), v.to_string()))
44 .collect()
45 }
46
47 #[test]
48 fn parse_key_only() {
49 assert_eq!(LabelFilter::parse("area"), LabelFilter::Exists("area".into()));
50 }
51
52 #[test]
53 fn parse_key_value() {
54 assert_eq!(
55 LabelFilter::parse("area=ci"),
56 LabelFilter::Equals("area".into(), "ci".into())
57 );
58 }
59
60 #[test]
61 fn parse_key_value_with_equals_in_value() {
62 assert_eq!(
64 LabelFilter::parse("key=a=b"),
65 LabelFilter::Equals("key".into(), "a=b".into())
66 );
67 }
68
69 #[test]
70 fn exists_filter_matches_present_key() {
71 let labels = map(&[("area", "ci")]);
72 assert!(LabelFilter::Exists("area".into()).matches(&labels));
73 }
74
75 #[test]
76 fn exists_filter_rejects_missing_key() {
77 let labels = map(&[("other", "x")]);
78 assert!(!LabelFilter::Exists("area".into()).matches(&labels));
79 }
80
81 #[test]
82 fn equals_filter_matches_exact_value() {
83 let labels = map(&[("area", "ci")]);
84 assert!(LabelFilter::Equals("area".into(), "ci".into()).matches(&labels));
85 }
86
87 #[test]
88 fn equals_filter_rejects_wrong_value() {
89 let labels = map(&[("area", "build")]);
90 assert!(!LabelFilter::Equals("area".into(), "ci".into()).matches(&labels));
91 }
92
93 #[test]
94 fn equals_filter_rejects_missing_key() {
95 let labels = map(&[]);
96 assert!(!LabelFilter::Equals("area".into(), "ci".into()).matches(&labels));
97 }
98
99 #[test]
100 fn matches_all_empty_filters() {
101 let labels = map(&[]);
102 assert!(matches_all(&[], &labels));
103 }
104
105 #[test]
106 fn matches_all_multiple_filters_all_match() {
107 let labels = map(&[("area", "ci"), ("kind", "test")]);
108 let filters = vec![
109 LabelFilter::Equals("area".into(), "ci".into()),
110 LabelFilter::Exists("kind".into()),
111 ];
112 assert!(matches_all(&filters, &labels));
113 }
114
115 #[test]
116 fn matches_all_multiple_filters_one_fails() {
117 let labels = map(&[("area", "ci")]);
118 let filters = vec![
119 LabelFilter::Equals("area".into(), "ci".into()),
120 LabelFilter::Exists("kind".into()),
121 ];
122 assert!(!matches_all(&filters, &labels));
123 }
124
125 #[test]
126 fn matches_all_no_match() {
127 let labels = map(&[("area", "build")]);
128 let filters = vec![LabelFilter::Equals("area".into(), "ci".into())];
129 assert!(!matches_all(&filters, &labels));
130 }
131}