Skip to main content

icu_locale_core/extensions/unicode/
subdivision.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 core::str::FromStr;
6
7use crate::parser::ParseError;
8use crate::subtags::{Region, Subtag};
9
10#[repr(transparent)]
#[doc = r" A subdivision suffix used in [`SubdivisionId`]."]
#[doc = r""]
#[doc =
r" This suffix represents a specific subdivision code under a given [`Region`]."]
#[doc =
r" For example the value of [`SubdivisionId`] may be `gbsct`, where the [`SubdivisionSuffix`]"]
#[doc = r" is `sct` for Scotland."]
#[doc = r""]
#[doc =
r" Such a value associated with a key `rg` means that the locale should use Unit Preferences"]
#[doc =
r" (default calendar, currency, week data, time cycle, measurement system) for Scotland, even if the"]
#[doc = r" [`LanguageIdentifier`](crate::LanguageIdentifier) is `en-US`."]
#[doc = r""]
#[doc =
r" A subdivision suffix has to be a sequence of alphanumerical characters no"]
#[doc = r" shorter than one and no longer than four characters."]
#[doc = r""]
#[doc = r""]
#[doc = r" # Examples"]
#[doc = r""]
#[doc = r" ```"]
#[doc = r" use icu::locale::extensions::unicode::{"]
#[doc = r"     SubdivisionSuffix, subdivision_suffix,"]
#[doc = r" };"]
#[doc = r""]
#[doc = r" let ss: SubdivisionSuffix ="]
#[doc =
r#"     "sct".parse().expect("Failed to parse a SubdivisionSuffix.");"#]
#[doc = r""]
#[doc = r#" assert_eq!(ss, subdivision_suffix!("sct"));"#]
#[doc = r" ```"]
pub struct SubdivisionSuffix(tinystr::TinyAsciiStr<4>);
#[automatically_derived]impl ::core::fmt::Debug for SubdivisionSuffix {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "SubdivisionSuffix", &&self.0)
    }
}
#[automatically_derived]impl ::core::marker::StructuralPartialEq for
    SubdivisionSuffix {
}
#[automatically_derived]impl ::core::cmp::PartialEq for SubdivisionSuffix {
    #[inline]
    fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}
#[automatically_derived]impl ::core::cmp::Eq for SubdivisionSuffix {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<tinystr::TinyAsciiStr<4>>;
    }
}
#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for SubdivisionSuffix {
}
#[automatically_derived]impl ::core::clone::Clone for SubdivisionSuffix {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<tinystr::TinyAsciiStr<4>>;
        *self
    }
}
#[automatically_derived]impl ::core::hash::Hash for SubdivisionSuffix {
    #[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 SubdivisionSuffix {
    #[inline]
    fn partial_cmp(&self, other: &Self)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}
#[automatically_derived]impl ::core::cmp::Ord for SubdivisionSuffix {
    #[inline]
    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}
