1use {
16 anyhow::anyhow,
17 rust_icu_common as common,
18 rust_icu_common::buffered_string_method_with_retry,
19 rust_icu_sys as sys,
20 rust_icu_sys::versioned_function,
21 rust_icu_sys::*,
22 rust_icu_uenum::Enumeration,
23 rust_icu_ustring as ustring,
24 rust_icu_ustring::buffered_uchar_method_with_retry,
25 std::{
26 cmp::Ordering,
27 collections::HashMap,
28 convert::{From, TryFrom, TryInto},
29 ffi, fmt,
30 os::raw,
31 },
32};
33
34const LOCALE_CAPACITY: usize = 158;
37
38#[derive(Debug, Clone)]
48pub struct ULocMut {
49 base: ULoc,
50 unicode_keyvalues: HashMap<String, String>,
51 other_keyvalues: HashMap<String, String>,
52}
53
54impl From<ULoc> for ULocMut {
55 fn from(l: ULoc) -> Self {
57 let all_keywords = l.keywords();
58 let mut unicode_keyvalues: HashMap<String, String> = HashMap::new();
59 let mut other_keyvalues: HashMap<String, String> = HashMap::new();
60 for kw in all_keywords {
61 let ukw = to_unicode_locale_key(&kw);
64 match ukw {
65 None => {
66 let v = l.keyword_value(&kw).unwrap().unwrap();
67 other_keyvalues.insert(kw, v);
68 }
69 Some(u) => {
70 let v = l.unicode_keyword_value(&u).unwrap().unwrap();
71 unicode_keyvalues.insert(u, v);
72 }
73 }
74 }
75 let locmut = ULocMut {
77 base: l.base_name(),
78 unicode_keyvalues,
79 other_keyvalues,
80 };
81 locmut
82 }
83}
84
85impl From<ULocMut> for ULoc {
86 fn from(lm: ULocMut) -> Self {
88 let mut unicode_extensions_vec = lm
90 .unicode_keyvalues
91 .iter()
92 .map(|(k, v)| format!("{}-{}", k, v))
93 .collect::<Vec<String>>();
94 unicode_extensions_vec.sort();
95 let unicode_extensions: String = unicode_extensions_vec
96 .join("-");
97 let unicode_extension: String = if unicode_extensions.len() > 0 {
98 vec!["u-".to_string(), unicode_extensions]
99 .into_iter()
100 .collect()
101 } else {
102 "".to_string()
103 };
104 let mut all_extensions: Vec<String> = lm
106 .other_keyvalues
107 .iter()
108 .map(|(k, v)| format!("{}-{}", k, v))
109 .collect();
110 if unicode_extension.len() > 0 {
111 all_extensions.push(unicode_extension);
112 }
113 let base_tag = lm.base.to_language_tag(true)
116 .expect("should be known-good");
117 let mut everything_vec: Vec<String> = vec![base_tag];
118 if !all_extensions.is_empty() {
119 all_extensions.sort();
120 let extension_string = all_extensions.join("-");
121 everything_vec.push(extension_string);
122 }
123 let everything = everything_vec.join("-").to_lowercase();
124 ULoc::for_language_tag(&everything).unwrap()
125 }
126}
127
128impl ULocMut {
129 pub fn set_unicode_keyvalue(&mut self, key: &str, value: &str) -> Option<String> {
132 if let None = to_unicode_locale_key(key) {
133 return None;
134 }
135 self.unicode_keyvalues
136 .insert(key.to_string(), value.to_string())
137 }
138
139 pub fn remove_unicode_keyvalue(&mut self, key: &str) -> Option<String> {
143 if let None = to_unicode_locale_key(key) {
144 return None;
145 }
146 self.unicode_keyvalues.remove(key)
147 }
148}
149
150#[derive(Debug, Clone, Eq, PartialEq, Hash)]
157pub struct ULoc {
158 repr: String,
160}
161
162impl fmt::Display for ULoc {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 write!(f, "{}", self.repr)
168 }
169}
170
171impl TryFrom<&str> for ULoc {
172 type Error = common::Error;
173 fn try_from(s: &str) -> Result<Self, Self::Error> {
177 let s = String::from(s);
178 ULoc { repr: s }.canonicalize()
179 }
180}
181
182impl TryFrom<&ffi::CStr> for ULoc {
183 type Error = common::Error;
184
185 fn try_from(s: &ffi::CStr) -> Result<Self, Self::Error> {
187 let repr = s.to_str()?;
188 ULoc {
189 repr: String::from(repr),
190 }
191 .canonicalize()
192 }
193}
194
195impl ULoc {
196 pub fn language(&self) -> Option<String> {
198 self.call_buffered_string_method_to_option(versioned_function!(uloc_getLanguage))
199 }
200
201 pub fn script(&self) -> Option<String> {
203 self.call_buffered_string_method_to_option(versioned_function!(uloc_getScript))
204 }
205
206 pub fn country(&self) -> Option<String> {
208 self.call_buffered_string_method_to_option(versioned_function!(uloc_getCountry))
209 }
210
211 pub fn variant(&self) -> Option<String> {
213 self.call_buffered_string_method_to_option(versioned_function!(uloc_getVariant))
214 }
215
216 pub fn name(&self) -> Option<String> {
218 self.call_buffered_string_method_to_option(versioned_function!(uloc_getName))
219 }
220
221 pub fn canonicalize(&self) -> Result<ULoc, common::Error> {
223 self.call_buffered_string_method(versioned_function!(uloc_canonicalize))
224 .map(|repr| ULoc { repr })
225 }
226
227 pub fn iso3_language(&self) -> Option<String> {
229 let lang = unsafe {
230 ffi::CStr::from_ptr(
231 versioned_function!(uloc_getISO3Language)(self.as_c_str().as_ptr())
232 ).to_str()
233 };
234 let value = lang.unwrap();
235 if value.is_empty() {
236 None
237 } else {
238 Some(value.to_string())
239 }
240 }
241
242 pub fn iso3_country(&self) -> Option<String> {
244 let country = unsafe {
245 ffi::CStr::from_ptr(
246 versioned_function!(uloc_getISO3Country)(self.as_c_str().as_ptr())
247 ).to_str()
248 };
249 let value = country.unwrap();
250 if value.is_empty() {
251 None
252 } else {
253 Some(value.to_string())
254 }
255 }
256
257 pub fn display_language(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
259 buffered_uchar_method_with_retry!(
260 display_language_impl,
261 LOCALE_CAPACITY,
262 [locale: *const raw::c_char, display_locale: *const raw::c_char,],
263 []
264 );
265 display_language_impl(
266 versioned_function!(uloc_getDisplayLanguage),
267 self.as_c_str().as_ptr(),
268 display_locale.as_c_str().as_ptr(),
269 )
270 }
271
272 pub fn display_script(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
274 buffered_uchar_method_with_retry!(
275 display_script_impl,
276 LOCALE_CAPACITY,
277 [locale: *const raw::c_char, display_locale: *const raw::c_char,],
278 []
279 );
280 display_script_impl(
281 versioned_function!(uloc_getDisplayScript),
282 self.as_c_str().as_ptr(),
283 display_locale.as_c_str().as_ptr(),
284 )
285 }
286
287 pub fn display_country(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
289 buffered_uchar_method_with_retry!(
290 display_country_impl,
291 LOCALE_CAPACITY,
292 [locale: *const raw::c_char, display_locale: *const raw::c_char,],
293 []
294 );
295 display_country_impl(
296 versioned_function!(uloc_getDisplayCountry),
297 self.as_c_str().as_ptr(),
298 display_locale.as_c_str().as_ptr(),
299 )
300 }
301
302 pub fn display_variant(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
304 buffered_uchar_method_with_retry!(
305 display_variant_impl,
306 LOCALE_CAPACITY,
307 [locale: *const raw::c_char, display_locale: *const raw::c_char,],
308 []
309 );
310 display_variant_impl(
311 versioned_function!(uloc_getDisplayCountry),
312 self.as_c_str().as_ptr(),
313 display_locale.as_c_str().as_ptr(),
314 )
315 }
316
317 pub fn display_keyword(keyword: &String, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
319 buffered_uchar_method_with_retry!(
320 display_keyword_impl,
321 LOCALE_CAPACITY,
322 [keyword: *const raw::c_char, display_locale: *const raw::c_char,],
323 []
324 );
325 let keyword = ffi::CString::new(keyword.as_str()).unwrap();
326 display_keyword_impl(
327 versioned_function!(uloc_getDisplayKeyword),
328 keyword.as_ptr(),
329 display_locale.as_c_str().as_ptr(),
330 )
331 }
332
333 pub fn display_keyword_value(&self, keyword: &String, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
335 buffered_uchar_method_with_retry!(
336 display_keyword_value_impl,
337 LOCALE_CAPACITY,
338 [keyword: *const raw::c_char, value: *const raw::c_char, display_locale: *const raw::c_char,],
339 []
340 );
341 let keyword = ffi::CString::new(keyword.as_str()).unwrap();
342 display_keyword_value_impl(
343 versioned_function!(uloc_getDisplayKeywordValue),
344 self.as_c_str().as_ptr(),
345 keyword.as_ptr(),
346 display_locale.as_c_str().as_ptr(),
347 )
348 }
349
350 pub fn display_name(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
352 buffered_uchar_method_with_retry!(
353 display_name_impl,
354 LOCALE_CAPACITY,
355 [locale: *const raw::c_char, display_locale: *const raw::c_char,],
356 []
357 );
358 display_name_impl(
359 versioned_function!(uloc_getDisplayName),
360 self.as_c_str().as_ptr(),
361 display_locale.as_c_str().as_ptr(),
362 )
363 }
364
365 pub fn count_available() -> i32 {
367 let count = unsafe { versioned_function!(uloc_countAvailable)() };
368 count
369 }
370
371 pub fn get_available(n: i32) -> Result<ULoc, common::Error> {
373 if (0 > n) || (n >= Self::count_available()) {
374 panic!("{} is negative or greater than or equal to the value returned from count_available()", n);
375 }
376 let ptr = unsafe { versioned_function!(uloc_getAvailable)(n) };
377 if ptr == std::ptr::null() {
378 return Err(common::Error::Wrapper(anyhow!("uloc_getAvailable() returned a null pointer")));
379 }
380 let label = unsafe { ffi::CStr::from_ptr(ptr).to_str()? };
381 ULoc::try_from(label)
382 }
383
384 pub fn get_available_locales() -> Vec<ULoc> {
386 let count = ULoc::count_available();
387 let mut vec = Vec::with_capacity(count as usize);
388 let mut index: i32 = 0;
389 while index < count {
390 let locale = Self::get_available(index).unwrap();
391 vec.push(locale);
392 index += 1;
393 }
394 vec
395 }
396
397 pub fn open_available_by_type(locale_type: ULocAvailableType) -> Result<Enumeration, common::Error> {
399 let mut status = common::Error::OK_CODE;
400 unsafe {
401 let iter = Enumeration::from_raw_parts(None, versioned_function!(uloc_openAvailableByType)(locale_type, &mut status));
402 common::Error::ok_or_warning(status)?;
403 Ok(iter)
404 }
405 }
406
407 pub fn get_available_locales_by_type(locale_type: ULocAvailableType) -> Vec<ULoc> {
409 if locale_type == ULocAvailableType::ULOC_AVAILABLE_COUNT {
410 panic!("ULOC_AVAILABLE_COUNT is for internal use only");
411 }
412 Self::open_available_by_type(locale_type).unwrap().map(|x| ULoc::try_from(x.unwrap().as_str()).unwrap()).collect()
413 }
414
415 pub fn add_likely_subtags(&self) -> Result<ULoc, common::Error> {
417 self.call_buffered_string_method(versioned_function!(uloc_addLikelySubtags))
418 .map(|repr| ULoc { repr })
419 }
420
421 pub fn minimize_subtags(&self) -> Result<ULoc, common::Error> {
423 self.call_buffered_string_method(versioned_function!(uloc_minimizeSubtags))
424 .map(|repr| ULoc { repr })
425 }
426
427 pub fn to_language_tag(&self, strict: bool) -> Result<String, common::Error> {
429 let locale_id = self.as_c_str();
430 let strict = if strict { 1 } else { 0 };
432 buffered_string_method_with_retry(
433 |buf, len, error| unsafe {
434 versioned_function!(uloc_toLanguageTag)(
435 locale_id.as_ptr(),
436 buf,
437 len,
438 strict,
439 error,
440 )
441 },
442 LOCALE_CAPACITY,
443 )
444 }
445
446 pub fn open_keywords(&self) -> Result<Enumeration, common::Error> {
447 let mut status = common::Error::OK_CODE;
448 let asciiz_locale = self.as_c_str();
449 let raw_enum = unsafe {
450 assert!(common::Error::is_ok(status));
451 versioned_function!(uloc_openKeywords)(asciiz_locale.as_ptr(), &mut status)
452 };
453 common::Error::ok_or_warning(status)?;
454 if raw_enum.is_null() {
456 Ok(Enumeration::empty())
457 } else {
458 Ok(unsafe { Enumeration::from_raw_parts(None, raw_enum) })
459 }
460 }
461
462 pub fn keywords(&self) -> impl Iterator<Item = String> {
464 self.open_keywords()
465 .unwrap()
466 .map(|result| result.unwrap())
467 }
468
469 pub fn unicode_keywords(&self) -> impl Iterator<Item = String> {
471 self.keywords().filter_map(|s| to_unicode_locale_key(&s))
472 }
473
474 pub fn keyword_value(&self, keyword: &str) -> Result<Option<String>, common::Error> {
476 let locale_id = self.as_c_str();
477 let keyword_name = str_to_cstring(keyword);
478 buffered_string_method_with_retry(
479 |buf, len, error| unsafe {
480 versioned_function!(uloc_getKeywordValue)(
481 locale_id.as_ptr(),
482 keyword_name.as_ptr(),
483 buf,
484 len,
485 error,
486 )
487 },
488 LOCALE_CAPACITY,
489 )
490 .map(|value| if value.is_empty() { None } else { Some(value) })
491 }
492
493 pub fn unicode_keyword_value(
495 &self,
496 unicode_keyword: &str,
497 ) -> Result<Option<String>, common::Error> {
498 let legacy_keyword = to_legacy_key(unicode_keyword);
499 match legacy_keyword {
500 Some(legacy_keyword) => match self.keyword_value(&legacy_keyword) {
501 Ok(Some(legacy_value)) => {
502 Ok(to_unicode_locale_type(&legacy_keyword, &legacy_value))
503 }
504 Ok(None) => Ok(None),
505 Err(e) => Err(e),
506 },
507 None => Ok(None),
508 }
509 }
510
511 pub fn label(&self) -> &str {
513 &self.repr
514 }
515
516 pub fn as_c_str(&self) -> ffi::CString {
518 ffi::CString::new(self.repr.clone()).expect("ULoc contained interior NUL bytes")
519 }
520
521 pub fn for_language_tag(tag: &str) -> Result<ULoc, common::Error> {
526 let tag = str_to_cstring(tag);
527 let locale_id = buffered_string_method_with_retry(
528 |buf, len, error| unsafe {
529 versioned_function!(uloc_forLanguageTag)(
530 tag.as_ptr(),
531 buf,
532 len,
533 std::ptr::null_mut(),
534 error
535 )
536 },
537 LOCALE_CAPACITY,
538 )?;
539 ULoc::try_from(&locale_id[..])
540 }
541
542 fn call_buffered_string_method(
544 &self,
545 uloc_method: unsafe extern "C" fn(
546 *const raw::c_char,
547 *mut raw::c_char,
548 i32,
549 *mut UErrorCode,
550 ) -> i32,
551 ) -> Result<String, common::Error> {
552 let asciiz = self.as_c_str();
553 buffered_string_method_with_retry(
554 |buf, len, error| unsafe { uloc_method(asciiz.as_ptr(), buf, len, error) },
555 LOCALE_CAPACITY,
556 )
557 }
558
559 fn call_buffered_string_method_to_option(
562 &self,
563 uloc_method: unsafe extern "C" fn(
564 *const raw::c_char,
565 *mut raw::c_char,
566 i32,
567 *mut UErrorCode,
568 ) -> i32,
569 ) -> Option<String> {
570 let value: String = self.call_buffered_string_method(uloc_method).unwrap();
571 if value.is_empty() {
572 None
573 } else {
574 Some(value)
575 }
576 }
577
578 pub fn base_name(self) -> Self {
580 let result = self
581 .call_buffered_string_method(versioned_function!(uloc_getBaseName))
582 .expect("should be able to produce a shorter locale");
583 ULoc::try_from(&result[..]).expect("should be able to convert to locale")
584 }
585}
586
587impl Ord for ULoc {
591 fn cmp(&self, other: &Self) -> Ordering {
592 fn compare_keywords(
595 this: &ULoc,
596 self_keyword: &Option<String>,
597 other: &ULoc,
598 other_keyword: &Option<String>,
599 ) -> Option<Ordering> {
600 match (self_keyword, other_keyword) {
601 (Some(self_keyword), Some(other_keyword)) => {
602 match self_keyword.cmp(&other_keyword) {
604 Ordering::Equal => {
605 let self_val = this.keyword_value(&self_keyword[..]).unwrap();
607 let other_val = other.keyword_value(&other_keyword[..]).unwrap();
608 Some(self_val.cmp(&other_val))
609 }
610 unequal_ordering => Some(unequal_ordering),
611 }
612 }
613 (Some(_), _) => Some(Ordering::Greater),
615 (_, Some(_)) => Some(Ordering::Less),
617 (_, _) => None,
619 }
620 }
621
622 self.language()
623 .cmp(&other.language())
624 .then_with(|| self.script().cmp(&other.script()))
625 .then_with(|| self.country().cmp(&other.country()))
626 .then_with(|| self.variant().cmp(&other.variant()))
627 .then_with(|| {
628 let mut self_keywords = self.keywords();
629 let mut other_keywords = other.keywords();
630
631 while let Some(keyword_ordering) =
632 compare_keywords(self, &self_keywords.next(), other, &other_keywords.next())
633 {
634 match keyword_ordering {
635 Ordering::Equal => {}
636 unequal_ordering => {
637 return unequal_ordering;
638 }
639 }
640 }
641
642 Ordering::Equal
644 })
645 }
646}
647
648impl PartialOrd for ULoc {
649 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
650 Some(self.cmp(other))
651 }
652}
653
654pub fn get_default() -> ULoc {
658 let loc = unsafe { versioned_function!(uloc_getDefault)() };
659 let uloc_cstr = unsafe { ffi::CStr::from_ptr(loc) };
660 crate::ULoc::try_from(uloc_cstr).expect("could not convert default locale to ULoc")
661}
662
663pub fn set_default(loc: &ULoc) -> Result<(), common::Error> {
667 let mut status = common::Error::OK_CODE;
668 let asciiz = str_to_cstring(&loc.repr);
669 unsafe { versioned_function!(uloc_setDefault)(asciiz.as_ptr(), &mut status) };
670 common::Error::ok_or_warning(status)
671}
672
673pub fn accept_language(
675 accept_list: impl IntoIterator<Item = impl Into<ULoc>>,
676 available_locales: impl IntoIterator<Item = impl Into<ULoc>>,
677) -> Result<(Option<ULoc>, UAcceptResult), common::Error> {
678 let mut accept_result: UAcceptResult = UAcceptResult::ULOC_ACCEPT_FAILED;
679 let mut accept_list_cstrings: Vec<ffi::CString> = vec![];
680 let mut accept_list: Vec<*const raw::c_char> = accept_list
682 .into_iter()
683 .map(|item| {
684 let uloc: ULoc = item.into();
685 accept_list_cstrings.push(uloc.as_c_str());
686 accept_list_cstrings
687 .last()
688 .expect("non-empty list")
689 .as_ptr()
690 })
691 .collect();
692
693 let available_locales: Vec<ULoc> = available_locales
694 .into_iter()
695 .map(|item| item.into())
696 .collect();
697 let available_locales: Vec<&str> = available_locales.iter().map(|uloc| uloc.label()).collect();
698 let mut available_locales = Enumeration::try_from(&available_locales[..])?;
699
700 let matched_locale = buffered_string_method_with_retry(
701 |buf, len, error| unsafe {
702 versioned_function!(uloc_acceptLanguage)(
703 buf,
704 len,
705 &mut accept_result,
706 accept_list.as_mut_ptr(),
707 accept_list.len() as i32,
708 available_locales.repr(),
709 error,
710 )
711 },
712 LOCALE_CAPACITY,
713 );
714
715 if accept_result == UAcceptResult::ULOC_ACCEPT_FAILED {
717 return Ok((None, accept_result));
718 }
719
720 matched_locale
721 .and_then(|s| ULoc::try_from(s.as_str()))
722 .map(|uloc| (Some(uloc), accept_result))
723}
724
725pub fn to_unicode_locale_key(legacy_keyword: &str) -> Option<String> {
727 let legacy_keyword = str_to_cstring(legacy_keyword);
728 let unicode_keyword: Option<ffi::CString> = unsafe {
729 let ptr = versioned_function!(uloc_toUnicodeLocaleKey)(legacy_keyword.as_ptr());
730 ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
731 };
732 unicode_keyword.map(|cstring| cstring_to_string(&cstring))
733}
734
735pub fn to_unicode_locale_type(legacy_keyword: &str, legacy_value: &str) -> Option<String> {
737 let legacy_keyword = str_to_cstring(legacy_keyword);
738 let legacy_value = str_to_cstring(legacy_value);
739 let unicode_value: Option<ffi::CString> = unsafe {
740 let ptr = versioned_function!(uloc_toUnicodeLocaleType)(
741 legacy_keyword.as_ptr(),
742 legacy_value.as_ptr(),
743 );
744 ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
745 };
746 unicode_value.map(|cstring| cstring_to_string(&cstring))
747}
748
749pub fn to_legacy_key(unicode_keyword: &str) -> Option<String> {
751 let unicode_keyword = str_to_cstring(unicode_keyword);
752 let legacy_keyword: Option<ffi::CString> = unsafe {
753 let ptr = versioned_function!(uloc_toLegacyKey)(unicode_keyword.as_ptr());
754 ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
755 };
756 legacy_keyword.map(|cstring| cstring_to_string(&cstring))
757}
758
759fn str_to_cstring(input: &str) -> ffi::CString {
762 ffi::CString::new(input)
763 .unwrap_or_else(|e| ffi::CString::new(&input[0..e.nul_position()]).unwrap())
764}
765
766fn cstring_to_string(input: &ffi::CString) -> String {
769 input.to_string_lossy().to_string()
770}
771
772#[cfg(test)]
773mod tests {
774 use {super::*, anyhow::Error};
775
776 #[test]
777 fn test_language() -> Result<(), Error> {
778 let loc = ULoc::try_from("es-CO")?;
779 assert_eq!(loc.language(), Some("es".to_string()));
780 Ok(())
781 }
782
783 #[test]
784 fn test_language_absent() -> Result<(), Error> {
785 let loc = ULoc::for_language_tag("und-CO")?;
786 assert_eq!(loc.language(), None);
787 Ok(())
788 }
789
790 #[test]
792 fn test_long_language_tag() -> Result<(), Error> {
793 let mut language_tag: String = "und-CO".to_owned();
794 let language_tag_rest = (0..500).map(|_| " ").collect::<String>();
795 language_tag.push_str(&language_tag_rest);
796 let _loc = ULoc::for_language_tag(&language_tag)?;
797 Ok(())
798 }
799
800 #[test]
801 fn test_script() -> Result<(), Error> {
802 let loc = ULoc::try_from("sr-Cyrl")?;
803 assert_eq!(loc.script(), Some("Cyrl".to_string()));
804 Ok(())
805 }
806
807 #[test]
808 fn test_script_absent() -> Result<(), Error> {
809 let loc = ULoc::try_from("sr")?;
810 assert_eq!(loc.script(), None);
811 Ok(())
812 }
813
814 #[test]
815 fn test_country() -> Result<(), Error> {
816 let loc = ULoc::try_from("es-CO")?;
817 assert_eq!(loc.country(), Some("CO".to_string()));
818 Ok(())
819 }
820
821 #[test]
822 fn test_country_absent() -> Result<(), Error> {
823 let loc = ULoc::try_from("es")?;
824 assert_eq!(loc.country(), None);
825 Ok(())
826 }
827
828 #[test]
831 fn test_variant() -> Result<(), Error> {
832 let loc = ULoc::try_from("zh-Latn-pinyin")?;
833 assert_eq!(
834 loc.variant(),
835 Some("PINYIN".to_string()),
836 "locale was: {:?}",
837 loc
838 );
839 Ok(())
840 }
841
842 #[test]
843 fn test_variant_absent() -> Result<(), Error> {
844 let loc = ULoc::try_from("zh-Latn")?;
845 assert_eq!(loc.variant(), None);
846 Ok(())
847 }
848
849 #[test]
850 fn test_name() -> Result<(), Error> {
851 let loc = ULoc::try_from("en-US")?;
852 match loc.name() {
853 None => assert!(false),
854 Some(name) => assert_eq!(name, "en_US"),
855 }
856 let loc = ULoc::try_from("und")?;
857 assert_eq!(loc.name(), None);
858 let loc = ULoc::try_from("")?;
859 assert_eq!(loc.name(), None);
860 Ok(())
861 }
862
863 #[test]
864 fn test_default_locale() {
865 let loc = ULoc::try_from("fr-fr").expect("get fr_FR locale");
866 set_default(&loc).expect("successful set of locale");
867 assert_eq!(get_default().label(), loc.label());
868 assert_eq!(loc.label(), "fr_FR", "The locale should get canonicalized");
869 let loc = ULoc::try_from("en-us").expect("get en_US locale");
870 set_default(&loc).expect("successful set of locale");
871 assert_eq!(get_default().label(), loc.label());
872 }
873
874 #[test]
875 fn test_add_likely_subtags() {
876 let loc = ULoc::try_from("en-US").expect("get en_US locale");
877 let with_likely_subtags = loc.add_likely_subtags().expect("should add likely subtags");
878 let expected = ULoc::try_from("en_Latn_US").expect("get en_Latn_US locale");
879 assert_eq!(with_likely_subtags.label(), expected.label());
880 }
881
882 #[test]
883 fn test_minimize_subtags() {
884 let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale");
885 let minimized_subtags = loc.minimize_subtags().expect("should minimize subtags");
886 let expected = ULoc::try_from("sr").expect("get sr locale");
887 assert_eq!(minimized_subtags.label(), expected.label());
888 }
889
890 #[test]
891 fn test_to_language_tag() {
892 let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale");
893 let language_tag = loc
894 .to_language_tag(true)
895 .expect("should convert to language tag");
896 assert_eq!(language_tag, "sr-Cyrl-RS".to_string());
897 }
898
899 #[test]
900 fn test_keywords() -> Result<(), Error> {
901 let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
902 let keywords: Vec<String> = loc.keywords().collect();
903 assert_eq!(
904 keywords,
905 vec![
906 "calendar".to_string(),
907 "fw".to_string(),
908 "numbers".to_string(),
909 "timezone".to_string()
910 ]
911 );
912 Ok(())
913 }
914
915 #[test]
916 fn test_keywords_nounicode() -> Result<(), Error> {
917 let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-t-it-x-whatever")?;
918 let keywords: Vec<String> = loc.keywords().collect();
919 assert_eq!(
920 keywords,
921 vec!["calendar".to_string(), "t".to_string(), "x".to_string(),]
922 );
923 assert_eq!(loc.keyword_value("t")?.unwrap(), "it");
924 assert_eq!(loc.keyword_value("x")?.unwrap(), "whatever");
925 Ok(())
926 }
927
928 #[test]
929 fn test_keywords_empty() -> Result<(), Error> {
930 let loc = ULoc::for_language_tag("az-Cyrl-AZ")?;
931 let keywords: Vec<String> = loc.keywords().collect();
932 assert!(keywords.is_empty());
933 Ok(())
934 }
935
936 #[test]
937 fn test_unicode_keywords() -> Result<(), Error> {
938 let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
939 let keywords: Vec<String> = loc.unicode_keywords().collect();
940 assert_eq!(
941 keywords,
942 vec![
943 "ca".to_string(),
944 "fw".to_string(),
945 "nu".to_string(),
946 "tz".to_string()
947 ]
948 );
949 Ok(())
950 }
951
952 #[test]
953 fn test_unicode_keywords_empty() -> Result<(), Error> {
954 let loc = ULoc::for_language_tag("az-Cyrl-AZ")?;
955 let keywords: Vec<String> = loc.unicode_keywords().collect();
956 assert!(keywords.is_empty());
957 Ok(())
958 }
959
960 #[test]
961 fn test_keyword_value() -> Result<(), Error> {
962 let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
963 assert_eq!(loc.keyword_value("calendar")?, Some("hebrew".to_string()));
964 assert_eq!(loc.keyword_value("collation")?, None);
965 Ok(())
966 }
967
968 #[test]
969 fn test_unicode_keyword_value() -> Result<(), Error> {
970 let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
971 assert_eq!(loc.unicode_keyword_value("ca")?, Some("hebrew".to_string()));
972 assert_eq!(loc.unicode_keyword_value("fw")?, Some("sunday".to_string()));
973 assert_eq!(loc.unicode_keyword_value("co")?, None);
974 Ok(())
975 }
976
977 #[test]
978 fn test_order() -> Result<(), Error> {
979 assert!(ULoc::for_language_tag("az")? < ULoc::for_language_tag("az-Cyrl")?);
980 assert!(ULoc::for_language_tag("az-Cyrl")? < ULoc::for_language_tag("az-Cyrl-AZ")?);
981 assert!(
982 ULoc::for_language_tag("az-Cyrl-AZ")? < ULoc::for_language_tag("az-Cyrl-AZ-variant")?
983 );
984 assert!(
985 ULoc::for_language_tag("az-Cyrl-AZ-variant")?
986 < ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")?
987 );
988 assert!(
989 ULoc::for_language_tag("az-u-ca-gregory")? < ULoc::for_language_tag("az-u-fw-fri")?
990 );
991 assert!(
992 ULoc::for_language_tag("az-u-ca-buddhist")?
993 < ULoc::for_language_tag("az-u-ca-chinese")?
994 );
995 assert!(ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-tue")?);
996 assert!(
997 ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-mon-nu-arab")?
998 );
999 assert!(
1000 ULoc::for_language_tag("az-u-fw-mon-nu-arab")? > ULoc::for_language_tag("az-u-fw-mon")?
1001 );
1002
1003 let loc = ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")?;
1004 assert_eq!(loc.cmp(&loc), Ordering::Equal,);
1005 Ok(())
1006 }
1007
1008 #[test]
1009 fn test_accept_language_fallback() {
1010 let accept_list: Result<Vec<_>, _> = vec!["es_MX", "ar_EG", "fr_FR"]
1011 .into_iter()
1012 .map(ULoc::try_from)
1013 .collect();
1014 let accept_list = accept_list.expect("make accept_list");
1015
1016 let available_locales: Result<Vec<_>, _> =
1017 vec!["de_DE", "en_US", "es", "nl_NL", "sr_RS_Cyrl"]
1018 .into_iter()
1019 .map(ULoc::try_from)
1020 .collect();
1021 let available_locales = available_locales.expect("make available_locales");
1022
1023 let actual = accept_language(accept_list, available_locales).expect("call accept_language");
1024 assert_eq!(
1025 actual,
1026 (
1027 ULoc::try_from("es").ok(),
1028 UAcceptResult::ULOC_ACCEPT_FALLBACK
1029 )
1030 );
1031 }
1032
1033 #[test]
1034 fn test_accept_language_exact_match() {
1035 let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
1036 .into_iter()
1037 .map(ULoc::try_from)
1038 .collect();
1039 let accept_list = accept_list.expect("make accept_list");
1040
1041 let available_locales: Result<Vec<_>, _> = vec!["de_DE", "en_US", "es_MX", "ar_EG"]
1042 .into_iter()
1043 .map(ULoc::try_from)
1044 .collect();
1045 let available_locales = available_locales.expect("make available_locales");
1046
1047 let actual = accept_language(accept_list, available_locales).expect("call accept_language");
1048 assert_eq!(
1049 actual,
1050 (
1051 ULoc::try_from("es_MX").ok(),
1052 UAcceptResult::ULOC_ACCEPT_FALLBACK,
1053 )
1054 );
1055 }
1056
1057 #[test]
1058 fn test_accept_language_no_match() {
1059 let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
1060 .into_iter()
1061 .map(ULoc::try_from)
1062 .collect();
1063 let accept_list = accept_list.expect("make accept_list");
1064
1065 let available_locales: Result<Vec<_>, _> =
1066 vec!["el_GR"].into_iter().map(ULoc::try_from).collect();
1067 let available_locales = available_locales.expect("make available_locales");
1068
1069 let actual = accept_language(accept_list, available_locales).expect("call accept_language");
1070 assert_eq!(actual, (None, UAcceptResult::ULOC_ACCEPT_FAILED))
1071 }
1072
1073 #[test]
1074 fn test_to_unicode_locale_key() -> Result<(), Error> {
1075 let actual = to_unicode_locale_key("calendar");
1076 assert_eq!(actual, Some("ca".to_string()));
1077 Ok(())
1078 }
1079
1080 #[test]
1081 fn test_to_unicode_locale_type() -> Result<(), Error> {
1082 let actual = to_unicode_locale_type("co", "phonebook");
1083 assert_eq!(actual, Some("phonebk".to_string()));
1084 Ok(())
1085 }
1086
1087 #[test]
1088 fn test_to_legacy_key() -> Result<(), Error> {
1089 let actual = to_legacy_key("ca");
1090 assert_eq!(actual, Some("calendar".to_string()));
1091 Ok(())
1092 }
1093
1094 #[test]
1095 fn test_str_to_cstring() -> Result<(), Error> {
1096 assert_eq!(str_to_cstring("abc"), ffi::CString::new("abc")?);
1097 assert_eq!(str_to_cstring("abc\0def"), ffi::CString::new("abc")?);
1098
1099 Ok(())
1100 }
1101
1102 #[test]
1103 fn test_base_name() -> Result<(), Error> {
1104 assert_eq!(
1105 ULoc::try_from("en-u-tz-uslax-x-foo")?.base_name(),
1106 ULoc::try_from("en")?
1107 );
1108 Ok(())
1109 }
1110
1111 #[test]
1112 fn test_uloc_mut() -> Result<(), Error> {
1113 let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1114 let loc_mut = ULocMut::from(loc);
1115 let loc = ULoc::from(loc_mut);
1116 assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?, loc);
1117 Ok(())
1118 }
1119
1120 #[test]
1121 fn test_uloc_mut_changes() -> Result<(), Error> {
1122 let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1123 let mut loc_mut = ULocMut::from(loc);
1124 loc_mut.remove_unicode_keyvalue("tz");
1125 let loc = ULoc::from(loc_mut);
1126 assert_eq!(ULoc::for_language_tag("en-t-it-x-foo")?, loc);
1127
1128 let loc = ULoc::for_language_tag("en-u-tz-uslax")?;
1129 let mut loc_mut = ULocMut::from(loc);
1130 loc_mut.remove_unicode_keyvalue("tz");
1131 let loc = ULoc::from(loc_mut);
1132 assert_eq!(ULoc::for_language_tag("en")?, loc);
1133 Ok(())
1134 }
1135
1136 #[test]
1137 fn test_uloc_mut_overrides() -> Result<(), Error> {
1138 let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1139 let mut loc_mut = ULocMut::from(loc);
1140 loc_mut.set_unicode_keyvalue("tz", "usnyc");
1141 let loc = ULoc::from(loc_mut);
1142 assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-usnyc-x-foo")?, loc);
1143
1144 let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1145 let mut loc_mut = ULocMut::from(loc);
1146 loc_mut.set_unicode_keyvalue("tz", "usnyc");
1147 loc_mut.set_unicode_keyvalue("nu", "arabic");
1148 let loc = ULoc::from(loc_mut);
1149 assert_eq!(ULoc::for_language_tag("en-t-it-u-nu-arabic-tz-usnyc-x-foo")?, loc);
1150 assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-usnyc-nu-arabic-x-foo")?, loc);
1151 Ok(())
1152 }
1153
1154 #[test]
1155 fn test_uloc_mut_add_unicode_extension() -> Result<(), Error> {
1156 let loc = ULoc::for_language_tag("en-t-it-x-foo")?;
1157 let mut loc_mut = ULocMut::from(loc);
1158 loc_mut.set_unicode_keyvalue("tz", "usnyc");
1159 let loc = ULoc::from(loc_mut);
1160 assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-usnyc-x-foo")?, loc);
1161 Ok(())
1162 }
1163
1164 #[test]
1165 fn test_round_trip_from_uloc_plain() -> Result<(), Error> {
1166 let loc = ULoc::for_language_tag("sr")?;
1167 let loc = ULocMut::from(loc);
1168 let loc = ULoc::from(loc);
1169 assert_eq!(ULoc::try_from("sr")?, loc);
1170 Ok(())
1171 }
1172
1173 #[test]
1174 fn test_round_trip_from_uloc_with_country() -> Result<(), Error> {
1175 let loc = ULoc::for_language_tag("sr-rs")?;
1176 let loc = ULoc::from(ULocMut::from(loc));
1177 assert_eq!(ULoc::try_from("sr-rs")?, loc);
1178 Ok(())
1179 }
1180
1181 #[test]
1182 fn test_equivalence() {
1183 let loc = ULoc::try_from("sr@timezone=America/Los_Angeles").unwrap();
1184 assert_eq!(ULoc::for_language_tag("sr-u-tz-uslax").unwrap(), loc);
1185 }
1186
1187 #[test]
1188 fn test_for_language_error() {
1189 let loc = ULoc::for_language_tag("en_US").unwrap();
1190 assert_eq!(loc.language(), None);
1191 assert_eq!(loc.country(), None);
1192 }
1193
1194 #[test]
1195 fn test_iso3_language() {
1196 let loc = ULoc::for_language_tag("en-US").unwrap();
1197 let iso_lang = loc.iso3_language();
1198 assert_eq!(iso_lang, Some("eng".to_string()));
1199 let loc = ULoc::for_language_tag("und").unwrap();
1200 let iso_lang = loc.iso3_language();
1201 assert_eq!(iso_lang, None);
1202 }
1203
1204 #[test]
1205 fn test_iso3_country() {
1206 let loc = ULoc::for_language_tag("en-US").unwrap();
1207 let iso_country = loc.iso3_country();
1208 assert_eq!(iso_country, Some("USA".to_string()));
1209 let loc = ULoc::for_language_tag("und").unwrap();
1210 let iso_country = loc.iso3_country();
1211 assert_eq!(iso_country, None);
1212 }
1213
1214 #[test]
1215 fn test_display_language() {
1216 let english_locale = ULoc::for_language_tag("en").unwrap();
1217 let french_locale = ULoc::for_language_tag("fr").unwrap();
1218 let english_in_french = english_locale.display_language(&french_locale);
1219 assert!(english_in_french.is_ok());
1220 assert_eq!(english_in_french.unwrap().as_string_debug(), "anglais");
1221 let root_locale = ULoc::for_language_tag("und").unwrap();
1222 assert_eq!(root_locale.display_language(&french_locale).unwrap().as_string_debug(), "langue indéterminée");
1223 let root_locale = ULoc::for_language_tag("en_US").unwrap();
1224 assert_eq!(root_locale.display_language(&french_locale).unwrap().as_string_debug(), "langue indéterminée");
1225 }
1226
1227 #[test]
1228 fn test_display_script() {
1229 let english_latin_locale = ULoc::for_language_tag("en-latg").unwrap();
1230 let french_locale = ULoc::for_language_tag("fr").unwrap();
1231 let latin_script_in_french = english_latin_locale.display_script(&french_locale);
1232 assert!(latin_script_in_french.is_ok());
1233 assert_eq!(latin_script_in_french.unwrap().as_string_debug(), "latin (variante gaélique)");
1234 let english_locale = ULoc::for_language_tag("en").unwrap();
1235 assert_eq!(english_locale.display_script(&french_locale).unwrap().as_string_debug(), "");
1236 }
1237
1238 #[test]
1239 fn test_display_country() {
1240 let usa_locale = ULoc::for_language_tag("en-US").unwrap();
1241 let french_locale = ULoc::for_language_tag("fr").unwrap();
1242 let usa_in_french = usa_locale.display_country(&french_locale);
1243 assert!(usa_in_french.is_ok());
1244 assert_eq!(usa_in_french.unwrap().as_string_debug(), "États-Unis");
1245 let english_locale = ULoc::for_language_tag("en").unwrap();
1246 assert_eq!(english_locale.display_script(&french_locale).unwrap().as_string_debug(), "");
1247 }
1248
1249 #[test]
1250 fn test_display_keyword() {
1251 let french_locale = ULoc::for_language_tag("fr").unwrap();
1252 let currency_in_french = ULoc::display_keyword(&"currency".to_string(), &french_locale);
1253 assert!(currency_in_french.is_ok());
1254 assert_eq!(currency_in_french.unwrap().as_string_debug(), "devise");
1255 }
1256
1257 #[test]
1258 fn test_display_keyword_value() {
1259 let locale_w_calendar = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-t-it-x-whatever").unwrap();
1260 let french_locale = ULoc::for_language_tag("fr").unwrap();
1261 let calendar_value_in_french = locale_w_calendar.display_keyword_value(
1262 &"calendar".to_string(), &french_locale);
1263 assert!(calendar_value_in_french.is_ok());
1264 assert_eq!(calendar_value_in_french.unwrap().as_string_debug(), "calendrier hébraïque");
1265 }
1266
1267 #[test]
1268 fn test_display_name() {
1269 let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-t-it-x-whatever").unwrap();
1270 let french_locale = ULoc::for_language_tag("fr").unwrap();
1271 let display_name_in_french = loc.display_name(&french_locale);
1272 assert!(display_name_in_french.is_ok());
1273 let display_name = display_name_in_french.unwrap().as_string_debug();
1274 assert!(
1275 display_name == "azerbaïdjanais (cyrillique, Azerbaïdjan, calendrier=calendrier hébraïque, t=it, usage privé=whatever)" ||
1276 display_name == "azerbaïdjanais (cyrillique, Azerbaïdjan, calendrier=calendrier hébraïque, transformation=it, usage privé=whatever)",
1277 "expected display_name to be one of the known formats, but got: {}", display_name
1278 );
1279 }
1280
1281 #[test]
1282 #[should_panic(expected = "-1 is negative or greater than or equal to the value returned from count_available()")]
1283 fn test_get_available_negative() {
1284 let _ = ULoc::get_available(-1);
1285 }
1286
1287 #[test]
1288 fn test_get_available_overrun() {
1289 let index = ULoc::count_available();
1290 let result = std::panic::catch_unwind(|| ULoc::get_available(index));
1291 assert!(result.is_err());
1292 }
1293
1294 #[test]
1295 fn test_get_available_locales() {
1296 let locales = ULoc::get_available_locales();
1297 assert!(locales.contains(&ULoc::try_from("en").unwrap()));
1298 assert!(locales.contains(&ULoc::try_from("en-US").unwrap()));
1299 assert!(locales.contains(&ULoc::try_from("fr").unwrap()));
1300 assert!(locales.contains(&ULoc::try_from("fr-FR").unwrap()));
1301 assert_eq!(ULoc::count_available() as usize, locales.capacity());
1302 assert_eq!(locales.len(), locales.capacity());
1303 }
1304
1305 #[test]
1306 fn test_get_available() {
1307 let locale = ULoc::get_available(0);
1309 assert!(locale.is_ok());
1310 }
1311
1312 #[test]
1313 fn test_get_available_locales_by_type() {
1314 let locales1 = ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_DEFAULT);
1315 let locales2 = ULoc::get_available_locales();
1316 assert_eq!(locales1, locales2);
1317 let alias_locales = ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_ONLY_LEGACY_ALIASES);
1318 let all_locales = ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_WITH_LEGACY_ALIASES);
1319 for locale in &alias_locales {
1320 assert!(all_locales.contains(&locale));
1321 assert!(!locales1.contains(&locale));
1322 }
1323 for locale in &locales1 {
1324 assert!(all_locales.contains(&locale));
1325 assert!(!alias_locales.contains(&locale));
1326 }
1327 assert!(alias_locales.len() > 0);
1328 assert!(locales1.len() > alias_locales.len());
1329 assert!(all_locales.len() > locales1.len());
1330 assert!(alias_locales.contains(&ULoc::try_from("iw").unwrap()));
1331 assert!(alias_locales.contains(&ULoc::try_from("mo").unwrap()));
1332 assert!(alias_locales.contains(&ULoc::try_from("zh-CN").unwrap()));
1333 assert!(alias_locales.contains(&ULoc::try_from("sr-BA").unwrap()));
1334 assert!(alias_locales.contains(&ULoc::try_from("ars").unwrap()));
1335 }
1336
1337 #[test]
1338 #[should_panic(expected = "ULOC_AVAILABLE_COUNT is for internal use only")]
1339 fn test_get_available_locales_by_type_panic() {
1340 ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_COUNT);
1341 }
1342
1343 #[test]
1344 fn test_get_available_locales_by_type_error() {
1345 assert!(!ULoc::open_available_by_type(ULocAvailableType::ULOC_AVAILABLE_COUNT).is_ok());
1346 }
1347}