Skip to main content

icu_locale_core/preferences/extensions/unicode/keywords/
currency.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::extensions::unicode::{Key, key};
6use crate::preferences::extensions::unicode::errors::PreferencesParseError;
7use crate::preferences::extensions::unicode::struct_keyword;
8use crate::{extensions::unicode::Value, subtags::Subtag};
9use tinystr::TinyAsciiStr;
10
11impl_tinystr_subtag!(
12    /// A Unicode Currency Identifier defines a type of currency.
13    ///
14    /// The valid values are listed in [LDML](https://unicode.org/reports/tr35/#UnicodeCurrencyIdentifier).
15    CurrencyType,
16    preferences::extensions::unicode::keywords,
17    currency,
18    preferences_extensions_unicode_keywords_currency,
19    3..=3,
20    s,
21    s.is_ascii_alphabetic(),
22    s.to_ascii_lowercase(),
23    s.is_ascii_alphabetic() && s.is_ascii_lowercase(),
24    InvalidExtension,
25    ["usd"],
26    ["dollar"],
27);
28
29impl CurrencyType {
30    /// Returns the ISO 4217 3-letter upper case currency code as a [`TinyAsciiStr<3>`].
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use icu_locale_core::preferences::extensions::unicode::keywords::CurrencyType;
36    /// use tinystr::tinystr;
37    ///
38    /// let currency = CurrencyType::try_from_str("usd").unwrap();
39    /// assert_eq!(currency.iso_code(), tinystr!(3, "USD"));
40    /// ```
41    #[inline]
42    pub const fn iso_code(self) -> TinyAsciiStr<3> {
43        self.0.to_ascii_uppercase()
44    }
45}
46
47impl TryFrom<Value> for CurrencyType {
48    type Error = PreferencesParseError;
49    fn try_from(input: Value) -> Result<Self, Self::Error> {
50        Self::try_from(&input)
51    }
52}
53
54impl TryFrom<&Value> for CurrencyType {
55    type Error = PreferencesParseError;
56    fn try_from(input: &Value) -> Result<Self, Self::Error> {
57        if let Some(subtag) = input.as_single_subtag() {
58            let ts = subtag.as_tinystr();
59            if ts.len() == 3 && ts.is_ascii_alphabetic() {
60                return Ok(Self(ts.resize()));
61            }
62        }
63        Err(PreferencesParseError::InvalidKeywordValue)
64    }
65}
66
67impl From<CurrencyType> for Value {
68    fn from(input: CurrencyType) -> Value {
69        (&input).into()
70    }
71}
72impl From<&CurrencyType> for Value {
73    fn from(input: &CurrencyType) -> Value {
74        Value::from_subtag(Some(Subtag::from_tinystr_unvalidated(input.0.resize())))
75    }
76}
77impl crate::preferences::PreferenceKey for CurrencyType {
78    fn unicode_extension_key() -> Option<Key> {
79        Some(Self::UNICODE_EXTENSION_KEY)
80    }
81    fn try_from_key_value(key: &Key, value: &Value) -> Result<Option<Self>, PreferencesParseError> {
82        if Self::UNICODE_EXTENSION_KEY == *key {
83            let result = Self::try_from(value.clone())?;
84            Ok(Some(result))
85        } else {
86            Ok(None)
87        }
88    }
89    fn unicode_extension_value(&self) -> Option<Value> {
90        Some(self.into())
91    }
92}
93impl CurrencyType {
94    pub(crate) const UNICODE_EXTENSION_KEY: Key = key!("cu");
95}
96impl core::ops::Deref for CurrencyType {
97    type Target = TinyAsciiStr<3>;
98    fn deref(&self) -> &Self::Target {
99        &self.0
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use tinystr::tinystr;
107
108    #[test]
109    fn test_valid_currency_types() {
110        let valid = [
111            ("USD", "usd", "USD"),
112            ("uSd", "usd", "USD"),
113            ("usd", "usd", "USD"),
114            ("EUR", "eur", "EUR"),
115            ("JPY", "jpy", "JPY"),
116        ];
117        for (input, expected_subtag, expected_iso) in valid {
118            let parsed = CurrencyType::try_from_str(input).unwrap();
119            let expected_ts_iso = TinyAsciiStr::<3>::try_from_str(expected_iso).unwrap();
120            assert_eq!(parsed.as_str(), expected_subtag);
121            assert_eq!(parsed.iso_code(), expected_ts_iso);
122            assert_eq!(parsed, input.parse::<CurrencyType>().unwrap());
123        }
124    }
125
126    #[test]
127    fn test_invalid_currency_types() {
128        let invalid = [
129            "", "U", "US", "USDDD", "US1", "123", "U$D", " US", "US ", "ÉUR",
130        ];
131        for input in invalid {
132            assert!(CurrencyType::try_from_str(input).is_err());
133            assert!(input.parse::<CurrencyType>().is_err());
134        }
135    }
136}