1macro_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 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
23 #[repr(transparent)]
24 $(#[$doc])*
25 pub struct $name(tinystr::TinyAsciiStr<$len_end>);
26
27 impl $name {
28 #[doc = concat!("produces a well-formed [`", stringify!($name), "`].")]
30 #[doc = concat!("use icu_locale_core::", stringify!($($path::)+), stringify!($name), ";")]
35 #[doc = concat!("assert!(", stringify!($name), "::try_from_str(", stringify!($good_example), ").is_ok());")]
37 #[doc = concat!("assert!(", stringify!($name), "::try_from_str(", stringify!($bad_example), ").is_err());")]
38 #[inline]
40 pub const fn try_from_str(s: &str) -> Result<Self, crate::parser::errors::ParseError> {
41 Self::try_from_utf8(s.as_bytes())
42 }
43
44 pub const fn try_from_utf8(
46 code_units: &[u8],
47 ) -> Result<Self, crate::parser::errors::ParseError> {
48 #[allow(clippy::double_comparisons)] if code_units.len() < $len_start || code_units.len() > $len_end {
50 return Err(crate::parser::errors::ParseError::$error);
51 }
52
53 match tinystr::TinyAsciiStr::try_from_utf8(code_units) {
54 Ok($tinystr_ident) if $validate => Ok(Self($normalize)),
55 _ => Err(crate::parser::errors::ParseError::$error),
56 }
57 }
58
59 #[doc = concat!("Safely creates a [`", stringify!($name), "`] from its raw format")]
60 pub const fn try_from_raw(
63 raw: [u8; $len_end],
64 ) -> Result<Self, crate::parser::errors::ParseError> {
65 if let Ok($tinystr_ident) = tinystr::TinyAsciiStr::<$len_end>::try_from_raw(raw) {
66 if $tinystr_ident.len() >= $len_start && $is_normalized {
67 Ok(Self($tinystr_ident))
68 } else {
69 Err(crate::parser::errors::ParseError::$error)
70 }
71 } else {
72 Err(crate::parser::errors::ParseError::$error)
73 }
74 }
75
76 #[doc = concat!("Unsafely creates a [`", stringify!($name), "`] from its raw format")]
77 pub const unsafe fn from_raw_unchecked(v: [u8; $len_end]) -> Self {
85 Self(tinystr::TinyAsciiStr::from_utf8_unchecked(v))
86 }
87
88 pub const fn into_raw(self) -> [u8; $len_end] {
92 *self.0.all_bytes()
93 }
94
95 #[inline]
96 pub const fn as_str(&self) -> &str {
98 self.0.as_str()
99 }
100
101 #[doc(hidden)]
102 pub const fn to_tinystr(&self) -> tinystr::TinyAsciiStr<$len_end> {
103 self.0
104 }
105
106 #[inline]
115 pub fn strict_cmp(self, other: &[u8]) -> core::cmp::Ordering {
116 self.as_str().as_bytes().cmp(other)
117 }
118
119 #[inline]
125 pub fn normalizing_eq(self, other: &str) -> bool {
126 self.as_str().eq_ignore_ascii_case(other)
127 }
128 }
129
130 impl core::str::FromStr for $name {
131 type Err = crate::parser::errors::ParseError;
132
133 #[inline]
134 fn from_str(s: &str) -> Result<Self, Self::Err> {
135 Self::try_from_str(s)
136 }
137 }
138
139 impl<'l> From<&'l $name> for &'l str {
140 fn from(input: &'l $name) -> Self {
141 input.as_str()
142 }
143 }
144
145 impl From<$name> for tinystr::TinyAsciiStr<$len_end> {
146 fn from(input: $name) -> Self {
147 input.to_tinystr()
148 }
149 }
150
151 impl writeable::Writeable for $name {
152 #[inline]
153 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
154 sink.write_str(self.as_str())
155 }
156 #[inline]
157 fn writeable_length_hint(&self) -> writeable::LengthHint {
158 writeable::LengthHint::exact(self.0.len())
159 }
160 #[inline]
161 #[cfg(feature = "alloc")]
162 fn write_to_string(&self) -> alloc::borrow::Cow<str> {
163 alloc::borrow::Cow::Borrowed(self.0.as_str())
164 }
165 }
166
167 writeable::impl_display_with_writeable!($name);
168
169 #[doc = concat!("A macro allowing for compile-time construction of valid [`", stringify!($name), "`] subtags.")]
170 #[doc = concat!(" icu_locale_core::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($good_example) ,"),")]
177 #[doc = concat!(" ", stringify!($good_example), ".parse::<icu_locale_core::", $(stringify!($path), "::",)+ stringify!($name), ">().unwrap()")]
178 #[doc = concat!("icu_locale_core::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($bad_example) ,");")]
184 #[doc = concat!("[`", stringify!($name), "`]: crate::", $(stringify!($path), "::",)+ stringify!($name))]
187 #[macro_export]
188 #[doc(hidden)] macro_rules! $internal_macro_name {
190 ($string:literal) => { const {
191 use $crate::$($path ::)+ $name;
192 match $name::try_from_utf8($string.as_bytes()) {
193 Ok(r) => r,
194 #[allow(clippy::panic)] _ => panic!(concat!("Invalid ", $(stringify!($path), "::",)+ stringify!($name), ": ", $string)),
196 }
197 }};
198 }
199 #[doc(inline)]
200 pub use $internal_macro_name as $macro_name;
201
202 #[cfg(feature = "databake")]
203 impl databake::Bake for $name {
204 fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
205 env.insert("icu_locale_core");
206 let string = self.as_str();
207 databake::quote! { icu_locale_core::$($path::)+ $macro_name!(#string) }
208 }
209 }
210
211 #[cfg(feature = "databake")]
212 impl databake::BakeSize for $name {
213 fn borrows_size(&self) -> usize {
214 0
215 }
216 }
217
218 #[test]
219 fn test_construction() {
220 let maybe = $name::try_from_utf8($good_example.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(), $good_example);
224 $(
225 let maybe = $name::try_from_utf8($more_good_examples.as_bytes());
226 assert!(maybe.is_ok());
227 assert_eq!(maybe, $name::try_from_raw(maybe.unwrap().into_raw()));
228 assert_eq!(maybe.unwrap().as_str(), $more_good_examples);
229 )*
230 assert!($name::try_from_utf8($bad_example.as_bytes()).is_err());
231 $(
232 assert!($name::try_from_utf8($more_bad_examples.as_bytes()).is_err());
233 )*
234 }
235
236 #[test]
237 fn test_writeable() {
238 writeable::assert_writeable_eq!(&$good_example.parse::<$name>().unwrap(), $good_example);
239 $(
240 writeable::assert_writeable_eq!($more_good_examples.parse::<$name>().unwrap(), $more_good_examples);
241 )*
242 }
243
244 #[cfg(feature = "serde")]
245 impl<'de> serde::Deserialize<'de> for $name {
246 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247 where
248 D: serde::de::Deserializer<'de>,
249 {
250 struct Visitor;
251
252 impl<'de> serde::de::Visitor<'de> for Visitor {
253 type Value = $name;
254
255 fn expecting(
256 &self,
257 formatter: &mut core::fmt::Formatter<'_>,
258 ) -> core::fmt::Result {
259 write!(formatter, "a valid BCP-47 {}", stringify!($name))
260 }
261
262 fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
263 s.parse().map_err(serde::de::Error::custom)
264 }
265 }
266
267 if deserializer.is_human_readable() {
268 deserializer.deserialize_string(Visitor)
269 } else {
270 Self::try_from_raw(serde::de::Deserialize::deserialize(deserializer)?)
271 .map_err(serde::de::Error::custom)
272 }
273 }
274 }
275
276 #[cfg(feature = "zerovec")]
285 unsafe impl zerovec::ule::ULE for $name {
286 fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> {
287 let it = bytes.chunks_exact(core::mem::size_of::<Self>());
288 if !it.remainder().is_empty() {
289 return Err(zerovec::ule::UleError::length::<Self>(bytes.len()));
290 }
291 for v in it {
292 let mut a = [0; core::mem::size_of::<Self>()];
294 a.copy_from_slice(v);
295 if Self::try_from_raw(a).is_err() {
296 return Err(zerovec::ule::UleError::parse::<Self>());
297 }
298 }
299 Ok(())
300 }
301 }
302
303 #[cfg(feature = "zerovec")]
304 impl zerovec::ule::NicheBytes<$len_end> for $name {
305 const NICHE_BIT_PATTERN: [u8; $len_end] = <tinystr::TinyAsciiStr<$len_end>>::NICHE_BIT_PATTERN;
306 }
307
308 #[cfg(feature = "zerovec")]
309 impl zerovec::ule::AsULE for $name {
310 type ULE = Self;
311 fn to_unaligned(self) -> Self::ULE {
312 self
313 }
314 fn from_unaligned(unaligned: Self::ULE) -> Self {
315 unaligned
316 }
317 }
318
319 #[cfg(feature = "zerovec")]
320 impl<'a> zerovec::maps::ZeroMapKV<'a> for $name {
321 type Container = zerovec::ZeroVec<'a, $name>;
322 type Slice = zerovec::ZeroSlice<$name>;
323 type GetType = $name;
324 type OwnedType = $name;
325 }
326 };
327}
328
329#[macro_export]
330#[doc(hidden)]
331macro_rules! impl_writeable_for_each_subtag_str_no_test {
332 ($type:tt $(, $self:ident, $borrow_cond:expr => $borrow:expr)?) => {
333 impl writeable::Writeable for $type {
334 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
335 let mut initial = true;
336 self.for_each_subtag_str(&mut |subtag| {
337 if initial {
338 initial = false;
339 } else {
340 sink.write_char('-')?;
341 }
342 sink.write_str(subtag)
343 })
344 }
345
346 #[inline]
347 fn writeable_length_hint(&self) -> writeable::LengthHint {
348 let mut result = writeable::LengthHint::exact(0);
349 let mut initial = true;
350 self.for_each_subtag_str::<core::convert::Infallible, _>(&mut |subtag| {
351 if initial {
352 initial = false;
353 } else {
354 result += 1;
355 }
356 result += subtag.len();
357 Ok(())
358 })
359 .expect("infallible");
360 result
361 }
362
363 $(
364 #[cfg(feature = "alloc")]
365 fn write_to_string(&self) -> alloc::borrow::Cow<str> {
366 #[allow(clippy::unwrap_used)] let $self = self;
368 if $borrow_cond {
369 $borrow
370 } else {
371 let mut output = alloc::string::String::with_capacity(self.writeable_length_hint().capacity());
372 let _ = self.write_to(&mut output);
373 alloc::borrow::Cow::Owned(output)
374 }
375 }
376 )?
377 }
378
379 writeable::impl_display_with_writeable!($type);
380 };
381}
382
383macro_rules! impl_writeable_for_subtag_list {
384 ($type:tt, $sample1:literal, $sample2:literal) => {
385 impl_writeable_for_each_subtag_str_no_test!($type, selff, selff.0.len() == 1 => alloc::borrow::Cow::Borrowed(selff.0.get(0).unwrap().as_str()));
386
387 #[test]
388 fn test_writeable() {
389 writeable::assert_writeable_eq!(&$type::default(), "");
390 writeable::assert_writeable_eq!(
391 &$type::from_vec_unchecked(alloc::vec![$sample1.parse().unwrap()]),
392 $sample1,
393 );
394 writeable::assert_writeable_eq!(
395 &$type::from_vec_unchecked(vec![
396 $sample1.parse().unwrap(),
397 $sample2.parse().unwrap()
398 ]),
399 core::concat!($sample1, "-", $sample2),
400 );
401 }
402 };
403}
404
405macro_rules! impl_writeable_for_key_value {
406 ($type:tt, $key1:literal, $value1:literal, $key2:literal, $expected2:literal) => {
407 impl_writeable_for_each_subtag_str_no_test!($type);
408
409 #[test]
410 fn test_writeable() {
411 writeable::assert_writeable_eq!(&$type::default(), "");
412 writeable::assert_writeable_eq!(
413 &$type::from_tuple_vec(vec![($key1.parse().unwrap(), $value1.parse().unwrap())]),
414 core::concat!($key1, "-", $value1),
415 );
416 writeable::assert_writeable_eq!(
417 &$type::from_tuple_vec(vec![
418 ($key1.parse().unwrap(), $value1.parse().unwrap()),
419 ($key2.parse().unwrap(), "true".parse().unwrap())
420 ]),
421 core::concat!($key1, "-", $value1, "-", $expected2),
422 );
423 }
424 };
425}