• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 use core::str::FromStr;
6 
7 use crate::parser::ParseError;
8 use crate::subtags::{Region, Subtag};
9 
10 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::{subdivision_suffix, SubdivisionSuffix};
29     ///
30     /// let ss: SubdivisionSuffix =
31     ///     "sct".parse().expect("Failed to parse a SubdivisionSuffix.");
32     ///
33     /// assert_eq!(ss, subdivision_suffix!("sct"));
34     /// ```
35     SubdivisionSuffix,
36     extensions::unicode,
37     subdivision_suffix,
38     extensions_unicode_subdivision_suffix,
39     1..=4,
40     s,
41     s.is_ascii_alphanumeric(),
42     s.to_ascii_lowercase(),
43     s.is_ascii_alphanumeric() && s.is_ascii_lowercase(),
44     InvalidExtension,
45     ["sct"],
46     ["toolooong"],
47 );
48 
49 /// A Subivision Id as defined in [`Unicode Locale Identifier`].
50 ///
51 /// Subdivision Id is used in [`Unicode`] extensions:
52 ///  * `rg` - Regional Override
53 ///  * `sd` - Regional Subdivision
54 ///
55 /// In both cases the subdivision is composed of a [`Region`] and a [`SubdivisionSuffix`] which represents
56 /// different meaning depending on the key.
57 ///
58 /// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/tr35.html#unicode_subdivision_id
59 /// [`Unicode`]: crate::extensions::unicode::Unicode
60 ///
61 /// # Examples
62 ///
63 /// ```
64 /// use icu::locale::{
65 ///     extensions::unicode::{subdivision_suffix, SubdivisionId},
66 ///     subtags::region,
67 /// };
68 ///
69 /// let ss = subdivision_suffix!("zzzz");
70 /// let region = region!("gb");
71 ///
72 /// let si = SubdivisionId::new(region, ss);
73 ///
74 /// assert_eq!(si.to_string(), "gbzzzz");
75 /// ```
76 #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Copy)]
77 #[non_exhaustive]
78 pub struct SubdivisionId {
79     /// A region field of a Subdivision Id.
80     pub region: Region,
81     /// A subdivision suffix field of a Subdivision Id.
82     pub suffix: SubdivisionSuffix,
83 }
84 
85 impl SubdivisionId {
86     /// Returns a new [`SubdivisionId`].
87     ///
88     /// # Examples
89     ///
90     /// ```
91     /// use icu::locale::{
92     ///     extensions::unicode::{subdivision_suffix, SubdivisionId},
93     ///     subtags::region,
94     /// };
95     ///
96     /// let ss = subdivision_suffix!("zzzz");
97     /// let region = region!("gb");
98     ///
99     /// let si = SubdivisionId::new(region, ss);
100     ///
101     /// assert_eq!(si.to_string(), "gbzzzz");
102     /// ```
new(region: Region, suffix: SubdivisionSuffix) -> Self103     pub const fn new(region: Region, suffix: SubdivisionSuffix) -> Self {
104         Self { region, suffix }
105     }
106 
107     /// A constructor which takes a str slice, parses it and
108     /// produces a well-formed [`SubdivisionId`].
109     #[inline]
try_from_str(s: &str) -> Result<Self, ParseError>110     pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
111         Self::try_from_utf8(s.as_bytes())
112     }
113 
114     /// See [`Self::try_from_str`]
try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError>115     pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
116         let is_alpha = code_units
117             .first()
118             .and_then(|b| {
119                 b.is_ascii_alphabetic()
120                     .then_some(true)
121                     .or_else(|| b.is_ascii_digit().then_some(false))
122             })
123             .ok_or(ParseError::InvalidExtension)?;
124         let region_len = if is_alpha { 2 } else { 3 };
125         let (region_code_units, suffix_code_units) = code_units
126             .split_at_checked(region_len)
127             .ok_or(ParseError::InvalidExtension)?;
128         let region =
129             Region::try_from_utf8(region_code_units).map_err(|_| ParseError::InvalidExtension)?;
130         let suffix = SubdivisionSuffix::try_from_utf8(suffix_code_units)?;
131         Ok(Self { region, suffix })
132     }
133 
134     /// Convert to [`Subtag`]
into_subtag(self) -> Subtag135     pub fn into_subtag(self) -> Subtag {
136         let result = self.region.to_tinystr().concat(self.suffix.to_tinystr());
137         Subtag::from_tinystr_unvalidated(result)
138     }
139 }
140 
141 impl writeable::Writeable for SubdivisionId {
142     #[inline]
write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result143     fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
144         sink.write_str(self.region.to_tinystr().to_ascii_lowercase().as_str())?;
145         sink.write_str(self.suffix.as_str())
146     }
147 
148     #[inline]
writeable_length_hint(&self) -> writeable::LengthHint149     fn writeable_length_hint(&self) -> writeable::LengthHint {
150         self.region.writeable_length_hint() + self.suffix.writeable_length_hint()
151     }
152 }
153 
154 writeable::impl_display_with_writeable!(SubdivisionId);
155 
156 impl FromStr for SubdivisionId {
157     type Err = ParseError;
158 
159     #[inline]
from_str(s: &str) -> Result<Self, Self::Err>160     fn from_str(s: &str) -> Result<Self, Self::Err> {
161         Self::try_from_str(s)
162     }
163 }
164 
165 #[cfg(test)]
166 mod tests {
167     use super::*;
168 
169     #[test]
test_subdivisionid_fromstr()170     fn test_subdivisionid_fromstr() {
171         let si: SubdivisionId = "gbzzzz".parse().expect("Failed to parse SubdivisionId");
172         assert_eq!(si.region.to_string(), "GB");
173         assert_eq!(si.suffix.to_string(), "zzzz");
174         assert_eq!(si.to_string(), "gbzzzz");
175 
176         for sample in ["", "gb", "o"] {
177             let oe: Result<SubdivisionId, _> = sample.parse();
178             assert!(oe.is_err(), "Should fail: {}", sample);
179         }
180     }
181 }
182