Skip to main content

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