locale_strict/
string.rs

1/*!
2The `StrictLocaleString` type provides a `LocaleIdentifier` that validates
3that language, territory, and code set identifiers are present in the
4corresponding standards.
5
6*/
7use std::collections::HashMap;
8use std::fmt;
9use std::fmt::Display;
10use std::str::FromStr;
11
12use locale_codes::{codeset, country, language};
13use locale_types::string::ParseError;
14use locale_types::{LocaleError, LocaleIdentifier, LocaleResult, LocaleString};
15
16// ------------------------------------------------------------------------------------------------
17// Public Types
18// ------------------------------------------------------------------------------------------------
19
20/// A `StringLocaleString` is a wrapper around `LocaleString`.
21#[derive(Debug, PartialEq)]
22pub struct StrictLocaleString(LocaleString);
23
24// ------------------------------------------------------------------------------------------------
25// Implementations - LocaleString
26// ------------------------------------------------------------------------------------------------
27
28impl LocaleIdentifier for StrictLocaleString {
29    fn new(language_code: String) -> LocaleResult<Self> {
30        match language::lookup(&language_code) {
31            None => Err(LocaleError::InvalidLanguageCode),
32            Some(_) => Ok(StrictLocaleString(LocaleString::new(language_code)?)),
33        }
34    }
35
36    fn with_language(&self, language_code: String) -> LocaleResult<Self> {
37        match language::lookup(&language_code) {
38            None => Err(LocaleError::InvalidLanguageCode),
39            Some(_) => Ok(StrictLocaleString(self.0.with_language(language_code)?)),
40        }
41    }
42
43    fn with_territory(&self, territory: String) -> LocaleResult<Self> {
44        match country::lookup(&territory) {
45            None => Err(LocaleError::InvalidTerritoryCode),
46            Some(_) => Ok(StrictLocaleString(self.0.with_territory(territory)?)),
47        }
48    }
49
50    fn with_code_set(&self, code_set: String) -> LocaleResult<Self> {
51        match codeset::lookup(&code_set) {
52            None => Err(LocaleError::InvalidCodeSet),
53            Some(_) => Ok(StrictLocaleString(self.0.with_code_set(code_set)?)),
54        }
55    }
56
57    fn with_modifier(&self, modifier: String) -> LocaleResult<Self> {
58        Ok(StrictLocaleString(self.0.with_modifier(modifier)?))
59    }
60
61    fn with_modifiers<K, V>(&self, modifiers: HashMap<K, V>) -> LocaleResult<Self>
62    where
63        K: Display,
64        V: Display,
65    {
66        Ok(StrictLocaleString(self.0.with_modifiers(modifiers)?))
67    }
68
69    fn language_code(&self) -> String {
70        self.0.language_code()
71    }
72
73    fn territory(&self) -> Option<String> {
74        self.0.territory()
75    }
76
77    fn code_set(&self) -> Option<String> {
78        self.0.code_set()
79    }
80
81    fn modifier(&self) -> Option<String> {
82        self.0.modifier()
83    }
84}
85
86impl Display for StrictLocaleString {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(f, "{}", self.0)
89    }
90}
91
92impl FromStr for StrictLocaleString {
93    type Err = ParseError;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        match LocaleString::from_str(s) {
97            Err(e) => Err(e),
98            Ok(locale) => {
99                let mut strict = StrictLocaleString::new(locale.language_code()).unwrap();
100                if let Some(territory) = locale.territory() {
101                    strict = strict.with_territory(territory).unwrap();
102                }
103                if let Some(code_set) = locale.code_set() {
104                    strict = strict.with_code_set(code_set).unwrap();
105                }
106                if let Some(modifier) = locale.modifier() {
107                    strict = strict.with_modifier(modifier).unwrap();
108                }
109                Ok(strict)
110            }
111        }
112    }
113}
114
115// ------------------------------------------------------------------------------------------------
116// Unit Tests
117// ------------------------------------------------------------------------------------------------
118
119#[cfg(test)]
120mod tests {
121    use std::str::FromStr;
122
123    use crate::StrictLocaleString;
124    use locale_types::{LocaleError, LocaleIdentifier};
125
126    // --------------------------------------------------------------------------------------------
127    #[test]
128    fn test_unknown_language() {
129        assert_eq!(
130            StrictLocaleString::new("xx".to_string()),
131            Err(LocaleError::InvalidLanguageCode)
132        );
133    }
134
135    #[test]
136    fn test_unknown_territory() {
137        assert_eq!(
138            StrictLocaleString::new("en".to_string())
139                .unwrap()
140                .with_territory("XX".to_string()),
141            Err(LocaleError::InvalidTerritoryCode)
142        );
143    }
144
145    #[test]
146    fn test_unknown_code_set() {
147        assert_eq!(
148            StrictLocaleString::new("en".to_string())
149                .unwrap()
150                .with_code_set("UNKNOWN".to_string()),
151            Err(LocaleError::InvalidCodeSet)
152        );
153    }
154
155    // --------------------------------------------------------------------------------------------
156    #[test]
157    fn test_constructor() {
158        let locale = StrictLocaleString::new("en".to_string()).unwrap();
159        assert_eq!(locale.language_code(), "en".to_string());
160        assert_eq!(locale.territory(), None);
161        assert_eq!(locale.modifier(), None);
162    }
163
164    #[test]
165    fn test_with_language() {
166        let locale = StrictLocaleString::new("en".to_string()).unwrap();
167        assert_eq!(
168            locale
169                .with_language("fr".to_string())
170                .unwrap()
171                .language_code(),
172            "fr".to_string()
173        );
174    }
175
176    #[test]
177    fn test_with_territory() {
178        let locale = StrictLocaleString::new("en".to_string()).unwrap();
179        assert_eq!(
180            locale.with_territory("GB".to_string()).unwrap().territory(),
181            Some("GB".to_string())
182        );
183    }
184
185    #[test]
186    fn test_with_code_set() {
187        let locale = StrictLocaleString::new("en".to_string()).unwrap();
188        assert_eq!(
189            locale
190                .with_code_set("UTF-8".to_string())
191                .unwrap()
192                .code_set(),
193            Some("UTF-8".to_string())
194        );
195    }
196
197    // --------------------------------------------------------------------------------------------
198    #[test]
199    fn test_to_string() {
200        let locale = StrictLocaleString::new("en".to_string())
201            .unwrap()
202            .with_territory("US".to_string())
203            .unwrap()
204            .with_code_set("UTF-8".to_string())
205            .unwrap()
206            .with_modifier("collation=pinyin;currency=CNY".to_string())
207            .unwrap();
208        assert_eq!(
209            locale.to_string(),
210            "en_US.UTF-8@collation=pinyin;currency=CNY".to_string()
211        );
212    }
213
214    // --------------------------------------------------------------------------------------------
215    #[test]
216    fn test_from_str_1() {
217        match StrictLocaleString::from_str("en") {
218            Ok(locale) => assert_eq!(locale.language_code(), "en"),
219            _ => panic!("LocaleString::from_str failure"),
220        }
221    }
222
223    #[test]
224    fn test_from_str_2() {
225        match StrictLocaleString::from_str("en_US") {
226            Ok(locale) => {
227                assert_eq!(locale.language_code(), "en");
228                assert_eq!(locale.territory(), Some("US".to_string()));
229            }
230            _ => panic!("LocaleString::from_str failure"),
231        }
232    }
233
234    #[test]
235    fn test_from_str_3() {
236        match StrictLocaleString::from_str("en_US.UTF-8") {
237            Ok(locale) => {
238                assert_eq!(locale.language_code(), "en");
239                assert_eq!(locale.territory(), Some("US".to_string()));
240                assert_eq!(locale.code_set(), Some("UTF-8".to_string()));
241            }
242            _ => panic!("LocaleString::from_str failure"),
243        }
244    }
245
246    #[test]
247    fn test_from_str_4() {
248        match StrictLocaleString::from_str("en_US.UTF-8@Latn") {
249            Ok(locale) => {
250                assert_eq!(locale.language_code(), "en");
251                assert_eq!(locale.territory(), Some("US".to_string()));
252                assert_eq!(locale.code_set(), Some("UTF-8".to_string()));
253                assert_eq!(locale.modifier(), Some("Latn".to_string()));
254            }
255            _ => panic!("LocaleString::from_str failure"),
256        }
257    }
258}