Skip to main content

compact_str/features/
garde.rs

1use crate::CompactString;
2
3#[cfg_attr(docsrs, doc(cfg(feature = "garde")))]
4impl garde::rules::AsStr for CompactString {
5    fn as_str(&self) -> &str {
6        self.as_str()
7    }
8}
9
10#[cfg_attr(docsrs, doc(cfg(feature = "garde")))]
11impl garde::rules::length::HasBytes for CompactString {
12    fn num_bytes(&self) -> usize {
13        self.len()
14    }
15}
16
17#[cfg_attr(docsrs, doc(cfg(feature = "garde")))]
18impl garde::rules::length::HasChars for CompactString {
19    fn num_chars(&self) -> usize {
20        self.chars().count()
21    }
22}
23
24#[cfg_attr(docsrs, doc(cfg(feature = "garde")))]
25impl garde::rules::length::HasSimpleLength for CompactString {
26    fn length(&self) -> usize {
27        self.len()
28    }
29}
30
31#[cfg_attr(docsrs, doc(cfg(feature = "garde")))]
32impl garde::rules::length::HasUtf16CodeUnits for CompactString {
33    fn num_code_units(&self) -> usize {
34        self.encode_utf16().count()
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use garde::Validate;
41
42    use crate::CompactString;
43
44    // Validates that CompactString works as a field in a garde-validated struct
45    // using the `ascii` rule (which relies on AsStr).
46    #[derive(garde::Validate)]
47    struct AsciiTest {
48        #[garde(ascii)]
49        value: CompactString,
50    }
51
52    #[test]
53    fn as_str_ascii_valid() {
54        let t = AsciiTest {
55            value: CompactString::from("hello world!"),
56        };
57        assert!(t.validate().is_ok());
58    }
59
60    #[test]
61    fn as_str_ascii_invalid() {
62        let t = AsciiTest {
63            value: CompactString::from("héllo"),
64        };
65        assert!(t.validate().is_err());
66    }
67
68    // Tests length(bytes, ...) which relies on HasBytes.
69    // Multi-byte UTF-8 chars make byte count diverge from char count.
70    #[derive(garde::Validate)]
71    struct ByteLengthTest {
72        #[garde(length(bytes, min = 1, max = 4))]
73        value: CompactString,
74    }
75
76    #[test]
77    fn has_bytes_within_limit() {
78        // "hi" = 2 bytes
79        let t = ByteLengthTest {
80            value: CompactString::from("hi"),
81        };
82        assert!(t.validate().is_ok());
83    }
84
85    #[test]
86    fn has_bytes_exceeds_limit() {
87        // "héllo" = 6 bytes (é is 2 bytes) — exceeds max=4
88        let t = ByteLengthTest {
89            value: CompactString::from("héllo"),
90        };
91        assert!(t.validate().is_err());
92    }
93
94    #[test]
95    fn has_bytes_empty_fails() {
96        let t = ByteLengthTest {
97            value: CompactString::new(""),
98        };
99        assert!(t.validate().is_err());
100    }
101
102    // Tests length(chars, ...) which relies on HasChars.
103    // A multi-byte char is still one char.
104    #[derive(garde::Validate)]
105    struct CharLengthTest {
106        #[garde(length(chars, min = 1, max = 3))]
107        value: CompactString,
108    }
109
110    #[test]
111    fn has_chars_within_limit() {
112        // "héy" = 3 chars (even though é is 2 bytes)
113        let t = CharLengthTest {
114            value: CompactString::from("héy"),
115        };
116        assert!(t.validate().is_ok());
117    }
118
119    #[test]
120    fn has_chars_exceeds_limit() {
121        let t = CharLengthTest {
122            value: CompactString::from("abcd"),
123        };
124        assert!(t.validate().is_err());
125    }
126
127    #[test]
128    fn has_chars_empty_fails() {
129        let t = CharLengthTest {
130            value: CompactString::new(""),
131        };
132        assert!(t.validate().is_err());
133    }
134
135    // Tests length(...) with no mode, which uses HasSimpleLength (byte-based).
136    #[derive(garde::Validate)]
137    struct SimpleLengthTest {
138        #[garde(length(min = 2, max = 5))]
139        value: CompactString,
140    }
141
142    #[test]
143    fn has_simple_length_valid() {
144        let t = SimpleLengthTest {
145            value: CompactString::from("abc"),
146        };
147        assert!(t.validate().is_ok());
148    }
149
150    #[test]
151    fn has_simple_length_too_short() {
152        let t = SimpleLengthTest {
153            value: CompactString::from("a"),
154        };
155        assert!(t.validate().is_err());
156    }
157
158    #[test]
159    fn has_simple_length_too_long() {
160        let t = SimpleLengthTest {
161            value: CompactString::from("abcdef"),
162        };
163        assert!(t.validate().is_err());
164    }
165
166    // Tests length(utf16, ...) which relies on HasUtf16CodeUnits.
167    // Emoji outside the BMP require 2 UTF-16 code units (surrogate pair).
168    #[derive(garde::Validate)]
169    struct Utf16LengthTest {
170        #[garde(length(utf16, min = 1, max = 2))]
171        value: CompactString,
172    }
173
174    #[test]
175    fn has_utf16_bmp_char_valid() {
176        // "é" = 1 UTF-16 code unit
177        let t = Utf16LengthTest {
178            value: CompactString::from("é"),
179        };
180        assert!(t.validate().is_ok());
181    }
182
183    #[test]
184    fn has_utf16_surrogate_pair_valid() {
185        // "😀" = 2 UTF-16 code units
186        let t = Utf16LengthTest {
187            value: CompactString::from("😀"),
188        };
189        assert!(t.validate().is_ok());
190    }
191
192    #[test]
193    fn has_utf16_exceeds_limit() {
194        // "😀😀" = 4 UTF-16 code units — exceeds max=2
195        let t = Utf16LengthTest {
196            value: CompactString::from("😀😀"),
197        };
198        assert!(t.validate().is_err());
199    }
200
201    // Ensure CompactString and String produce identical results across all traits.
202    #[derive(garde::Validate)]
203    struct StringTest {
204        #[garde(ascii)]
205        #[garde(length(bytes, min = 1, max = 100))]
206        #[garde(length(chars, min = 1, max = 100))]
207        #[garde(length(min = 1, max = 100))]
208        #[garde(length(utf16, min = 1, max = 100))]
209        value: std::string::String,
210    }
211
212    #[derive(garde::Validate)]
213    struct CompactTest {
214        #[garde(ascii)]
215        #[garde(length(bytes, min = 1, max = 100))]
216        #[garde(length(chars, min = 1, max = 100))]
217        #[garde(length(min = 1, max = 100))]
218        #[garde(length(utf16, min = 1, max = 100))]
219        value: CompactString,
220    }
221
222    #[test]
223    fn matches_string_behavior() {
224        for s in &["hello", "héllo", "😀", "a", ""] {
225            let string_result = StringTest {
226                value: std::string::String::from(*s),
227            }
228            .validate();
229            let compact_result = CompactTest {
230                value: CompactString::from(*s),
231            }
232            .validate();
233            assert_eq!(
234                string_result.is_ok(),
235                compact_result.is_ok(),
236                "mismatch for input {:?}",
237                s
238            );
239        }
240    }
241}