#[automatically_derived]impl ::core::marker::Copy for SubdivisionSuffix { }
impl SubdivisionSuffix {
    /// A constructor which takes a str slice, parses it and
    #[doc = "produces a well-formed [`SubdivisionSuffix`]."]
    ///
    /// # Examples
    ///
    /// ```
    #[doc = "use icu_locale_core::extensions :: unicode ::SubdivisionSuffix;"]
    ///
    #[doc = "assert!(SubdivisionSuffix::try_from_str(\"sct\").is_ok());"]
    #[doc =
    "assert!(SubdivisionSuffix::try_from_str(\"toolooong\").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() < 1 || code_units.len() > 4 {
            return Err(crate::parser::errors::ParseError::InvalidExtension);
        }
        match tinystr::TinyAsciiStr::try_from_utf8(code_units) {
            Ok(s) if s.is_ascii_alphanumeric() =>
                Ok(Self(s.to_ascii_lowercase())),
            _ => Err(crate::parser::errors::ParseError::InvalidExtension),
        }
    }
    #[doc = "Safely creates a [`SubdivisionSuffix`] 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; 4])
        -> Result<Self, crate::parser::errors::ParseError> {
        if let Ok(s) = tinystr::TinyAsciiStr::<4>::try_from_raw(raw) {
            if s.len() >= 1 &&
                    (s.is_ascii_alphanumeric() && 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 [`SubdivisionSuffix`] 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; 4]) -> 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; 4] { *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<4> { 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 SubdivisionSuffix {
    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 SubdivisionSuffix> for &'l str {
    fn from(input: &'l SubdivisionSuffix) -> Self { input.as_str() }
}
impl From<SubdivisionSuffix> for tinystr::TinyAsciiStr<4> {
    fn from(input: SubdivisionSuffix) -> Self { input.to_tinystr() }
}
impl ::writeable::Writeable for SubdivisionSuffix {
    #[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 SubdivisionSuffix {
    #[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 [`SubdivisionSuffix`] subtags."]
///
/// # Examples
///
/// Parsing errors don't have to be handled at runtime:
/// ```
/// assert_eq!(
#[doc =
"  icu_locale_core::extensions::unicode::subdivision_suffix!(\"sct\"),"]
#[doc =
"  \"sct\".parse::<icu_locale_core::extensions::unicode::SubdivisionSuffix>().unwrap()"]
/// );
/// ```
///
/// Invalid input is a compile failure:
/// ```compile_fail,E0080
#[doc =
"icu_locale_core::extensions::unicode::subdivision_suffix!(\"toolooong\");"]
/// ```
///
#[doc =
"[`SubdivisionSuffix`]: crate::extensions::unicode::SubdivisionSuffix"]
#[macro_export]
#[doc(hidden)]
macro_rules! extensions_unicode_subdivision_suffix {
    ($string : literal) =>
    {
        const
        {
            use crate :: extensions :: unicode :: SubdivisionSuffix; match
            SubdivisionSuffix :: try_from_utf8($string.as_bytes())
            {
                Ok(r) => r, _ => panic!
                (concat!
                ("Invalid ", stringify! (extensions), "::", stringify!
                (unicode), "::", stringify! (SubdivisionSuffix), ": ",
                $string)),
            }
        }
    };
}
#[doc(inline)]
pub use extensions_unicode_subdivision_suffix as subdivision_suffix;
unsafe impl zerovec::ule::ULE for SubdivisionSuffix {
    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<4> for SubdivisionSuffix {
    const NICHE_BIT_PATTERN: [u8; 4] =
        <tinystr::TinyAsciiStr<4>>::NICHE_BIT_PATTERN;
}
impl zerovec::ule::AsULE for SubdivisionSuffix {
    type ULE = Self;
    fn to_unaligned(self) -> Self::ULE { self }
    fn from_unaligned(unaligned: Self::ULE) -> Self { unaligned }
}impl_tinystr_subtag!(
11    /// A subdivision suffix used in [`SubdivisionId`].
12    ///
13    /// This suffix represents a specific subdivision code under a given [`Region`].
14    /// For example the value of [`SubdivisionId`] may be `gbsct`, where the [`SubdivisionSuffix`]
15    /// is `sct` for Scotland.
16    ///
17    /// Such a value associated with a key `rg` means that the locale should use Unit Preferences
18    /// (default calendar, currency, week data, time cycle, measurement system) for Scotland, even if the
19    /// [`LanguageIdentifier`](crate::LanguageIdentifier) is `en-US`.
20    ///
21    /// A subdivision suffix has to be a sequence of alphanumerical characters no
22    /// shorter than one and no longer than four characters.
23    ///
24    ///
25    /// # Examples
26    ///
27    /// ```
28    /// use icu::locale::extensions::unicode::{
29    ///     SubdivisionSuffix, subdivision_suffix,
30    /// };
31    ///
32    /// let ss: SubdivisionSuffix =
33    ///     "sct".parse().expect("Failed to parse a SubdivisionSuffix.");
34    ///
35    /// assert_eq!(ss, subdivision_suffix!("sct"));
36    /// ```
37    SubdivisionSuffix,
38    extensions::unicode,
39    subdivision_suffix,
40    extensions_unicode_subdivision_suffix,
41    1..=4,
42    s,
43    s.is_ascii_alphanumeric(),
44    s.to_ascii_lowercase(),
45    s.is_ascii_alphanumeric() && s.is_ascii_lowercase(),
46    InvalidExtension,
47    ["sct"],
48    ["toolooong"],
49);
50
51impl SubdivisionSuffix {
52    pub(crate) const UNKNOWN: Self = const {
        use crate::extensions::unicode::SubdivisionSuffix;
        match SubdivisionSuffix::try_from_utf8("zzzz".as_bytes()) {
            Ok(r) => r,
            _ => {
                ::core::panicking::panic_fmt(format_args!("Invalid extensions::unicode::SubdivisionSuffix: zzzz"));
            }
        }
    }subdivision_suffix!("zzzz");
53
54    pub(crate) fn is_unknown(self) -> bool {
55        self == Self::UNKNOWN
56    }
57}
58
59/// A Subivision Id as defined in [`Unicode Locale Identifier`].
60///
61/// Subdivision Id is used in [`Unicode`] extensions:
62///  * `rg` - Regional Override
63///  * `sd` - Regional Subdivision
64///
65/// In both cases the subdivision is composed of a [`Region`] and a [`SubdivisionSuffix`] which represents
66/// different meaning depending on the key.
67///
68/// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/tr35.html#unicode_subdivision_id
69/// [`Unicode`]: crate::extensions::unicode::Unicode
70///
71/// # Examples
72///
73/// ```
74/// use icu::locale::{
75///     extensions::unicode::{SubdivisionId, subdivision_suffix},
76///     subtags::region,
77/// };
78///
79/// // "zzzz" means "unknown subdivision"
80/// let ss = subdivision_suffix!("zzzz");
81/// let region = region!("gb");
82///
83/// let si = SubdivisionId::new(region, ss);
84///
85/// assert_eq!(si.to_string(), "gbzzzz");
86/// ```
87#[derive(#[automatically_derived]impl ::core::fmt::Debug for SubdivisionId {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "SubdivisionId region suffix", &self.region, &&self.suffix)
    }
}Debug, #[automatically_derived]impl ::core::marker::StructuralPartialEq for
    SubdivisionId {
}
#[automatically_derived]impl ::core::cmp::PartialEq for SubdivisionId {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.region == other.region && self.suffix == other.suffix
    }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for SubdivisionId {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Region>;
        let _: ::core::cmp::AssertParamIsEq<SubdivisionSuffix>;
    }
}Eq, #[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for SubdivisionId {
}
#[automatically_derived]impl ::core::clone::Clone for SubdivisionId {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<Region>;
        let _: ::core::clone::AssertParamIsClone<SubdivisionSuffix>;
        *self
    }
}Clone, #[automatically_derived]impl ::core::hash::Hash for SubdivisionId {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.region, state);
        ::core::hash::Hash::hash(&self.suffix, state)
    }
}Hash, #[automatically_derived]impl ::core::cmp::PartialOrd for SubdivisionId {
    #[inline]
    fn partial_cmp(&self, other: &Self)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}PartialOrd, #[automatically_derived]impl ::core::cmp::Ord for SubdivisionId {
    #[inline]
    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
        match ::core::cmp::Ord::cmp(&self.region, &other.region) {
            ::core::cmp::Ordering::Equal =>
                ::core::cmp::Ord::cmp(&self.suffix, &other.suffix),
            cmp => cmp,
        }
    }
}Ord, #[automatically_derived]impl ::core::marker::Copy for SubdivisionId { }Copy)]
88#[non_exhaustive]
89pub struct SubdivisionId {
90    /// A region field of a Subdivision Id.
91    pub region: Region,
92    /// A subdivision suffix field of a Subdivision Id.
93    pub suffix: SubdivisionSuffix,
94}
95
96impl SubdivisionId {
97    /// Returns a new [`SubdivisionId`].
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use icu::locale::{
103    ///     extensions::unicode::{SubdivisionId, subdivision_suffix},
104    ///     subtags::region,
105    /// };
106    ///
107    /// let ss = subdivision_suffix!("zzzz");
108    /// let region = region!("gb");
109    ///
110    /// let si = SubdivisionId::new(region, ss);
111    ///
112    /// assert_eq!(si.to_string(), "gbzzzz");
113    /// ```
114    pub const fn new(region: Region, suffix: SubdivisionSuffix) -> Self {
115        Self { region, suffix }
116    }
117
118    /// A constructor which takes a str slice, parses it and
119    /// produces a well-formed [`SubdivisionId`].
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use icu::locale::extensions::unicode::SubdivisionId;
125    /// use writeable::assert_writeable_eq;
126    ///
127    /// let subdivision = SubdivisionId::try_from_str("gbeng").unwrap();
128    ///
129    /// assert_writeable_eq!(subdivision, "gbeng");
130    /// assert_writeable_eq!(subdivision.region, "GB");
131    /// assert_writeable_eq!(subdivision.suffix, "eng");
132    /// ```
133    ///
134    /// When the value can't be parsed:
135    ///
136    /// ```
137    /// use icu::locale::ParseError;
138    /// use icu::locale::extensions::unicode::SubdivisionId;
139    ///
140    /// // Value is too short
141    /// assert!(matches!(
142    ///     SubdivisionId::try_from_str("zz"),
143    ///     Err(ParseError::InvalidExtension),
144    /// ));
145    ///
146    /// // Value is too long
147    /// assert!(matches!(
148    ///     SubdivisionId::try_from_str("abcdefg"),
149    ///     Err(ParseError::InvalidExtension),
150    /// ));
151    ///
152    /// // Value does not start with a valid region code
153    /// assert!(matches!(
154    ///     SubdivisionId::try_from_str("a0zzzz"),
155    ///     Err(ParseError::InvalidExtension),
156    /// ));
157    /// assert!(matches!(
158    ///     SubdivisionId::try_from_str("0azzzz"),
159    ///     Err(ParseError::InvalidExtension),
160    /// ));
161    /// ```
162    #[inline]
163    pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
164        Self::try_from_utf8(s.as_bytes())
165    }
166
167    /// See [`Self::try_from_str`]
168    pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
169        Self::try_from_subtag(Subtag::try_from_utf8(code_units)?)
170    }
171
172    pub(crate) const fn try_from_subtag(subtag: Subtag) -> Result<Self, ParseError> {
173        let code_units = subtag.as_str().as_bytes();
174        let Some(first) = code_units.first() else {
175            return Err(ParseError::InvalidExtension);
176        };
177        let region_len = if first.is_ascii_alphabetic() {
178            2
179        } else if first.is_ascii_digit() {
180            3
181        } else {
182            return Err(ParseError::InvalidExtension);
183        };
184        let Some((region_code_units, suffix_code_units)) = code_units.split_at_checked(region_len)
185        else {
186            return Err(ParseError::InvalidExtension);
187        };
188        let Ok(region) = Region::try_from_utf8(region_code_units) else {
189            return Err(ParseError::InvalidExtension);
190        };
191        let Ok(suffix) = SubdivisionSuffix::try_from_utf8(suffix_code_units) else {
192            return Err(ParseError::InvalidExtension);
193        };
194        Ok(Self { region, suffix })
195    }
196
197    /// Convert to [`Subtag`]
198    pub const fn into_subtag(self) -> Subtag {
199        let result = self
200            .region
201            .to_tinystr()
202            .to_ascii_lowercase()
203            .concat(self.suffix.to_tinystr());
204        Subtag::from_tinystr_unvalidated(result)
205    }
206}
207
208impl writeable::Writeable for SubdivisionId {
209    #[inline]
210    fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
211        sink.write_str(self.region.to_tinystr().to_ascii_lowercase().as_str())?;
212        sink.write_str(self.suffix.as_str())
213    }
214
215    #[inline]
216    fn writeable_length_hint(&self) -> writeable::LengthHint {
217        self.region.writeable_length_hint() + self.suffix.writeable_length_hint()
218    }
219}
220
221/// 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 SubdivisionId {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        ::writeable::Writeable::write_to(&self, f)
    }
}writeable::impl_display_with_writeable!(SubdivisionId, #[cfg(feature = "alloc")]);
222
223impl FromStr for SubdivisionId {
224    type Err = ParseError;
225
226    #[inline]
227    fn from_str(s: &str) -> Result<Self, Self::Err> {
228        Self::try_from_str(s)
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_subdivisionid_fromstr() {
238        let si: SubdivisionId = "gbzzzz".parse().expect("Failed to parse SubdivisionId");
239        assert_eq!(si.region.to_string(), "GB");
240        assert_eq!(si.suffix.to_string(), "zzzz");
241        assert_eq!(si.to_string(), "gbzzzz");
242
243        for sample in ["", "gb", "o"] {
244            let oe: Result<SubdivisionId, _> = sample.parse();
245            assert!(oe.is_err(), "Should fail: {sample}");
246        }
247    }
248}