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
11#[repr(transparent)]
#[doc = r" A Unicode Currency Identifier defines a type of currency."]
#[doc = r""]
#[doc =
r" The valid values are listed in [LDML](https://unicode.org/reports/tr35/#UnicodeCurrencyIdentifier)."]
pub struct CurrencyType(tinystr::TinyAsciiStr<3>);
#[automatically_derived]
impl ::core::fmt::Debug for CurrencyType {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "CurrencyType",
            &&self.0)
    }
}
#[automatically_derived]
impl ::core::marker::StructuralPartialEq for CurrencyType { }
#[automatically_derived]
impl ::core::cmp::PartialEq for CurrencyType {
    #[inline]
    fn eq(&self, other: &CurrencyType) -> bool { self.0 == other.0 }
}
#[automatically_derived]
impl ::core::cmp::Eq for CurrencyType {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<tinystr::TinyAsciiStr<3>>;
    }
}
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for CurrencyType { }
#[automatically_derived]
impl ::core::clone::Clone for CurrencyType {
    #[inline]
    fn clone(&self) -> CurrencyType {
        let _: ::core::clone::AssertParamIsClone<tinystr::TinyAsciiStr<3>>;
        *self
    }
}
#[automatically_derived]
impl ::core::hash::Hash for CurrencyType {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}
#[automatically_derived]
impl ::core::cmp::PartialOrd for CurrencyType {
    #[inline]
    fn partial_cmp(&self, other: &CurrencyType)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}
#[automatically_derived]
impl ::core::cmp::Ord for CurrencyType {
    #[inline]
    fn cmp(&self, other: &CurrencyType) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}
