1use unicode_normalization::char::is_combining_mark;
2use unicode_normalization::UnicodeNormalization;
3
4pub fn normalize(s: &str) -> String {
22 s.trim()
23 .to_lowercase()
24 .nfd()
25 .filter(|c| !is_combining_mark(*c))
26 .nfc()
27 .collect()
28}
29
30#[cfg(test)]
31mod test {
32 use super::*;
33
34 #[test]
35 fn test_lowercase() {
36 assert_eq!(normalize("Hello"), "hello");
37 }
38
39 #[test]
40 fn test_trim() {
41 assert_eq!(normalize(" hello "), "hello");
42 }
43
44 #[test]
45 fn test_both() {
46 assert_eq!(normalize(" Hello World "), "hello world")
47 }
48
49 #[test]
50 fn test_strips_acute_accent() {
51 assert_eq!(normalize("café"), "cafe");
52 }
53
54 #[test]
55 fn test_strips_tilde() {
56 assert_eq!(normalize("Ñoño"), "nono");
57 }
58
59 #[test]
60 fn test_strips_umlaut() {
61 assert_eq!(normalize("Über"), "uber");
62 assert_eq!(normalize("naïve"), "naive");
63 }
64
65 #[test]
66 fn test_strips_multiple_diacritics() {
67 assert_eq!(normalize("Crème Brûlée"), "creme brulee");
68 }
69
70 #[test]
71 fn test_no_diacritics_unchanged() {
72 assert_eq!(normalize("hello world"), "hello world");
73 }
74
75 #[test]
76 fn test_precomposed_and_decomposed_match() {
77 let precomposed = "caf\u{00E9}";
80 let decomposed = "cafe\u{0301}";
81 assert_eq!(normalize(precomposed), normalize(decomposed));
82 assert_eq!(normalize(precomposed), "cafe");
83 }
84}