icann_rdap_common/check/
string.rs1pub trait StringCheck {
11 fn is_whitespace_or_empty(&self) -> bool;
13
14 fn is_ldh_string(&self) -> bool;
16
17 fn is_ldh_domain_name(&self) -> bool;
20
21 fn is_unicode_domain_name(&self) -> bool;
23
24 fn is_tld(&self) -> bool;
26}
27
28impl<T: ToString> StringCheck for T {
29 fn is_whitespace_or_empty(&self) -> bool {
30 let s = self.to_string();
31 s.is_empty() || s.chars().all(char::is_whitespace)
32 }
33
34 fn is_ldh_string(&self) -> bool {
35 let s = self.to_string();
36 !s.is_empty() && s.chars().all(char::is_ldh)
37 }
38
39 fn is_ldh_domain_name(&self) -> bool {
40 let s = self.to_string();
41 s == "." || (!s.is_empty() && s.split_terminator('.').all(|s| s.is_ldh_string()))
42 }
43
44 fn is_unicode_domain_name(&self) -> bool {
45 let s = self.to_string();
46 s == "."
47 || (!s.is_empty()
48 && s.split_terminator('.').all(|s| {
49 s.chars()
50 .all(|c| c == '-' || (!c.is_ascii_punctuation() && !c.is_whitespace()))
51 }))
52 }
53
54 fn is_tld(&self) -> bool {
55 let s = self.to_string();
56 s.starts_with('.')
57 && s.len() > 2
58 && s.matches('.').count() == 1
59 && s.split_terminator('.').all(|s| {
60 s.chars()
61 .all(|c| !c.is_ascii_punctuation() && !c.is_whitespace())
62 })
63 }
64}
65
66pub trait StringListCheck {
76 fn is_empty_or_any_empty_or_whitespace(&self) -> bool;
79
80 fn is_ldh_string_list(&self) -> bool;
82}
83
84impl<T: ToString> StringListCheck for &[T] {
85 fn is_empty_or_any_empty_or_whitespace(&self) -> bool {
86 self.is_empty() || self.iter().any(|s| s.to_string().is_whitespace_or_empty())
87 }
88
89 fn is_ldh_string_list(&self) -> bool {
90 !self.is_empty() && self.iter().all(|s| s.to_string().is_ldh_string())
91 }
92}
93
94impl<T: ToString> StringListCheck for Vec<T> {
95 fn is_empty_or_any_empty_or_whitespace(&self) -> bool {
96 self.is_empty() || self.iter().any(|s| s.to_string().is_whitespace_or_empty())
97 }
98
99 fn is_ldh_string_list(&self) -> bool {
100 !self.is_empty() && self.iter().all(|s| s.to_string().is_ldh_string())
101 }
102}
103
104pub trait CharCheck {
114 #[allow(clippy::wrong_self_convention)]
116 fn is_ldh(self) -> bool;
117}
118
119impl CharCheck for char {
120 fn is_ldh(self) -> bool {
121 matches!(self, 'A'..='Z' | 'a'..='z' | '0'..='9' | '-')
122 }
123}
124
125#[cfg(test)]
126#[allow(non_snake_case)]
127mod tests {
128 use rstest::rstest;
129
130 use crate::check::string::{CharCheck, StringListCheck};
131
132 use super::StringCheck;
133
134 #[rstest]
135 #[case("foo", false)]
136 #[case("", true)]
137 #[case(" ", true)]
138 #[case("foo bar", false)]
139 fn GIVEN_string_WHEN_is_whitespace_or_empty_THEN_correct_result(
140 #[case] test_string: &str,
141 #[case] expected: bool,
142 ) {
143 let actual = test_string.is_whitespace_or_empty();
147
148 assert_eq!(actual, expected);
150 }
151
152 #[rstest]
153 #[case(&[], true)]
154 #[case(&["foo"], false)]
155 #[case(&["foo",""], true)]
156 #[case(&["foo","bar"], false)]
157 #[case(&["foo","bar baz"], false)]
158 #[case(&[""], true)]
159 #[case(&[" "], true)]
160 fn GIVEN_string_list_WHEN_is_whitespace_or_empty_THEN_correct_result(
161 #[case] test_list: &[&str],
162 #[case] expected: bool,
163 ) {
164 let actual = test_list.is_empty_or_any_empty_or_whitespace();
168
169 assert_eq!(actual, expected);
171 }
172
173 #[rstest]
174 #[case('a', true)]
175 #[case('l', true)]
176 #[case('z', true)]
177 #[case('A', true)]
178 #[case('L', true)]
179 #[case('Z', true)]
180 #[case('0', true)]
181 #[case('3', true)]
182 #[case('9', true)]
183 #[case('-', true)]
184 #[case('_', false)]
185 #[case('.', false)]
186 fn GIVEN_char_WHEN_is_ldh_THEN_correct_result(#[case] test_char: char, #[case] expected: bool) {
187 let actual = test_char.is_ldh();
191
192 assert_eq!(actual, expected);
194 }
195
196 #[rstest]
197 #[case("foo", true)]
198 #[case("", false)]
199 #[case("foo-bar", true)]
200 #[case("foo bar", false)]
201 fn GIVEN_string_WHEN_is_ldh_string_THEN_correct_result(
202 #[case] test_string: &str,
203 #[case] expected: bool,
204 ) {
205 let actual = test_string.is_ldh_string();
209
210 assert_eq!(actual, expected);
212 }
213
214 #[rstest]
215 #[case("foo", false)]
216 #[case("", false)]
217 #[case("foo-bar", false)]
218 #[case("foo bar", false)]
219 #[case(".", false)]
220 #[case(".foo.bar", false)]
221 #[case(".foo", true)]
222 fn GIVEN_string_WHEN_is_tld_THEN_correct_result(
223 #[case] test_string: &str,
224 #[case] expected: bool,
225 ) {
226 let actual = test_string.is_tld();
230
231 assert_eq!(actual, expected);
233 }
234
235 #[rstest]
236 #[case(&[], false)]
237 #[case(&["foo"], true)]
238 #[case(&["foo",""], false)]
239 #[case(&["foo","bar"], true)]
240 #[case(&["foo","bar baz"], false)]
241 #[case(&[""], false)]
242 #[case(&[" "], false)]
243 fn GIVEN_string_list_WHEN_is_ldh_string_list_THEN_correct_result(
244 #[case] test_list: &[&str],
245 #[case] expected: bool,
246 ) {
247 let actual = test_list.is_ldh_string_list();
251
252 assert_eq!(actual, expected);
254 }
255
256 #[rstest]
257 #[case("foo", true)]
258 #[case("", false)]
259 #[case(".", true)]
260 #[case("foo.bar", true)]
261 #[case("foo.bar.", true)]
262 fn GIVEN_string_WHEN_is_ldh_domain_name_THEN_correct_result(
263 #[case] test_string: &str,
264 #[case] expected: bool,
265 ) {
266 let actual = test_string.is_ldh_domain_name();
270
271 assert_eq!(actual, expected);
273 }
274
275 #[rstest]
276 #[case("foo", true)]
277 #[case("", false)]
278 #[case(".", true)]
279 #[case("foo.bar", true)]
280 #[case("foo.bar.", true)]
281 #[case("fo_o.bar.", false)]
282 #[case("fo o.bar.", false)]
283 fn GIVEN_string_WHEN_is_unicode_domain_name_THEN_correct_result(
284 #[case] test_string: &str,
285 #[case] expected: bool,
286 ) {
287 let actual = test_string.is_unicode_domain_name();
291
292 assert_eq!(actual, expected);
294 }
295}