1use std::borrow::Cow;
2
3pub fn normalize_domain(s: &str) -> Cow<'_, str> {
8 let trimmed = s.strip_suffix('.').unwrap_or(s);
9 if trimmed.bytes().any(|b| b.is_ascii_uppercase()) {
10 Cow::Owned(trimmed.to_ascii_lowercase())
11 } else {
12 Cow::Borrowed(trimmed)
13 }
14}
15
16pub fn domain_str(s: &str) -> Option<&str> {
20 psl::domain_str(s)
21}
22
23pub fn suffix_str(s: &str) -> Option<&str> {
27 psl::suffix_str(s)
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn already_normalized_borrows() {
36 let s = "example.com";
37 let n = normalize_domain(s);
38 assert!(matches!(n, Cow::Borrowed(_)));
39 assert_eq!(n, "example.com");
40 }
41
42 #[test]
43 fn trims_trailing_dot_without_alloc() {
44 let n = normalize_domain("example.com.");
45 assert!(matches!(n, Cow::Borrowed(_)));
46 assert_eq!(n, "example.com");
47 }
48
49 #[test]
50 fn lowercases_when_needed() {
51 let n = normalize_domain("Example.COM");
52 assert!(matches!(n, Cow::Owned(_)));
53 assert_eq!(n, "example.com");
54 }
55
56 #[test]
57 fn trailing_dot_and_uppercase() {
58 let n = normalize_domain("Example.COM.");
59 assert_eq!(n, "example.com");
60 }
61
62 #[test]
63 fn empty() {
64 assert_eq!(normalize_domain(""), "");
65 }
66
67 #[test]
68 fn domain_str_after_normalize() {
69 let n = normalize_domain("foo.Example.COM.");
70 assert_eq!(domain_str(&n), Some("example.com"));
71 }
72}