icann_rdap_common/check/
string.rs1pub trait StringCheck {
2 fn is_whitespace_or_empty(&self) -> bool;
4
5 fn is_ldh_string(&self) -> bool;
7
8 fn is_ldh_domain_name(&self) -> bool;
11
12 fn is_unicode_domain_name(&self) -> bool;
14
15 fn is_tld(&self) -> bool;
17}
18
19impl<T: ToString> StringCheck for T {
20 fn is_whitespace_or_empty(&self) -> bool {
21 let s = self.to_string();
22 s.is_empty() || s.chars().all(char::is_whitespace)
23 }
24
25 fn is_ldh_string(&self) -> bool {
26 let s = self.to_string();
27 !s.is_empty() && s.chars().all(char::is_ldh)
28 }
29
30 fn is_ldh_domain_name(&self) -> bool {
31 let s = self.to_string();
32 s == "." || (!s.is_empty() && s.split_terminator('.').all(|s| s.is_ldh_string()))
33 }
34
35 fn is_unicode_domain_name(&self) -> bool {
36 let s = self.to_string();
37 s == "."
38 || (!s.is_empty()
39 && s.split_terminator('.').all(|s| {
40 s.chars()
41 .all(|c| c == '-' || (!c.is_ascii_punctuation() && !c.is_whitespace()))
42 }))
43 }
44
45 fn is_tld(&self) -> bool {
46 let s = self.to_string();
47 s.starts_with('.')
48 && s.len() > 2
49 && s.matches('.').count() == 1
50 && s.split_terminator('.').all(|s| {
51 s.chars()
52 .all(|c| !c.is_ascii_punctuation() && !c.is_whitespace())
53 })
54 }
55}
56
57pub trait StringListCheck {
58 fn is_empty_or_any_empty_or_whitespace(&self) -> bool;
61
62 fn is_ldh_string_list(&self) -> bool;
64}
65
66impl<T: ToString> StringListCheck for &[T] {
67 fn is_empty_or_any_empty_or_whitespace(&self) -> bool {
68 self.is_empty() || self.iter().any(|s| s.to_string().is_whitespace_or_empty())
69 }
70
71 fn is_ldh_string_list(&self) -> bool {
72 !self.is_empty() && self.iter().all(|s| s.to_string().is_ldh_string())
73 }
74}
75
76pub trait CharCheck {
77 #[allow(clippy::wrong_self_convention)]
79 fn is_ldh(self) -> bool;
80}
81
82impl CharCheck for char {
83 fn is_ldh(self) -> bool {
84 matches!(self, 'A'..='Z' | 'a'..='z' | '0'..='9' | '-')
85 }
86}
87
88#[cfg(test)]
89#[allow(non_snake_case)]
90mod tests {
91 use rstest::rstest;
92
93 use crate::check::string::CharCheck;
94 use crate::check::string::StringListCheck;
95
96 use super::StringCheck;
97
98 #[rstest]
99 #[case("foo", false)]
100 #[case("", true)]
101 #[case(" ", true)]
102 #[case("foo bar", false)]
103 fn GIVEN_string_WHEN_is_whitespace_or_empty_THEN_correct_result(
104 #[case] test_string: &str,
105 #[case] expected: bool,
106 ) {
107 let actual = test_string.is_whitespace_or_empty();
111
112 assert_eq!(actual, expected);
114 }
115
116 #[rstest]
117 #[case(&[], true)]
118 #[case(&["foo"], false)]
119 #[case(&["foo",""], true)]
120 #[case(&["foo","bar"], false)]
121 #[case(&["foo","bar baz"], false)]
122 #[case(&[""], true)]
123 #[case(&[" "], true)]
124 fn GIVEN_string_list_WHEN_is_whitespace_or_empty_THEN_correct_result(
125 #[case] test_list: &[&str],
126 #[case] expected: bool,
127 ) {
128 let actual = test_list.is_empty_or_any_empty_or_whitespace();
132
133 assert_eq!(actual, expected);
135 }
136
137 #[rstest]
138 #[case('a', true)]
139 #[case('l', true)]
140 #[case('z', true)]
141 #[case('A', true)]
142 #[case('L', true)]
143 #[case('Z', true)]
144 #[case('0', true)]
145 #[case('3', true)]
146 #[case('9', true)]
147 #[case('-', true)]
148 #[case('_', false)]
149 #[case('.', false)]
150 fn GIVEN_char_WHEN_is_ldh_THEN_correct_result(#[case] test_char: char, #[case] expected: bool) {
151 let actual = test_char.is_ldh();
155
156 assert_eq!(actual, expected);
158 }
159
160 #[rstest]
161 #[case("foo", true)]
162 #[case("", false)]
163 #[case("foo-bar", true)]
164 #[case("foo bar", false)]
165 fn GIVEN_string_WHEN_is_ldh_string_THEN_correct_result(
166 #[case] test_string: &str,
167 #[case] expected: bool,
168 ) {
169 let actual = test_string.is_ldh_string();
173
174 assert_eq!(actual, expected);
176 }
177
178 #[rstest]
179 #[case("foo", false)]
180 #[case("", false)]
181 #[case("foo-bar", false)]
182 #[case("foo bar", false)]
183 #[case(".", false)]
184 #[case(".foo.bar", false)]
185 #[case(".foo", true)]
186 fn GIVEN_string_WHEN_is_tld_THEN_correct_result(
187 #[case] test_string: &str,
188 #[case] expected: bool,
189 ) {
190 let actual = test_string.is_tld();
194
195 assert_eq!(actual, expected);
197 }
198
199 #[rstest]
200 #[case(&[], false)]
201 #[case(&["foo"], true)]
202 #[case(&["foo",""], false)]
203 #[case(&["foo","bar"], true)]
204 #[case(&["foo","bar baz"], false)]
205 #[case(&[""], false)]
206 #[case(&[" "], false)]
207 fn GIVEN_string_list_WHEN_is_ldh_string_list_THEN_correct_result(
208 #[case] test_list: &[&str],
209 #[case] expected: bool,
210 ) {
211 let actual = test_list.is_ldh_string_list();
215
216 assert_eq!(actual, expected);
218 }
219
220 #[rstest]
221 #[case("foo", true)]
222 #[case("", false)]
223 #[case(".", true)]
224 #[case("foo.bar", true)]
225 #[case("foo.bar.", true)]
226 fn GIVEN_string_WHEN_is_ldh_domain_name_THEN_correct_result(
227 #[case] test_string: &str,
228 #[case] expected: bool,
229 ) {
230 let actual = test_string.is_ldh_domain_name();
234
235 assert_eq!(actual, expected);
237 }
238
239 #[rstest]
240 #[case("foo", true)]
241 #[case("", false)]
242 #[case(".", true)]
243 #[case("foo.bar", true)]
244 #[case("foo.bar.", true)]
245 #[case("fo_o.bar.", false)]
246 #[case("fo o.bar.", false)]
247 fn GIVEN_string_WHEN_is_unicode_domain_name_THEN_correct_result(
248 #[case] test_string: &str,
249 #[case] expected: bool,
250 ) {
251 let actual = test_string.is_unicode_domain_name();
255
256 assert_eq!(actual, expected);
258 }
259}