rama_proxy/proxydb/
str.rs

1use serde::{Deserialize, Serialize};
2use std::{convert::Infallible, str::FromStr};
3use unicode_normalization::UnicodeNormalization;
4
5#[derive(Debug, Clone)]
6/// A string filter that normalizes the string prior to consumption.
7///
8/// Normalizations:
9///
10/// - trims whitespace
11/// - case-insensitive
12/// - NFC normalizes
13pub struct StringFilter(String);
14
15impl StringFilter {
16    /// Create a string filter which will match anything
17    pub fn any() -> Self {
18        "*".into()
19    }
20
21    /// Create a new string filter.
22    pub fn new(value: impl AsRef<str>) -> Self {
23        Self(value.as_ref().trim().to_lowercase().nfc().collect())
24    }
25
26    /// Get the inner string.
27    pub fn inner(&self) -> &str {
28        &self.0
29    }
30
31    /// Convert the string filter into the inner string.
32    pub fn into_inner(self) -> String {
33        self.0
34    }
35
36    /// Return `true` if this value is considered an "any" value
37    pub fn is_any(&self) -> bool {
38        self.0 == "*"
39    }
40}
41
42impl PartialEq for StringFilter {
43    fn eq(&self, other: &Self) -> bool {
44        match (self.0.as_str(), other.0.as_str()) {
45            ("*", _) | (_, "*") => true,
46            _ => self.0 == other.0,
47        }
48    }
49}
50
51impl Eq for StringFilter {}
52
53impl std::hash::Hash for StringFilter {
54    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
55        self.0.hash(state);
56    }
57}
58
59impl AsRef<str> for StringFilter {
60    fn as_ref(&self) -> &str {
61        &self.0
62    }
63}
64
65impl FromStr for StringFilter {
66    type Err = Infallible;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        Ok(Self::new(s))
70    }
71}
72
73impl std::fmt::Display for StringFilter {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}", self.0)
76    }
77}
78
79impl From<StringFilter> for String {
80    fn from(filter: StringFilter) -> Self {
81        filter.0
82    }
83}
84
85impl From<&StringFilter> for String {
86    fn from(filter: &StringFilter) -> Self {
87        filter.0.clone()
88    }
89}
90
91impl From<&str> for StringFilter {
92    fn from(value: &str) -> Self {
93        Self::new(value)
94    }
95}
96
97impl From<String> for StringFilter {
98    fn from(value: String) -> Self {
99        Self::new(value)
100    }
101}
102
103impl From<&String> for StringFilter {
104    fn from(value: &String) -> Self {
105        Self::new(value)
106    }
107}
108
109impl Serialize for StringFilter {
110    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111    where
112        S: serde::Serializer,
113    {
114        self.0.serialize(serializer)
115    }
116}
117
118impl<'de> Deserialize<'de> for StringFilter {
119    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120    where
121        D: serde::Deserializer<'de>,
122    {
123        String::deserialize(deserializer).map(Self::new)
124    }
125}
126
127#[cfg(feature = "memory-db")]
128impl venndb::Any for StringFilter {
129    fn is_any(&self) -> bool {
130        Self::is_any(self)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_string_filter_creation() {
140        let filter = StringFilter::new("  Hello World  ");
141        assert_eq!(filter, "hello world".into());
142    }
143
144    #[test]
145    fn test_string_filter_nfc() {
146        let filter = StringFilter::new("ÅΩ");
147        assert_eq!(filter, "ÅΩ".into());
148    }
149
150    #[test]
151    fn test_string_filter_case_insensitive() {
152        let filter = StringFilter::new("Hello World");
153        assert_eq!(filter, "hello world".into());
154    }
155
156    #[test]
157    fn test_string_filter_deref() {
158        let filter = StringFilter::new("Hello World");
159        assert_eq!(filter.as_ref().to_ascii_uppercase(), "HELLO WORLD");
160    }
161
162    #[test]
163    fn test_string_filter_as_str() {
164        let filter = StringFilter::new("Hello World");
165        assert_eq!(filter.as_ref(), "hello world");
166    }
167
168    #[test]
169    fn test_string_filter_serialization() {
170        let filter = StringFilter::new("Hello World");
171        let json = serde_json::to_string(&filter).unwrap();
172        assert_eq!(json, "\"hello world\"");
173        let filter2: StringFilter = serde_json::from_str(&json).unwrap();
174        assert_eq!(filter, filter2);
175    }
176
177    #[test]
178    fn test_string_filter_deserialization() {
179        let json = "\"  Hello World\"";
180        let filter: StringFilter = serde_json::from_str(json).unwrap();
181        assert_eq!(filter, "hello world".into());
182    }
183
184    #[test]
185    fn test_string_filter_any() {
186        let filter = StringFilter::any();
187        assert!(filter.is_any());
188
189        let filter: StringFilter = "hello".into();
190        assert!(!filter.is_any());
191    }
192
193    #[test]
194    fn test_string_filter_eq_cases() {
195        for (a, b) in [
196            ("hello", "hello"),
197            ("hello", "HELLO"),
198            ("HELLO", "hello"),
199            ("HELLO", "HELLO"),
200            (" foo", "foo "),
201            ("foo ", " foo"),
202            (" FOO ", " foo"),
203            ("*", "*"),
204            ("*", "foo"),
205            ("foo", "*"),
206            ("  * ", "foo"),
207            ("foo", "  * "),
208        ] {
209            let a: StringFilter = a.into();
210            let b: StringFilter = b.into();
211            assert_eq!(a, b);
212        }
213    }
214
215    #[test]
216    fn test_string_filter_neq() {
217        for (a, b) in [("hello", "world"), ("world", "hello")] {
218            let a: StringFilter = a.into();
219            let b: StringFilter = b.into();
220            assert_ne!(a, b);
221        }
222    }
223}