1use std::cmp::Ordering;
19use std::str;
20use std::str::FromStr;
21
22#[derive(Debug)]
23struct Language {
24 name: String,
25 quality: f32,
26}
27
28impl Eq for Language {}
29
30impl Ord for Language {
31 fn cmp(&self, other: &Language) -> Ordering {
32 if self.quality > other.quality {
33 Ordering::Less
34 } else if self.quality < other.quality {
35 Ordering::Greater
36 } else {
37 Ordering::Equal
38 }
39 }
40}
41
42impl PartialOrd for Language {
43 fn partial_cmp(&self, other: &Language) -> Option<Ordering> {
44 Some(self.cmp(other))
45 }
46}
47
48impl PartialEq for Language {
49 fn eq(&self, other: &Language) -> bool {
50 self.quality == other.quality && self.name.to_lowercase() == other.name.to_lowercase()
51 }
52}
53
54impl Language {
55 fn new(tag: &str) -> Language {
56 let tag_parts: Vec<&str> = tag.split(';').collect();
57 let name = tag_parts[0].to_string();
58 let quality = match tag_parts.len() {
59 1 => 1.0,
60 _ => Language::quality_with_default(tag_parts[1]),
61 };
62 Language { name, quality }
63 }
64
65 fn quality_with_default(raw_quality: &str) -> f32 {
66 let quality_parts: Vec<&str> = raw_quality.split('=').collect();
67 match quality_parts.len() {
68 2 => f32::from_str(quality_parts[1]).unwrap_or(0.0),
69 _ => 0.0,
70 }
71 }
72}
73
74pub fn parse(raw_languages: &str) -> Vec<String> {
85 let stripped_languages = raw_languages.to_owned().replace(' ', "");
86 let language_strings: Vec<&str> = stripped_languages.split(',').collect();
87 let mut languages: Vec<Language> = language_strings.iter().map(|l| Language::new(l)).collect();
88 languages.sort();
89 languages
90 .iter()
91 .map(|l| l.name.to_owned())
92 .filter(|l| !l.is_empty())
93 .collect()
94}
95
96pub fn parse_with_quality(raw_languages: &str) -> Vec<(String, f32)> {
109 let stripped_languages = raw_languages.to_owned().replace(' ', "");
110 let language_strings: Vec<&str> = stripped_languages.split(',').collect();
111 let mut languages: Vec<Language> = language_strings.iter().map(|l| Language::new(l)).collect();
112 languages.sort();
113 languages
114 .iter()
115 .map(|l| (l.name.to_owned(), l.quality))
116 .filter(|l| !l.0.is_empty())
117 .collect()
118}
119
120pub fn intersection(raw_languages: &str, supported_languages: &[&str]) -> Vec<String> {
131 let user_languages = parse(raw_languages);
132 user_languages
133 .into_iter()
134 .filter(|l| supported_languages.contains(&l.as_str()))
135 .collect()
136}
137pub fn intersection_ordered(raw_languages: &str, supported_languages: &[&str]) -> Vec<String> {
149 let user_languages = parse(raw_languages);
150 user_languages
151 .into_iter()
152 .filter(|l| supported_languages.binary_search(&l.as_str()).is_ok())
153 .collect()
154}
155pub fn intersection_with_quality(
169 raw_languages: &str,
170 supported_languages: &[&str],
171) -> Vec<(String, f32)> {
172 let user_languages = parse_with_quality(raw_languages);
173 user_languages
174 .into_iter()
175 .filter(|l| supported_languages.contains(&l.0.as_str()))
176 .collect()
177}
178
179pub fn intersection_ordered_with_quality(
192 raw_languages: &str,
193 supported_languages: &[&str],
194) -> Vec<(String, f32)> {
195 let user_languages = parse_with_quality(raw_languages);
196 user_languages
197 .into_iter()
198 .filter(|l| supported_languages.binary_search(&l.0.as_str()).is_ok())
199 .collect()
200}
201
202#[cfg(test)]
203mod tests {
204 use super::{
205 intersection, intersection_ordered, intersection_ordered_with_quality,
206 intersection_with_quality, parse, Language,
207 };
208
209 static MOCK_ACCEPT_LANGUAGE: &str = "en-US, de;q=0.7, zh-Hant, jp;q=0.1";
210 static AVIALABLE_LANGUAGES: &[&str] =
211 &["da", "de", "en-US", "it", "jp", "zh", "zh-Hans", "zh-Hant"];
212
213 #[test]
214 fn it_creates_a_new_language_from_a_string() {
215 let language = Language::new("en-US;q=0.7");
216 assert_eq!(
217 language,
218 Language {
219 name: String::from("en-US"),
220 quality: 0.7,
221 }
222 )
223 }
224
225 #[test]
226 fn it_creates_a_new_language_from_a_string_with_lowercase_country() {
227 let language = Language::new("en-us;q=0.7");
228 assert_eq!(
229 language,
230 Language {
231 name: String::from("en-US"),
232 quality: 0.7,
233 }
234 )
235 }
236
237 #[test]
238 fn it_creates_a_new_language_from_a_string_with_a_default_quality() {
239 let language = Language::new("en-US");
240 assert_eq!(
241 language,
242 Language {
243 name: String::from("en-US"),
244 quality: 1.0,
245 }
246 )
247 }
248
249 #[test]
250 fn it_parses_quality() {
251 let quality = Language::quality_with_default("q=0.5");
252 assert_eq!(quality, 0.5)
253 }
254
255 #[test]
256 fn it_parses_an_invalid_quality() {
257 let quality = Language::quality_with_default("q=yolo");
258 assert_eq!(quality, 0.0)
259 }
260
261 #[test]
262 fn it_parses_a_valid_accept_language_header() {
263 let user_languages = parse(MOCK_ACCEPT_LANGUAGE);
264 assert_eq!(
265 user_languages,
266 vec![
267 String::from("en-US"),
268 String::from("zh-Hant"),
269 String::from("de"),
270 String::from("jp"),
271 ]
272 )
273 }
274
275 #[test]
276 fn it_parses_an_empty_accept_language_header() {
277 let user_languages = parse("");
278 assert_eq!(user_languages.len(), 0)
279 }
280
281 #[test]
282 fn it_parses_an_invalid_accept_language_header() {
283 let user_languages_one = parse("q");
284 let user_languages_two = parse(";q");
285 let user_languages_three = parse("q-");
286 let user_languages_four = parse("en;q=");
287 assert_eq!(user_languages_one, vec![String::from("q")]);
288 assert_eq!(user_languages_two.len(), 0);
289 assert_eq!(user_languages_three, vec![String::from("q-")]);
290 assert_eq!(user_languages_four, vec![String::from("en")])
291 }
292
293 #[test]
294 fn it_sorts_languages_by_quality() {
295 let user_languages = parse("en-US, de;q=0.1, jp;q=0.7");
296 assert_eq!(
297 user_languages,
298 vec![
299 String::from("en-US"),
300 String::from("jp"),
301 String::from("de"),
302 ]
303 )
304 }
305
306 #[test]
307 fn it_returns_language_intersection() {
308 let common_languages = intersection(MOCK_ACCEPT_LANGUAGE, AVIALABLE_LANGUAGES);
309 assert_eq!(
310 common_languages,
311 vec![
312 String::from("en-US"),
313 String::from("zh-Hant"),
314 String::from("de"),
315 String::from("jp")
316 ]
317 )
318 }
319
320 #[test]
321 fn it_returns_language_intersection_ordered() {
322 let common_languages = intersection_ordered(MOCK_ACCEPT_LANGUAGE, AVIALABLE_LANGUAGES);
323 assert_eq!(
324 common_languages,
325 vec![
326 String::from("en-US"),
327 String::from("zh-Hant"),
328 String::from("de"),
329 String::from("jp")
330 ]
331 )
332 }
333
334 #[test]
335 fn it_returns_language_intersection_with_quality() {
336 let common_languages = intersection_with_quality(MOCK_ACCEPT_LANGUAGE, &["en-US", "jp"]);
337 assert_eq!(
338 common_languages,
339 vec![(String::from("en-US"), 1.0), (String::from("jp"), 0.1)]
340 )
341 }
342
343 #[test]
344 fn it_returns_language_intersection_ordered_with_quality() {
345 let common_languages =
346 intersection_ordered_with_quality(MOCK_ACCEPT_LANGUAGE, &["en-US", "jp"]);
347 assert_eq!(
348 common_languages,
349 vec![(String::from("en-US"), 1.0), (String::from("jp"), 0.1)]
350 )
351 }
352
353 #[test]
354 fn it_returns_an_empty_array_when_no_intersection() {
355 let common_languages = intersection(MOCK_ACCEPT_LANGUAGE, &["fr", "en-GB"]);
356 assert_eq!(common_languages.len(), 0)
357 }
358
359 #[test]
360 fn it_parses_traditional_chinese() {
361 assert_eq!(parse("zh-Hant"), &["zh-Hant"]);
362 }
363
364 #[test]
365 fn it_implements_case_insensitive_equality() {
366 assert_eq!(Language::new("en-US"), Language::new("en-us"));
367 assert_eq!(Language::new("en-US;q=0.7"), Language::new("en-us;q=0.7"));
368 assert_ne!(Language::new("en"), Language::new("en-US"));
369 assert_ne!(Language::new("en;q=0.7"), Language::new("en;q=0.8"));
370 assert_ne!(Language::new("en;q=0.7"), Language::new("en-US;q=0.7"));
371 }
372}