icu_locale_core/
helpers.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
5macro_rules! impl_tinystr_subtag {
6    (
7        $(#[$doc:meta])*
8        $name:ident,
9        $($path:ident)::+,
10        $macro_name:ident,
11        $internal_macro_name:ident,
12        $len_start:literal..=$len_end:literal,
13        $tinystr_ident:ident,
14        $validate:expr,
15        $normalize:expr,
16        $is_normalized:expr,
17        $error:ident,
18        [$good_example:literal $(,$more_good_examples:literal)*],
19        [$bad_example:literal $(, $more_bad_examples:literal)*],
20    ) => {
21        #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Copy)]
22        #[repr(transparent)]
23        $(#[$doc])*
24        pub struct $name(tinystr::TinyAsciiStr<$len_end>);
25
26        impl $name {
27            /// A constructor which takes a str slice, parses it and
28            #[doc = concat!("produces a well-formed [`", stringify!($name), "`].")]
29            ///
30            /// # Examples
31            ///
32            /// ```
33            #[doc = concat!("use icu_locale_core::", stringify!($($path::)+), stringify!($name), ";")]
34            ///
35            #[doc = concat!("assert!(", stringify!($name), "::try_from_str(", stringify!($good_example), ").is_ok());")]
36            #[doc = concat!("assert!(", stringify!($name), "::try_from_str(", stringify!($bad_example), ").is_err());")]
37            /// ```
38            #[inline]
39            pub const fn try_from_str(s: &str) -> Result<Self, crate::parser::errors::ParseError> {
40                Self::try_from_utf8(s.as_bytes())
41            }
42
43            /// See [`Self::try_from_str`]
44            pub const fn try_from_utf8(
45                code_units: &[u8],
46            ) -> Result<Self, crate::parser::errors::ParseError> {
47                if code_units.len() < $len_start || code_units.len() > $len_end {
48                    return Err(crate::parser::errors::ParseError::$error);
49                }
50
51                match tinystr::TinyAsciiStr::try_from_utf8(code_units) {
52                    Ok($tinystr_ident) if $validate => Ok(Self($normalize)),
53                    _ => Err(crate::parser::errors::ParseError::$error),
54                }
55            }
56
57            #[doc = concat!("Safely creates a [`", stringify!($name), "`] from its raw format")]
58            /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
59            /// this constructor only takes normalized values.
60            pub const fn try_from_raw(
61                raw: [u8; $len_end],
62            ) -> Result<Self, crate::parser::errors::ParseError> {
63                if let Ok($tinystr_ident) = tinystr::TinyAsciiStr::<$len_end>::try_from_raw(raw) {
64                    if $tinystr_ident.len() >= $len_start && $is_normalized {
65                        Ok(Self($tinystr_ident))
66                    } else {
67                        Err(crate::parser::errors::ParseError::$error)
68                    }
69                } else {
70                    Err(crate::parser::errors::ParseError::$error)
71                }
72            }
73
74            #[doc = concat!("Unsafely creates a [`", stringify!($name), "`] from its raw format")]
75            /// as returned by [`Self::into_raw`]. Unlike [`Self::try_from_utf8`],
76            /// this constructor only takes normalized values.
77            ///
78            /// # Safety
79            ///
80            /// This function is safe iff [`Self::try_from_raw`] returns an `Ok`. This is the case
81            /// for inputs that are correctly normalized.
82            pub const unsafe fn from_raw_unchecked(v: [u8; $len_end]) -> Self {
83                Self(tinystr::TinyAsciiStr::from_utf8_unchecked(v))
84            }
85
86            /// Deconstructs into a raw format to be consumed by
87            /// [`from_raw_unchecked`](Self::from_raw_unchecked()) or
88            /// [`try_from_raw`](Self::try_from_raw()).
89            pub const fn into_raw(self) -> [u8; $len_end] {
90                *self.0.all_bytes()
91            }
92
93            #[inline]
94            /// A helper function for displaying as a `&str`.
95            pub const fn as_str(&self) -> &str {
96                self.0.as_str()
97            }
98
99            #[doc(hidden)]
100            pub const fn to_tinystr(&self) -> tinystr::TinyAsciiStr<$len_end> {
101                self.0
102            }
103
104            /// Compare with BCP-47 bytes.
105            ///
106            /// The return value is equivalent to what would happen if you first converted
107            /// `self` to a BCP-47 string and then performed a byte comparison.
108            ///
109            /// This function is case-sensitive and results in a *total order*, so it is appropriate for
110            /// binary search. The only argument producing [`Ordering::Equal`](core::cmp::Ordering::Equal)
111            /// is `self.as_str().as_bytes()`.
112            #[inline]
113            pub fn strict_cmp(self, other: &[u8]) -> core::cmp::Ordering {
114                self.as_str().as_bytes().cmp(other)
115            }
116
117            /// Compare with a potentially unnormalized BCP-47 string.
118            ///
119            /// The return value is equivalent to what would happen if you first parsed the
120            /// BCP-47 string and then performed a structural comparison.
121            ///
122            #[inline]
123            pub fn normalizing_eq(self, other: &str) -> bool {
124                self.as_str().eq_ignore_ascii_case(other)
125            }
126        }
127
128        impl core::str::FromStr for $name {
129            type Err = crate::parser::errors::ParseError;
130
131            #[inline]
132            fn from_str(s: &str) -> Result<Self, Self::Err> {
133                Self::try_from_str(s)
134            }
135        }
136
137        impl<'l> From<&'l $name> for &'l str {
138            fn from(input: &'l $name) -> Self {
139                input.as_str()
140            }
141        }
142
143        impl From<$name> for tinystr::TinyAsciiStr<$len_end> {
144            fn from(input: $name) -> Self {
145                input.to_tinystr()
146            }
147        }
148
149        impl writeable::Writeable for $name {
150            #[inline]
151            fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
152                sink.write_str(self.as_str())
153            }
154            #[inline]
155            fn writeable_length_hint(&self) -> writeable::LengthHint {
156                writeable::LengthHint::exact(self.0.len())
157            }
158            fn writeable_borrow(&self) -> Option<&str> {
159                Some(self.0.as_str())
160            }
161        }
162
163        writeable::impl_display_with_writeable!($name, #[cfg(feature = "alloc")]);
164
165        #[doc = concat!("A macro allowing for compile-time construction of valid [`", stringify!($name), "`] subtags.")]
166        ///
167        /// # Examples
168        ///
169        /// Parsing errors don't have to be handled at runtime:
170        /// ```
171        /// assert_eq!(
172        #[doc = concat!("  icu_locale_core::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($good_example) ,"),")]
173        #[doc = concat!("  ", stringify!($good_example), ".parse::<icu_locale_core::", $(stringify!($path), "::",)+ stringify!($name), ">().unwrap()")]
174        /// );
175        /// ```
176        ///
177        /// Invalid input is a compile failure:
178        /// ```compile_fail,E0080
179        #[doc = concat!("icu_locale_core::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($bad_example) ,");")]
180        /// ```
181        ///
182        #[doc = concat!("[`", stringify!($name), "`]: crate::", $(stringify!($path), "::",)+ stringify!($name))]
183        #[macro_export]
184        #[doc(hidden)] // macro
185        macro_rules! $internal_macro_name {
186            ($string:literal) => { const {
187                use $crate::$($path ::)+ $name;
188                match $name::try_from_utf8($string.as_bytes()) {
189                    Ok(r) => r,
190                    _ => panic!(concat!("Invalid ", $(stringify!($path), "::",)+ stringify!($name), ": ", $string)),
191                }
192            }};
193        }
194        #[doc(inline)]
195        pub use $internal_macro_name as $macro_name;
196
197        #[cfg(feature = "databake")]
198        impl databake::Bake for $name {
199            fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
200                env.insert("icu_locale_core");
201                let string = self.as_str();
202                databake::quote! { icu_locale_core::$($path::)+ $macro_name!(#string) }
203            }
204        }
205
206        #[cfg(feature = "databake")]
207        impl databake::BakeSize for $name {
208            fn borrows_size(&self) -> usize {
209                0
210            }
211        }
212
213        #[test]
214        fn test_construction() {
215            let maybe = $name::try_from_utf8($good_example.as_bytes());
216            assert!(maybe.is_ok());
217            assert_eq!(maybe, $name::try_from_raw(maybe.unwrap().into_raw()));
218            assert_eq!(maybe.unwrap().as_str(), $good_example);
219            $(
220                let maybe = $name::try_from_utf8($more_good_examples.as_bytes());
221                assert!(maybe.is_ok());
222                assert_eq!(maybe, $name::try_from_raw(maybe.unwrap().into_raw()));
223                assert_eq!(maybe.unwrap().as_str(), $more_good_examples);
224            )*
225            assert!($name::try_from_utf8($bad_example.as_bytes()).is_err());
226            $(
227                assert!($name::try_from_utf8($more_bad_examples.as_bytes()).is_err());
228            )*
229        }
230
231        #[test]
232        fn test_writeable() {
233            writeable::assert_writeable_eq!(&$good_example.parse::<$name>().unwrap(), $good_example);
234            $(
235                writeable::assert_writeable_eq!($more_good_examples.parse::<$name>().unwrap(), $more_good_examples);
236            )*
237        }
238
239        #[cfg(feature = "serde")]
240        impl serde::Serialize for $name {
241            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242            where
243                S: serde::Serializer,
244            {
245                self.0.serialize(serializer)
246            }
247        }
248
249        #[cfg(feature = "serde")]
250        impl<'de> serde::Deserialize<'de> for $name {
251            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252            where
253                D: serde::de::Deserializer<'de>,
254            {
255                struct Visitor;
256
257                impl<'de> serde::de::Visitor<'de> for Visitor {
258                    type Value = $name;
259
260                    fn expecting(
261                        &self,
262                        formatter: &mut core::fmt::Formatter<'_>,
263                    ) -> core::fmt::Result {
264                        write!(formatter, "a valid BCP-47 {}", stringify!($name))
265                    }
266
267                    fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
268                        s.parse().map_err(serde::de::Error::custom)
269                    }
270                }
271
272                if deserializer.is_human_readable() {
273                    deserializer.deserialize_string(Visitor)
274                } else {
275                    Self::try_from_raw(serde::de::Deserialize::deserialize(deserializer)?)
276                        .map_err(serde::de::Error::custom)
277                }
278            }
279        }
280
281        // Safety checklist for ULE:
282        //
283        // 1. Must not include any uninitialized or padding bytes (true since transparent over a ULE).
284        // 2. Must have an alignment of 1 byte (true since transparent over a ULE).
285        // 3. ULE::validate_bytes() checks that the given byte slice represents a valid slice.
286        // 4. ULE::validate_bytes() checks that the given byte slice has a valid length.
287        // 5. All other methods must be left with their default impl.
288        // 6. Byte equality is semantic equality.
289        #[cfg(feature = "zerovec")]
290        unsafe impl zerovec::ule::ULE for $name {
291            fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> {
292                let it = bytes.chunks_exact(core::mem::size_of::<Self>());
293                if !it.remainder().is_empty() {
294                    return Err(zerovec::ule::UleError::length::<Self>(bytes.len()));
295                }
296                for v in it {
297                    // The following can be removed once `array_chunks` is stabilized.
298                    let mut a = [0; core::mem::size_of::<Self>()];
299                    a.copy_from_slice(v);
300                    if Self::try_from_raw(a).is_err() {
301                        return Err(zerovec::ule::UleError::parse::<Self>());
302                    }
303                }
304                Ok(())
305            }
306        }
307
308        #[cfg(feature = "zerovec")]
309        impl zerovec::ule::NicheBytes<$len_end> for $name {
310            const NICHE_BIT_PATTERN: [u8; $len_end] = <tinystr::TinyAsciiStr<$len_end>>::NICHE_BIT_PATTERN;
311        }
312
313        #[cfg(feature = "zerovec")]
314        impl zerovec::ule::AsULE for $name {
315            type ULE = Self;
316            fn to_unaligned(self) -> Self::ULE {
317                self
318            }
319            fn from_unaligned(unaligned: Self::ULE) -> Self {
320                unaligned
321            }
322        }
323
324        #[cfg(feature = "zerovec")]
325        #[cfg(feature = "alloc")]
326        impl<'a> zerovec::maps::ZeroMapKV<'a> for $name {
327            type Container = zerovec::ZeroVec<'a, $name>;
328            type Slice = zerovec::ZeroSlice<$name>;
329            type GetType = $name;
330            type OwnedType = $name;
331        }
332    };
333}
334
335#[macro_export]
336#[doc(hidden)]
337macro_rules! impl_writeable_for_each_subtag_str_no_test {
338    ($type:tt $(, $self:ident, $borrow_cond:expr => $borrow:expr)?) => {
339        impl writeable::Writeable for $type {
340            fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
341                let mut initial = true;
342                self.for_each_subtag_str(&mut |subtag| {
343                    if initial {
344                        initial = false;
345                    } else {
346                        sink.write_char('-')?;
347                    }
348                    sink.write_str(subtag)
349                })
350            }
351
352            #[inline]
353            fn writeable_length_hint(&self) -> writeable::LengthHint {
354                let mut result = writeable::LengthHint::exact(0);
355                let mut initial = true;
356                self.for_each_subtag_str::<core::convert::Infallible, _>(&mut |subtag| {
357                    if initial {
358                        initial = false;
359                    } else {
360                        result += 1;
361                    }
362                    result += subtag.len();
363                    Ok(())
364                })
365                .expect("infallible");
366                result
367            }
368
369            $(
370                fn writeable_borrow(&self) -> Option<&str> {
371                    let $self = self;
372                    if $borrow_cond {
373                        $borrow
374                    } else {
375                        None
376                    }
377                }
378            )?
379        }
380
381        writeable::impl_display_with_writeable!($type, #[cfg(feature = "alloc")]);
382    };
383}
384
385macro_rules! impl_writeable_for_subtag_list {
386    ($type:tt, $sample1:literal, $sample2:literal) => {
387        impl_writeable_for_each_subtag_str_no_test!($type, selff, selff.0.len() == 1 => #[allow(clippy::unwrap_used)] { Some(selff.0.get(0).unwrap().as_str()) } );
388
389        #[test]
390        fn test_writeable() {
391            writeable::assert_writeable_eq!(&$type::default(), "");
392            writeable::assert_writeable_eq!(
393                &$type::from_vec_unchecked(alloc::vec![$sample1.parse().unwrap()]),
394                $sample1,
395            );
396            writeable::assert_writeable_eq!(
397                &$type::from_vec_unchecked(vec![
398                    $sample1.parse().unwrap(),
399                    $sample2.parse().unwrap()
400                ]),
401                core::concat!($sample1, "-", $sample2),
402            );
403        }
404    };
405}
406
407macro_rules! impl_writeable_for_key_value {
408    ($type:tt, $key1:literal, $value1:literal, $key2:literal, $expected2:literal) => {
409        impl_writeable_for_each_subtag_str_no_test!($type);
410
411        #[test]
412        fn test_writeable() {
413            writeable::assert_writeable_eq!(&$type::default(), "");
414            writeable::assert_writeable_eq!(
415                &$type::from_tuple_vec(vec![($key1.parse().unwrap(), $value1.parse().unwrap())]),
416                core::concat!($key1, "-", $value1),
417            );
418            writeable::assert_writeable_eq!(
419                &$type::from_tuple_vec(vec![
420                    ($key1.parse().unwrap(), $value1.parse().unwrap()),
421                    ($key2.parse().unwrap(), "true".parse().unwrap())
422                ]),
423                core::concat!($key1, "-", $value1, "-", $expected2),
424            );
425        }
426    };
427}