Coverage Report

Created: 2024-02-20 21:15

/builds/xfbs/cindy/common/src/tag/filter.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::tag::{ParseError, Tag, TagPredicate};
2
use serde::{Deserialize, Serialize};
3
use std::{
4
    borrow::{Borrow, Cow},
5
    fmt::{Display, Formatter, Result as FmtResult},
6
    str::FromStr,
7
};
8
9
760
#[derive(Clone, 
Debug342
,
P14
artialE
q14
, Eq,
P0
artialOr
d0
,
O0
r
d0
,
S0
erializ
e0
,
D0
eserializ
e0
,
Hash0
)]
10
pub struct TagFilter<'a>(Option<Cow<'a, str>>, Option<Cow<'a, str>>);
11
12
impl<'a> TagFilter<'a> {
13
5.37k
    pub fn new<S: Into<Cow<'a, str>>>(name: Option<S>, value: Option<S>) -> Self {
14
5.37k
        TagFilter(name.map(Into::into), value.map(Into::into))
15
5.37k
    }
16
17
4.81k
    pub fn name(&self) -> Option<&str> {
18
4.81k
        self.0.as_ref().map(|v| 
v.borrow()4.81k
)
19
4.81k
    }
20
21
4.81k
    pub fn value(&self) -> Option<&str> {
22
4.81k
        self.1.as_ref().map(|v| 
v.borrow()4.80k
)
23
4.81k
    }
24
25
9
    pub fn matches(&self, tag: &Tag) -> bool {
26
9
        let name_matches = self.name().map(|name| 
name == tag.name()6
).unwrap_or(true);
27
9
        let value_matches = self
28
9
            .value()
29
9
            .map(|value| 
value == tag.value()6
)
30
9
            .unwrap_or(true);
31
9
        name_matches && 
value_matches6
32
9
    }
33
34
4.78k
    pub fn exists(self) -> TagPredicate<'a> {
35
4.78k
        TagPredicate::Exists(self)
36
4.78k
    }
37
38
1
    pub fn missing(self) -> TagPredicate<'a> {
39
1
        TagPredicate::Missing(self)
40
1
    }
41
}
42
43
impl FromStr for TagFilter<'static> {
44
    type Err = ParseError;
45
46
21
    fn from_str(input: &str) -> Result<Self, Self::Err> {
47
21
        let Some((
name, value20
)) = input.split_once(':') else {
48
1
            return Err(ParseError::MissingColon);
49
        };
50
20
        Ok(TagFilter(
51
20
            parse_glob(name).map(|v| 
v.to_string().into()16
),
52
20
            parse_glob(value).map(|v| 
v.to_string().into()15
),
53
20
        ))
54
21
    }
55
}
56
57
impl<'a> Display for TagFilter<'a> {
58
6
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
59
6
        write!(
60
6
            f,
61
6
            "{}:{}",
62
6
            self.0.as_ref().map(Cow::as_ref).unwrap_or("*"),
63
6
            self.1.as_ref().map(Cow::as_ref).unwrap_or("*")
64
6
        )
65
6
    }
66
}
67
68
40
fn parse_glob(input: &str) -> Option<&str> {
69
40
    match input {
70
40
        "*" => 
None9
,
71
31
        other => Some(other),
72
    }
73
40
}
74
75
#[cfg(test)]
76
mod tests {
77
    use super::*;
78
79
1
    #[test]
80
1
    fn tag_filter() {
81
1
        let tag_filter = TagFilter::new(Some("object"), None);
82
1
        assert_eq!(tag_filter.name(), Some("object"));
83
1
        assert_eq!(tag_filter.value(), None);
84
1
    }
85
86
1
    #[test]
87
1
    fn test_predicate() {
88
1
        let tag_filter = TagFilter::new(Some("object"), None);
89
1
90
1
        assert_eq!(
91
1
            tag_filter.clone().exists(),
92
1
            TagPredicate::Exists(tag_filter.clone())
93
1
        );
94
95
1
        assert_eq!(
96
1
            tag_filter.clone().missing(),
97
1
            TagPredicate::Missing(tag_filter.clone())
98
1
        );
99
1
    }
100
101
1
    #[test]
102
1
    fn test_from_str() {
103
1
        assert_eq!(
104
1
            "name:value".parse(),
105
1
            Ok(TagFilter::new(Some("name"), Some("value")))
106
1
        );
107
1
        assert_eq!("name:*".parse(), Ok(TagFilter::new(Some("name"), None)));
108
1
        assert_eq!("*:value".parse(), Ok(TagFilter::new(None, Some("value"))));
109
1
        assert_eq!("*:*".parse(), Ok(TagFilter::new::<&str>(None, None)));
110
1
        assert_eq!("abc".parse::<TagFilter>(), Err(ParseError::MissingColon));
111
1
    }
112
113
1
    #[test]
114
1
    fn test_display() {
115
1
        assert_eq!(
116
1
            TagFilter::new(Some("name"), Some("value")).to_string(),
117
1
            "name:value"
118
1
        );
119
1
        assert_eq!(TagFilter::new(None, Some("value")).to_string(), "*:value");
120
1
        assert_eq!(TagFilter::new(Some("name"), None).to_string(), "name:*");
121
1
        assert_eq!(TagFilter::new::<&str>(None, None).to_string(), "*:*");
122
1
    }
123
124
1
    #[test]
125
1
    fn tag_filter_matches() {
126
1
        let tag = Tag::new("name".into(), "value".into());
127
1
128
1
        // all matching cases:
129
1
        assert!(TagFilter::new::<&str>(None, None).matches(&tag));
130
1
        assert!(TagFilter::new::<&str>(Some("name"), None).matches(&tag));
131
1
        assert!(TagFilter::new::<&str>(None, Some("value")).matches(&tag));
132
1
        assert!(TagFilter::new::<&str>(Some("name"), Some("value")).matches(&tag));
133
134
        // non-matching examples:
135
1
        assert!(!TagFilter::new::<&str>(Some("other"), None).matches(&tag));
136
1
        assert!(!TagFilter::new::<&str>(Some("other"), Some("value")).matches(&tag));
137
1
        assert!(!TagFilter::new::<&str>(None, Some("other")).matches(&tag));
138
1
        assert!(!TagFilter::new::<&str>(Some("name"), Some("other")).matches(&tag));
139
1
        assert!(!TagFilter::new::<&str>(Some("other"), Some("other")).matches(&tag));
140
1
    }
141
}