1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct LanguageCode {
9 value: String,
10}
11
12impl LanguageCode {
13 #[must_use]
15 pub fn new(input: &str) -> Option<Self> {
16 parse_language_code(input)
17 }
18
19 #[must_use]
21 pub fn as_str(&self) -> &str {
22 &self.value
23 }
24
25 #[must_use]
27 pub fn into_string(self) -> String {
28 self.value
29 }
30}
31
32impl AsRef<str> for LanguageCode {
33 fn as_ref(&self) -> &str {
34 self.as_str()
35 }
36}
37
38impl fmt::Display for LanguageCode {
39 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40 formatter.write_str(self.as_str())
41 }
42}
43
44#[must_use]
46pub fn parse_language_code(input: &str) -> Option<LanguageCode> {
47 normalize_language_code(input).map(|value| LanguageCode { value })
48}
49
50#[must_use]
52pub fn is_language_code(input: &str) -> bool {
53 normalize_language_code(input).is_some()
54}
55
56#[must_use]
58pub fn normalize_language_code(input: &str) -> Option<String> {
59 let trimmed = input.trim();
60 if !matches!(trimmed.len(), 2 | 3) || !trimmed.bytes().all(|byte| byte.is_ascii_alphabetic()) {
61 return None;
62 }
63
64 Some(trimmed.to_ascii_lowercase())
65}
66
67#[cfg(test)]
68mod tests {
69 use super::{LanguageCode, is_language_code, normalize_language_code, parse_language_code};
70
71 #[test]
72 fn accepts_common_language_examples() {
73 for language in ["en", "es", "fr", "de", "zh", "ar", "ja"] {
74 assert!(is_language_code(language));
75 assert_eq!(parse_language_code(language).unwrap().as_str(), language);
76 }
77 }
78
79 #[test]
80 fn normalizes_to_lowercase() {
81 assert_eq!(normalize_language_code("EN"), Some("en".to_string()));
82 assert_eq!(LanguageCode::new(" ZHO ").unwrap().as_str(), "zho");
83 }
84
85 #[test]
86 fn rejects_invalid_language_shapes() {
87 for language in ["", "e", "engb", "en-US", "e1", "en_", "中文"] {
88 assert!(!is_language_code(language));
89 assert!(parse_language_code(language).is_none());
90 }
91 }
92}