Skip to main content

rust_icu_uloc/
lib.rs

1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use {
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
34/// Maximum length of locale supported by uloc.h.
35/// See `ULOC_FULLNAME_CAPACITY`.
36const LOCALE_CAPACITY: usize = 158;
37
38/// [ULocMut] is a mutable companion to [ULoc].
39///
40/// It has methods that allow one to create a different [ULoc] by adding and
41/// removing keywords to the locale identifier.  You can only create a `ULocMut`
42/// by converting from an existing `ULoc` by calling `ULocMut::from`.  And once
43/// you are done changing it, you can only convert it back with `ULoc::from`.
44///
45/// [ULocMut] is not meant to have comprehensive coverage of mutation options.
46/// They may be added as necessary.
47#[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    /// Turns [ULoc] into [ULocMut], which can be mutated.
56    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            // Despite the many unwraps below, none should be triggered, since we know
62            // that the keywords come from the list of keywords that already exist.
63            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        // base_name may return an invalid language tag, so convert here.
76        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    // Creates an [ULoc] from [ULocMut].
87    fn from(lm: ULocMut) -> Self {
88        // Assemble the unicode extension.
89        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        // Assemble all other extensions.
105        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        // The base language must be in the form of BCP47 language tag to
114        // be usable in the code below.
115        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    /// Sets the specified unicode extension keyvalue.  Only valid keys can be set,
130    /// inserting an invalid extension key does not change [ULocMut].
131    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    /// Removes the specified unicode extension keyvalue.  Only valid keys can
140    /// be removed, attempting to remove an invalid extension key does not
141    /// change [ULocMut].
142    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/// A representation of a Unicode locale.
151///
152/// For the time being, only basic conversion and methods are in fact implemented.
153///
154/// To get basic validation when creating a locale, use
155/// [`for_language_tag`](ULoc::for_language_tag) with a Unicode BCP-47 locale ID.
156#[derive(Debug, Clone, Eq, PartialEq, Hash)]
157pub struct ULoc {
158    // A locale's representation in C is really just a string.
159    repr: String,
160}
161
162/// Implement the Display trait to convert the ULoc into string for display.
163///
164/// The string for display and string serialization happen to be the same for [ULoc].
165impl 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    /// Creates a new ULoc from a string slice.
174    ///
175    /// The creation wil fail if the locale is nonexistent.
176    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    /// Creates a new `ULoc` from a borrowed C string.
186    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    /// Implements `uloc_getLanguage`.
197    pub fn language(&self) -> Option<String> {
198        self.call_buffered_string_method_to_option(versioned_function!(uloc_getLanguage))
199    }
200
201    /// Implements `uloc_getScript`.
202    pub fn script(&self) -> Option<String> {
203        self.call_buffered_string_method_to_option(versioned_function!(uloc_getScript))
204    }
205
206    /// Implements `uloc_getCountry`.
207    pub fn country(&self) -> Option<String> {
208        self.call_buffered_string_method_to_option(versioned_function!(uloc_getCountry))
209    }
210
211    /// Implements `uloc_getVariant`.
212    pub fn variant(&self) -> Option<String> {
213        self.call_buffered_string_method_to_option(versioned_function!(uloc_getVariant))
214    }
215
216    /// Implements `uloc_getName`.
217    pub fn name(&self) -> Option<String> {
218        self.call_buffered_string_method_to_option(versioned_function!(uloc_getName))
219    }
220
221    /// Implements `uloc_canonicalize` from ICU4C.
222    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    /// Implements 'uloc_getISO3Language' from ICU4C.
228    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    /// Implements 'uloc_getISO3Country' from ICU4C.
243    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    /// Implements `uloc_getDisplayLanguage`.
258    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    /// Implements `uloc_getDisplayScript`.
273    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    /// Implements `uloc_getDisplayCountry`.
288    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    /// Implements `uloc_getDisplayVariant`.
303    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    /// Implements `uloc_getDisplayKeyword`.
318    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    /// Implements `uloc_getDisplayKeywordValue`.
334    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    /// Implements `uloc_getDisplayName`.
351    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    /// Implements `uloc_countAvailable`.
366    pub fn count_available() -> i32 {
367        let count = unsafe { versioned_function!(uloc_countAvailable)() };
368        count
369    }
370
371    /// Implements `uloc_getAvailable`.
372    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    /// Returns a vector of available locales
385    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    /// Implements `uloc_openAvailableByType`.
398    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    /// Returns a vector of locales of the requested type.
408    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    /// Implements `uloc_addLikelySubtags` from ICU4C.
416    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    /// Implements `uloc_minimizeSubtags` from ICU4C.
422    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    /// Implements `uloc_toLanguageTag` from ICU4C.
428    pub fn to_language_tag(&self, strict: bool) -> Result<String, common::Error> {
429        let locale_id = self.as_c_str();
430        // No `UBool` constants available in rust_icu_sys, unfortunately.
431        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        // "No error but null" means that there are no keywords
455        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    /// Implements `uloc_openKeywords()` from ICU4C.
463    pub fn keywords(&self) -> impl Iterator<Item = String> {
464        self.open_keywords()
465            .unwrap()
466            .map(|result| result.unwrap())
467    }
468
469    /// Implements `icu::Locale::getUnicodeKeywords()` from the C++ API.
470    pub fn unicode_keywords(&self) -> impl Iterator<Item = String> {
471        self.keywords().filter_map(|s| to_unicode_locale_key(&s))
472    }
473
474    /// Implements `uloc_getKeywordValue()` from ICU4C.
475    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    /// Implements `icu::Locale::getUnicodeKeywordValue()` from the C++ API.
494    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    /// Returns the current label of this locale.
512    pub fn label(&self) -> &str {
513        &self.repr
514    }
515
516    /// Returns the current locale name as a C string.
517    pub fn as_c_str(&self) -> ffi::CString {
518        ffi::CString::new(self.repr.clone()).expect("ULoc contained interior NUL bytes")
519    }
520
521    /// Implements `uloc_forLanguageTag` from ICU4C.
522    ///
523    /// Note that an invalid tag will cause that tag and all others to be
524    /// ignored.  For example `en-us` will work but `en_US` will not.
525    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    /// Call a `uloc` method that takes this locale's ID and returns a string.
543    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    /// Call a `uloc` method that takes this locale's ID, panics on any errors, and returns
560    /// `Some(result)` if the resulting string is non-empty, or `None` otherwise.
561    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    /// Implements `uloc_getBaseName` from ICU4C.
579    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
587/// This implementation is based on ULocale.compareTo from ICU4J.
588/// See 
589/// <https://github.com/unicode-org/icu/blob/%6d%61%73%74%65%72/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java>
590impl Ord for ULoc {
591    fn cmp(&self, other: &Self) -> Ordering {
592        /// Compare corresponding keywords from two `ULoc`s. If the keywords match, compare the
593        /// keyword values.
594        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                    // Compare the two keywords
603                    match self_keyword.cmp(&other_keyword) {
604                        Ordering::Equal => {
605                            // Compare the two keyword values
606                            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                // `other` has run out of keywords
614                (Some(_), _) => Some(Ordering::Greater),
615                // `this` has run out of keywords
616                (_, Some(_)) => Some(Ordering::Less),
617                // Both iterators have run out
618                (_, _) => 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                // All keywords and values were identical (or there were none)
643                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
654/// Gets the current system default locale.
655///
656/// Implements `uloc_getDefault` from ICU4C.
657pub 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
663/// Sets the current default system locale.
664///
665/// Implements `uloc_setDefault` from ICU4C.
666pub 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
673/// Implements `uloc_acceptLanguage` from ICU4C.
674pub 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    // This is mutable only to satisfy the missing `const`s in the ICU4C API.
681    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    // Having no match is a valid if disappointing result.
716    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
725/// Implements `uloc_toUnicodeLocaleKey` from ICU4C.
726pub 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
735/// Implements `uloc_toUnicodeLocaleType` from ICU4C.
736pub 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
749/// Implements `uloc_toLegacyKey` from ICU4C.
750pub 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
759/// Infallibly converts a Rust string to a `CString`. If there's an interior NUL, the string is
760/// truncated up to that point.
761fn 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
766/// Infallibly converts a `CString` to a Rust `String`. We can safely assume that any strings
767/// coming from ICU data are valid UTF-8.
768fn 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    // https://github.com/google/rust_icu/issues/244
791    #[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    // This test yields a different result in ICU versions prior to 64:
829    // "zh-Latn@collation=pinyin".
830    #[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        // We know that index 0 should always be available.
1308        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}