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 #[repr(transparent)]
23 $(#[$doc])*
24 pub struct $name(tinystr::TinyAsciiStr<$len_end>);
25
26 impl $name {
27 #[doc = concat!("produces a well-formed [`", stringify!($name), "`].")]
29 #[doc = concat!("use icu_locale_core::", stringify!($($path::)+), stringify!($name), ";")]
34 #[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 #[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 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 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 pub const unsafe fn from_raw_unchecked(v: [u8; $len_end]) -> Self {
83 Self(tinystr::TinyAsciiStr::from_utf8_unchecked(v))
84 }
85
86 pub const fn into_raw(self) -> [u8; $len_end] {
90 *self.0.all_bytes()
91 }
92
93 #[inline]
94 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 #[inline]
113 pub fn strict_cmp(self, other: &[u8]) -> core::cmp::Ordering {
114 self.as_str().as_bytes().cmp(other)
115 }
116
117 #[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 #[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 #[doc = concat!("icu_locale_core::", $(stringify!($path), "::",)+ stringify!($macro_name), "!(", stringify!($bad_example) ,");")]
180 #[doc = concat!("[`", stringify!($name), "`]: crate::", $(stringify!($path), "::",)+ stringify!($name))]
183 #[macro_export]
184 #[doc(hidden)] 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 #[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 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}