#[automatically_derived]
impl ::core::marker::Copy for CurrencyType { }
impl CurrencyType {
    /// A constructor which takes a str slice, parses it and
    #[doc = "produces a well-formed [`CurrencyType`]."]
    ///
    /// # Examples
    ///
    /// ```
    #[doc =
    "use icu_locale_core::preferences :: extensions :: unicode :: keywords ::CurrencyType;"]
    ///
    #[doc = "assert!(CurrencyType::try_from_str(\"usd\").is_ok());"]
    #[doc = "assert!(CurrencyType::try_from_str(\"dollar\").is_err());"]
    /// ```
    #[inline]
    pub const fn try_from_str(s: &str)
        -> Result<Self, crate::parser::errors::ParseError> {
        Self::try_from_utf8(s.as_bytes())
    }
    /// See [`Self::try_from_str`]
    pub const fn try_from_utf8(code_units: &[u8])
        -> Result<Self, crate::parser::errors::ParseError> {
        if code_units.len() < 3 || code_units.len() > 3 {
            return Err(crate::parser::errors::ParseError::InvalidExtension);
        }
        match tinystr::TinyAsciiStr::try_from_utf8(code_units) {
            Ok(s) if s.is_ascii_alphabetic() =>
                Ok(Self(s.to_ascii_lowercase())),
            _ => Err(crate::parser::errors::ParseError::InvalidExtension),
        }
    }
    #[doc = "Safely creates a [`CurrencyType`] from its raw format"]
    /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
    /// this constructor only takes normalized values.
    pub const fn try_from_raw(raw: [u8; 3])
        -> Result<Self, crate::parser::errors::ParseError> {
        if let Ok(s) = tinystr::TinyAsciiStr::<3>::try_from_raw(raw) {
            if s.len() >= 3 &&
                    (s.is_ascii_alphabetic() && s.is_ascii_lowercase()) {
                Ok(Self(s))
            } else {
                Err(crate::parser::errors::ParseError::InvalidExtension)
            }
        } else { Err(crate::parser::errors::ParseError::InvalidExtension) }
    }
    #[doc = "Unsafely creates a [`CurrencyType`] from its raw format"]
    /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
    /// this constructor only takes normalized values.
    ///
    /// # Safety
    ///
    /// This function is safe iff [`Self::try_from_raw`] returns an `Ok`. This is the case
    /// for inputs that are correctly normalized.
    pub const unsafe fn from_raw_unchecked(v: [u8; 3]) -> Self {
        unsafe { Self(tinystr::TinyAsciiStr::from_utf8_unchecked(v)) }
    }
    /// Deconstructs into a raw format to be consumed by
    /// [`from_raw_unchecked`](Self::from_raw_unchecked()) or
    /// [`try_from_raw`](Self::try_from_raw()).
    pub const fn into_raw(self) -> [u8; 3] { *self.0.all_bytes() }
    #[inline]
    /// A helper function for displaying as a `&str`.
    pub const fn as_str(&self) -> &str { self.0.as_str() }
    #[doc(hidden)]
    pub const fn to_tinystr(&self) -> tinystr::TinyAsciiStr<3> { self.0 }
    /// Compare with BCP-47 bytes.
    ///
    /// The return value is equivalent to what would happen if you first converted
    /// `self` to a BCP-47 string and then performed a byte comparison.
    ///
    /// This function is case-sensitive and results in a *total order*, so it is appropriate for
    /// binary search. The only argument producing [`Ordering::Equal`](core::cmp::Ordering::Equal)
    /// is `self.as_str().as_bytes()`.
    #[inline]
    pub fn strict_cmp(self, other: &[u8]) -> core::cmp::Ordering {
        self.as_str().as_bytes().cmp(other)
    }
    /// Compare with a potentially unnormalized BCP-47 string.
    ///
    /// The return value is equivalent to what would happen if you first parsed the
    /// BCP-47 string and then performed a structural comparison.
    ///
    #[inline]
    pub fn normalizing_eq(self, other: &str) -> bool {
        self.as_str().eq_ignore_ascii_case(other)
    }
}
impl core::str::FromStr for CurrencyType {
    type Err = crate::parser::errors::ParseError;
    #[inline]
    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::try_from_str(s) }
}
impl<'l> From<&'l CurrencyType> for &'l str {
    fn from(input: &'l CurrencyType) -> Self { input.as_str() }
}
impl From<CurrencyType> for tinystr::TinyAsciiStr<3> {
    fn from(input: CurrencyType) -> Self { input.to_tinystr() }
}
impl ::writeable::Writeable for CurrencyType {
    #[inline]
    fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W)
        -> core::fmt::Result {
        (self.as_str()).write_to(sink)
    }
    #[inline]
    fn write_to_parts<S: ::writeable::PartsWrite +
        ?Sized>(&self, sink: &mut S) -> core::fmt::Result {
        (self.as_str()).write_to_parts(sink)
    }
    #[inline]
    fn writeable_length_hint(&self) -> ::writeable::LengthHint {
        (self.as_str()).writeable_length_hint()
    }
    #[inline]
    fn writeable_borrow(&self) -> Option<&str> {
        (self.as_str()).writeable_borrow()
    }
}
/// This trait is implemented for compatibility with [`fmt!`](core::fmt).
/// To create a string, [`Writeable::write_to_string`] is usually more efficient.
impl core::fmt::Display for CurrencyType {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        ::writeable::Writeable::write_to(&self, f)
    }
}
#[doc =
"A macro allowing for compile-time construction of valid [`CurrencyType`] subtags."]
///
/// # Examples
///
/// Parsing errors don't have to be handled at runtime:
/// ```
/// assert_eq!(
#[doc =
"  icu_locale_core::preferences::extensions::unicode::keywords::currency!(\"usd\"),"]
#[doc =
"  \"usd\".parse::<icu_locale_core::preferences::extensions::unicode::keywords::CurrencyType>().unwrap()"]
/// );
/// ```
///
/// Invalid input is a compile failure:
/// ```compile_fail,E0080
#[doc =
"icu_locale_core::preferences::extensions::unicode::keywords::currency!(\"dollar\");"]
/// ```
///
#[doc =
"[`CurrencyType`]: crate::preferences::extensions::unicode::keywords::CurrencyType"]
#[macro_export]
#[doc(hidden)]
macro_rules! preferences_extensions_unicode_keywords_currency {
    ($string : literal) =>
    {
        const
        {
            use crate :: preferences :: extensions :: unicode :: keywords ::
            CurrencyType; match CurrencyType ::
            try_from_utf8($string.as_bytes())
            {
                Ok(r) => r, _ => panic!
                (concat!
                ("Invalid ", stringify! (preferences), "::", stringify!
                (extensions), "::", stringify! (unicode), "::", stringify!
                (keywords), "::", stringify! (CurrencyType), ": ", $string)),
            }
        }
    };
}
#[doc(inline)]
pub use preferences_extensions_unicode_keywords_currency as currency;
unsafe impl zerovec::ule::ULE for CurrencyType {
    fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> {
        let it = bytes.chunks_exact(size_of::<Self>());
        if !it.remainder().is_empty() {
            return Err(zerovec::ule::UleError::length::<Self>(bytes.len()));
        }
        for v in it {
            let mut a = [0; size_of::<Self>()];
            a.copy_from_slice(v);
            if Self::try_from_raw(a).is_err() {
                return Err(zerovec::ule::UleError::parse::<Self>());
            }
        }
        Ok(())
    }
}
impl zerovec::ule::NicheBytes<3> for CurrencyType {
    const NICHE_BIT_PATTERN: [u8; 3] =
        <tinystr::TinyAsciiStr<3>>::NICHE_BIT_PATTERN;
}
impl zerovec::ule::AsULE for CurrencyType {
    type ULE = Self;
    fn to_unaligned(self) -> Self::ULE { self }
    fn from_unaligned(unaligned: Self::ULE) -> Self { unaligned }
}impl_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 = const {
        use crate::extensions::unicode::Key;
        match Key::try_from_utf8("cu".as_bytes()) {
            Ok(r) => r,
            _ => {
                ::core::panicking::panic_fmt(format_args!("Invalid extensions::unicode::Key: cu"));
            }
        }
    }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}