blob: faf0c652ba02e7c0403be7632983c4404b6015c0 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use {
rust_icu_common as common,
rust_icu_common::buffered_string_method_with_retry,
rust_icu_sys::versioned_function,
rust_icu_sys::*,
rust_icu_sys as sys,
rust_icu_uenum::Enumeration,
std::{
cmp::Ordering,
convert::{From, TryFrom, TryInto},
ffi,
os::raw,
fmt,
},
};
/// Maximum length of locale supported by uloc.h.
/// See `ULOC_FULLNAME_CAPACITY`.
const LOCALE_CAPACITY: usize = 158;
/// A representation of a Unicode locale.
///
/// For the time being, only basic conversion and methods are in fact implemented.
///
/// To get basic validation when creating a locale, use
/// [`for_language_tag`](ULoc::for_language_tag) with a Unicode BCP-47 locale ID.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ULoc {
// A locale's representation in C is really just a string.
repr: String,
}
/// Implement the Display trait to convert the ULoc into string for display.
///
/// The string for display and string serialization happen to be the same for [ULoc].
impl fmt::Display for ULoc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.repr)
}
}
impl TryFrom<&str> for ULoc {
type Error = common::Error;
/// Creates a new ULoc from a string slice.
///
/// The creation wil fail if the locale is nonexistent.
fn try_from(s: &str) -> Result<Self, Self::Error> {
let s = String::from(s);
ULoc { repr: s }.canonicalize()
}
}
impl TryFrom<&ffi::CStr> for ULoc {
type Error = common::Error;
/// Creates a new `ULoc` from a borrowed C string.
fn try_from(s: &ffi::CStr) -> Result<Self, Self::Error> {
let repr = s.to_str()?;
ULoc {
repr: String::from(repr),
}
.canonicalize()
}
}
impl ULoc {
/// Implements `uloc_getLanguage`.
pub fn language(&self) -> Option<String> {
self.call_buffered_string_method_to_option(versioned_function!(uloc_getLanguage))
}
/// Implements `uloc_getScript`.
pub fn script(&self) -> Option<String> {
self.call_buffered_string_method_to_option(versioned_function!(uloc_getScript))
}
/// Implements `uloc_getCountry`.
pub fn country(&self) -> Option<String> {
self.call_buffered_string_method_to_option(versioned_function!(uloc_getCountry))
}
/// Implements `uloc_getVariant`.
pub fn variant(&self) -> Option<String> {
self.call_buffered_string_method_to_option(versioned_function!(uloc_getVariant))
}
/// Implements `uloc_canonicalize` from ICU4C.
pub fn canonicalize(&self) -> Result<ULoc, common::Error> {
self.call_buffered_string_method(versioned_function!(uloc_canonicalize))
.map(|repr| ULoc { repr })
}
/// Implements `uloc_addLikelySubtags` from ICU4C.
pub fn add_likely_subtags(&self) -> Result<ULoc, common::Error> {
self.call_buffered_string_method(versioned_function!(uloc_addLikelySubtags))
.map(|repr| ULoc { repr })
}
/// Implements `uloc_minimizeSubtags` from ICU4C.
pub fn minimize_subtags(&self) -> Result<ULoc, common::Error> {
self.call_buffered_string_method(versioned_function!(uloc_minimizeSubtags))
.map(|repr| ULoc { repr })
}
/// Implements `uloc_toLanguageTag` from ICU4C.
pub fn to_language_tag(&self, strict: bool) -> Result<String, common::Error> {
buffered_string_method_with_retry!(
buffered_string_to_language_tag,
LOCALE_CAPACITY,
[locale_id: *const raw::c_char,],
[strict: rust_icu_sys::UBool,]
);
let locale_id = self.as_c_str();
// No `UBool` constants available in rust_icu_sys, unfortunately.
let strict = if strict { 1 } else { 0 };
buffered_string_to_language_tag(
versioned_function!(uloc_toLanguageTag),
locale_id.as_ptr(),
strict,
)
}
/// Implements `uloc_openKeywords()` from ICU4C.
pub fn keywords(&self) -> impl Iterator<Item = String> {
rust_icu_uenum::uloc_open_keywords(&self.repr)
.unwrap()
.map(|result| result.unwrap())
}
/// Implements `icu::Locale::getUnicodeKeywords()` from the C++ API.
pub fn unicode_keywords(&self) -> impl Iterator<Item = String> {
self.keywords().filter_map(|s| to_unicode_locale_key(&s))
}
/// Implements `uloc_getKeywordValue()` from ICU4C.
pub fn keyword_value(&self, keyword: &str) -> Result<Option<String>, common::Error> {
buffered_string_method_with_retry!(
buffered_string_keyword_value,
LOCALE_CAPACITY,
[
locale_id: *const raw::c_char,
keyword_name: *const raw::c_char,
],
[]
);
let locale_id = self.as_c_str();
let keyword_name = str_to_cstring(keyword);
buffered_string_keyword_value(
versioned_function!(uloc_getKeywordValue),
locale_id.as_ptr(),
keyword_name.as_ptr(),
)
.map(|value| if value.is_empty() { None } else { Some(value) })
}
/// Implements `icu::Locale::getUnicodeKeywordValue()` from ICU4C.
pub fn unicode_keyword_value(
&self,
unicode_keyword: &str,
) -> Result<Option<String>, common::Error> {
let legacy_keyword = to_legacy_key(unicode_keyword);
match legacy_keyword {
Some(legacy_keyword) => match self.keyword_value(&legacy_keyword) {
Ok(Some(legacy_value)) => {
Ok(to_unicode_locale_type(&legacy_keyword, &legacy_value))
}
Ok(None) => Ok(None),
Err(e) => Err(e),
},
None => Ok(None),
}
}
/// Returns the current label of this locale.
pub fn label(&self) -> &str {
&self.repr
}
/// Returns the current locale name as a C string.
pub fn as_c_str(&self) -> ffi::CString {
ffi::CString::new(self.repr.clone()).expect("ULoc contained interior NUL bytes")
}
/// Implements `uloc_forLanguageTag` from ICU4C.
pub fn for_language_tag(tag: &str) -> Result<ULoc, common::Error> {
buffered_string_method_with_retry!(
buffered_string_for_language_tag,
LOCALE_CAPACITY,
[tag: *const raw::c_char,],
[parsed_length: *mut i32,]
);
let tag = str_to_cstring(tag);
let locale_id = buffered_string_for_language_tag(
versioned_function!(uloc_forLanguageTag),
tag.as_ptr(),
std::ptr::null_mut(),
)?;
ULoc::try_from(&locale_id[..])
}
/// Call a `uloc` method that takes this locale's ID and returns a string.
fn call_buffered_string_method(
&self,
uloc_method: unsafe extern "C" fn(
*const raw::c_char,
*mut raw::c_char,
i32,
*mut UErrorCode,
) -> i32,
) -> Result<String, common::Error> {
buffered_string_method_with_retry!(
buffered_string_char_star,
LOCALE_CAPACITY,
[char_star: *const raw::c_char,],
[]
);
let asciiz = self.as_c_str();
buffered_string_char_star(uloc_method, asciiz.as_ptr())
}
/// Call a `uloc` method that takes this locale's ID, panics on any errors, and returns
/// `Some(result)` if the resulting string is non-empty, or `None` otherwise.
fn call_buffered_string_method_to_option(
&self,
uloc_method: unsafe extern "C" fn(
*const raw::c_char,
*mut raw::c_char,
i32,
*mut UErrorCode,
) -> i32,
) -> Option<String> {
let value: String = self.call_buffered_string_method(uloc_method).unwrap();
if value.is_empty() {
None
} else {
Some(value)
}
}
}
/// This implementation is based on ULocale.compareTo from ICU4J.
/// See https://github.com/unicode-org/icu/blob/master/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java
impl Ord for ULoc {
fn cmp(&self, other: &Self) -> Ordering {
/// Compare corresponding keywords from two `ULoc`s. If the keywords match, compare the
/// keyword values.
fn compare_keywords(
this: &ULoc,
self_keyword: &Option<String>,
other: &ULoc,
other_keyword: &Option<String>,
) -> Option<Ordering> {
match (self_keyword, other_keyword) {
(Some(self_keyword), Some(other_keyword)) => {
// Compare the two keywords
match self_keyword.cmp(&other_keyword) {
Ordering::Equal => {
// Compare the two keyword values
let self_val = this.keyword_value(&self_keyword[..]).unwrap();
let other_val = other.keyword_value(&other_keyword[..]).unwrap();
Some(self_val.cmp(&other_val))
}
unequal_ordering => Some(unequal_ordering),
}
}
// `other` has run out of keywords
(Some(_), _) => Some(Ordering::Greater),
// `this` has run out of keywords
(_, Some(_)) => Some(Ordering::Less),
// Both iterators have run out
(_, _) => None,
}
}
self.language()
.cmp(&other.language())
.then_with(|| self.script().cmp(&other.script()))
.then_with(|| self.country().cmp(&other.country()))
.then_with(|| self.variant().cmp(&other.variant()))
.then_with(|| {
let mut self_keywords = self.keywords();
let mut other_keywords = other.keywords();
while let Some(keyword_ordering) =
compare_keywords(self, &self_keywords.next(), other, &other_keywords.next())
{
match keyword_ordering {
Ordering::Equal => {}
unequal_ordering => {
return unequal_ordering;
}
}
}
// All keywords and values were identical (or there were none)
Ordering::Equal
})
}
}
impl PartialOrd for ULoc {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
/// Gets the current system default locale.
///
/// Implements `uloc_getDefault` from ICU4C.
pub fn get_default() -> ULoc {
let loc = unsafe { versioned_function!(uloc_getDefault)() };
let uloc_cstr = unsafe { ffi::CStr::from_ptr(loc) };
crate::ULoc::try_from(uloc_cstr).expect("could not convert default locale to ULoc")
}
/// Sets the current default system locale.
///
/// Implements `uloc_setDefault` from ICU4C.
pub fn set_default(loc: &ULoc) -> Result<(), common::Error> {
let mut status = common::Error::OK_CODE;
let asciiz = str_to_cstring(&loc.repr);
unsafe { versioned_function!(uloc_setDefault)(asciiz.as_ptr(), &mut status) };
common::Error::ok_or_warning(status)
}
/// Implements `uloc_acceptLanguage` from ICU4C.
pub fn accept_language(
accept_list: impl IntoIterator<Item = impl Into<ULoc>>,
available_locales: impl IntoIterator<Item = impl Into<ULoc>>,
) -> Result<(Option<ULoc>, UAcceptResult), common::Error> {
buffered_string_method_with_retry!(
buffered_string_uloc_accept_language,
LOCALE_CAPACITY,
[],
[
out_result: *mut UAcceptResult,
accept_list: *mut *const ::std::os::raw::c_char,
accept_list_count: i32,
available_locales: *mut UEnumeration,
]
);
let mut accept_result: UAcceptResult = UAcceptResult::ULOC_ACCEPT_FAILED;
let mut accept_list_cstrings: Vec<ffi::CString> = vec![];
// This is mutable only to satisfy the missing `const`s in the ICU4C API.
let mut accept_list: Vec<*const raw::c_char> = accept_list
.into_iter()
.map(|item| {
let uloc: ULoc = item.into();
accept_list_cstrings.push(uloc.as_c_str());
accept_list_cstrings
.last()
.expect("non-empty list")
.as_ptr()
})
.collect();
let available_locales: Vec<ULoc> = available_locales
.into_iter()
.map(|item| item.into())
.collect();
let available_locales: Vec<&str> = available_locales.iter().map(|uloc| uloc.label()).collect();
let mut available_locales = Enumeration::try_from(&available_locales[..])?;
let matched_locale = buffered_string_uloc_accept_language(
versioned_function!(uloc_acceptLanguage),
&mut accept_result,
accept_list.as_mut_ptr(),
accept_list.len() as i32,
available_locales.repr(),
);
// Having no match is a valid if disappointing result.
if accept_result == UAcceptResult::ULOC_ACCEPT_FAILED {
return Ok((None, accept_result));
}
matched_locale
.and_then(|s| ULoc::try_from(s.as_str()))
.map(|uloc| (Some(uloc), accept_result))
}
/// Implements `uloc_toUnicodeLocaleKey` from ICU4C.
pub fn to_unicode_locale_key(legacy_keyword: &str) -> Option<String> {
let legacy_keyword = str_to_cstring(legacy_keyword);
let unicode_keyword: Option<ffi::CString> = unsafe {
let ptr = versioned_function!(uloc_toUnicodeLocaleKey)(legacy_keyword.as_ptr());
ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
};
unicode_keyword.map(|cstring| cstring_to_string(&cstring))
}
/// Implements `uloc_toUnicodeLocaleType` from ICU4C.
pub fn to_unicode_locale_type(legacy_keyword: &str, legacy_value: &str) -> Option<String> {
let legacy_keyword = str_to_cstring(legacy_keyword);
let legacy_value = str_to_cstring(legacy_value);
let unicode_value: Option<ffi::CString> = unsafe {
let ptr = versioned_function!(uloc_toUnicodeLocaleType)(
legacy_keyword.as_ptr(),
legacy_value.as_ptr(),
);
ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
};
unicode_value.map(|cstring| cstring_to_string(&cstring))
}
/// Implements `uloc_toLegacyKey` from ICU4C.
pub fn to_legacy_key(unicode_keyword: &str) -> Option<String> {
let unicode_keyword = str_to_cstring(unicode_keyword);
let legacy_keyword: Option<ffi::CString> = unsafe {
let ptr = versioned_function!(uloc_toLegacyKey)(unicode_keyword.as_ptr());
ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
};
legacy_keyword.map(|cstring| cstring_to_string(&cstring))
}
/// Infallibly converts a Rust string to a `CString`. If there's an interior NUL, the string is
/// truncated up to that point.
fn str_to_cstring(input: &str) -> ffi::CString {
ffi::CString::new(input)
.unwrap_or_else(|e| ffi::CString::new(&input[0..e.nul_position()]).unwrap())
}
/// Infallibly converts a `CString` to a Rust `String`. We can safely assume that any strings
/// coming from ICU data are valid UTF-8.
fn cstring_to_string(input: &ffi::CString) -> String {
input.to_string_lossy().to_string()
}
#[cfg(test)]
mod tests {
use {super::*, anyhow::Error};
#[test]
fn test_language() -> Result<(), Error> {
let loc = ULoc::try_from("es-CO")?;
assert_eq!(loc.language(), Some("es".to_string()));
Ok(())
}
#[test]
fn test_language_absent() -> Result<(), Error> {
let loc = ULoc::for_language_tag("und-CO")?;
assert_eq!(loc.language(), None);
Ok(())
}
#[test]
fn test_script() -> Result<(), Error> {
let loc = ULoc::try_from("sr-Cyrl")?;
assert_eq!(loc.script(), Some("Cyrl".to_string()));
Ok(())
}
#[test]
fn test_script_absent() -> Result<(), Error> {
let loc = ULoc::try_from("sr")?;
assert_eq!(loc.script(), None);
Ok(())
}
#[test]
fn test_country() -> Result<(), Error> {
let loc = ULoc::try_from("es-CO")?;
assert_eq!(loc.country(), Some("CO".to_string()));
Ok(())
}
#[test]
fn test_country_absent() -> Result<(), Error> {
let loc = ULoc::try_from("es")?;
assert_eq!(loc.country(), None);
Ok(())
}
// This test yields a different result in ICU versions prior to 64:
// "zh-Latn@collation=pinyin".
#[cfg(features = "icu_version_64_plus")]
#[test]
fn test_variant() -> Result<(), Error> {
let loc = ULoc::try_from("zh-Latn-pinyin")?;
assert_eq!(
loc.variant(),
Some("PINYIN".to_string()),
"locale was: {:?}",
loc
);
Ok(())
}
#[test]
fn test_variant_absent() -> Result<(), Error> {
let loc = ULoc::try_from("zh-Latn")?;
assert_eq!(loc.variant(), None);
Ok(())
}
#[test]
fn test_default_locale() {
let loc = ULoc::try_from("fr-fr").expect("get fr_FR locale");
set_default(&loc).expect("successful set of locale");
assert_eq!(get_default().label(), loc.label());
assert_eq!(loc.label(), "fr_FR", "The locale should get canonicalized");
let loc = ULoc::try_from("en-us").expect("get en_US locale");
set_default(&loc).expect("successful set of locale");
assert_eq!(get_default().label(), loc.label());
}
#[test]
fn test_add_likely_subtags() {
let loc = ULoc::try_from("en-US").expect("get en_US locale");
let with_likely_subtags = loc.add_likely_subtags().expect("should add likely subtags");
let expected = ULoc::try_from("en_Latn_US").expect("get en_Latn_US locale");
assert_eq!(with_likely_subtags.label(), expected.label());
}
#[test]
fn test_minimize_subtags() {
let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale");
let minimized_subtags = loc.minimize_subtags().expect("should minimize subtags");
let expected = ULoc::try_from("sr").expect("get sr locale");
assert_eq!(minimized_subtags.label(), expected.label());
}
#[test]
fn test_to_language_tag() {
let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale");
let language_tag = loc
.to_language_tag(true)
.expect("should convert to language tag");
assert_eq!(language_tag, "sr-Cyrl-RS".to_string());
}
#[test]
fn test_keywords() -> Result<(), Error> {
let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
let keywords: Vec<String> = loc.keywords().collect();
assert_eq!(
keywords,
vec![
"calendar".to_string(),
"fw".to_string(),
"numbers".to_string(),
"timezone".to_string()
]
);
Ok(())
}
#[test]
fn test_keywords_empty() -> Result<(), Error> {
let loc = ULoc::for_language_tag("az-Cyrl-AZ")?;
let keywords: Vec<String> = loc.keywords().collect();
assert!(keywords.is_empty());
Ok(())
}
#[test]
fn test_unicode_keywords() -> Result<(), Error> {
let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
let keywords: Vec<String> = loc.unicode_keywords().collect();
assert_eq!(
keywords,
vec![
"ca".to_string(),
"fw".to_string(),
"nu".to_string(),
"tz".to_string()
]
);
Ok(())
}
#[test]
fn test_unicode_keywords_empty() -> Result<(), Error> {
let loc = ULoc::for_language_tag("az-Cyrl-AZ")?;
let keywords: Vec<String> = loc.unicode_keywords().collect();
assert!(keywords.is_empty());
Ok(())
}
#[test]
fn test_keyword_value() -> Result<(), Error> {
let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
assert_eq!(loc.keyword_value("calendar")?, Some("hebrew".to_string()));
assert_eq!(loc.keyword_value("collation")?, None);
Ok(())
}
#[test]
fn test_unicode_keyword_value() -> Result<(), Error> {
let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
assert_eq!(loc.unicode_keyword_value("ca")?, Some("hebrew".to_string()));
assert_eq!(loc.unicode_keyword_value("fw")?, Some("sunday".to_string()));
assert_eq!(loc.unicode_keyword_value("co")?, None);
Ok(())
}
#[test]
fn test_order() -> Result<(), Error> {
assert!(ULoc::for_language_tag("az")? < ULoc::for_language_tag("az-Cyrl")?);
assert!(ULoc::for_language_tag("az-Cyrl")? < ULoc::for_language_tag("az-Cyrl-AZ")?);
assert!(
ULoc::for_language_tag("az-Cyrl-AZ")? < ULoc::for_language_tag("az-Cyrl-AZ-variant")?
);
assert!(
ULoc::for_language_tag("az-Cyrl-AZ-variant")?
< ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")?
);
assert!(
ULoc::for_language_tag("az-u-ca-gregory")? < ULoc::for_language_tag("az-u-fw-fri")?
);
assert!(
ULoc::for_language_tag("az-u-ca-buddhist")?
< ULoc::for_language_tag("az-u-ca-chinese")?
);
assert!(ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-tue")?);
assert!(
ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-mon-nu-arab")?
);
assert!(
ULoc::for_language_tag("az-u-fw-mon-nu-arab")? > ULoc::for_language_tag("az-u-fw-mon")?
);
let loc = ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")?;
assert_eq!(loc.cmp(&loc), Ordering::Equal,);
Ok(())
}
#[test]
fn test_accept_language_fallback() {
let accept_list: Result<Vec<_>, _> = vec!["es_MX", "ar_EG", "fr_FR"]
.into_iter()
.map(ULoc::try_from)
.collect();
let accept_list = accept_list.expect("make accept_list");
let available_locales: Result<Vec<_>, _> =
vec!["de_DE", "en_US", "es", "nl_NL", "sr_RS_Cyrl"]
.into_iter()
.map(ULoc::try_from)
.collect();
let available_locales = available_locales.expect("make available_locales");
let actual = accept_language(accept_list, available_locales).expect("call accept_language");
assert_eq!(
actual,
(
ULoc::try_from("es").ok(),
UAcceptResult::ULOC_ACCEPT_FALLBACK
)
);
}
// This tests verifies buggy behavior which is fixed since ICU version 67.1
#[cfg(not(feature="icu_version_67_plus"))]
#[test]
fn test_accept_language_exact_match() {
let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
.into_iter()
.map(ULoc::try_from)
.collect();
let accept_list = accept_list.expect("make accept_list");
let available_locales: Result<Vec<_>, _> = vec!["de_DE", "en_US", "es_MX", "ar_EG"]
.into_iter()
.map(ULoc::try_from)
.collect();
let available_locales = available_locales.expect("make available_locales");
let actual = accept_language(accept_list, available_locales).expect("call accept_language");
assert_eq!(
actual,
(
// "es_MX" should be preferred as a fallback over exact match "ar_EG".
ULoc::try_from("ar_EG").ok(),
UAcceptResult::ULOC_ACCEPT_VALID
)
);
}
#[cfg(feature="icu_version_67_plus")]
#[test]
fn test_accept_language_exact_match() {
let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
.into_iter()
.map(ULoc::try_from)
.collect();
let accept_list = accept_list.expect("make accept_list");
let available_locales: Result<Vec<_>, _> = vec!["de_DE", "en_US", "es_MX", "ar_EG"]
.into_iter()
.map(ULoc::try_from)
.collect();
let available_locales = available_locales.expect("make available_locales");
let actual = accept_language(accept_list, available_locales).expect("call accept_language");
assert_eq!(
actual,
(
ULoc::try_from("es_MX").ok(),
UAcceptResult::ULOC_ACCEPT_FALLBACK,
)
);
}
#[test]
fn test_accept_language_no_match() {
let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
.into_iter()
.map(ULoc::try_from)
.collect();
let accept_list = accept_list.expect("make accept_list");
let available_locales: Result<Vec<_>, _> =
vec!["el_GR"].into_iter().map(ULoc::try_from).collect();
let available_locales = available_locales.expect("make available_locales");
let actual = accept_language(accept_list, available_locales).expect("call accept_language");
assert_eq!(actual, (None, UAcceptResult::ULOC_ACCEPT_FAILED))
}
#[test]
fn test_to_unicode_locale_key() -> Result<(), Error> {
let actual = to_unicode_locale_key("calendar");
assert_eq!(actual, Some("ca".to_string()));
Ok(())
}
#[test]
fn test_to_unicode_locale_type() -> Result<(), Error> {
let actual = to_unicode_locale_type("co", "phonebook");
assert_eq!(actual, Some("phonebk".to_string()));
Ok(())
}
#[test]
fn test_to_legacy_key() -> Result<(), Error> {
let actual = to_legacy_key("ca");
assert_eq!(actual, Some("calendar".to_string()));
Ok(())
}
#[test]
fn test_str_to_cstring() -> Result<(), Error> {
assert_eq!(str_to_cstring("abc"), ffi::CString::new("abc")?);
assert_eq!(str_to_cstring("abc\0def"), ffi::CString::new("abc")?);
Ok(())
}
}