icu_locid/extensions/transform/
value.rs
1use crate::parser::{ParserError, SubtagIterator};
6use crate::shortvec::ShortBoxSlice;
7use core::ops::RangeInclusive;
8use core::str::FromStr;
9use tinystr::TinyAsciiStr;
10
11#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Default)]
30pub struct Value(ShortBoxSlice<TinyAsciiStr<{ *TYPE_LENGTH.end() }>>);
31
32const TYPE_LENGTH: RangeInclusive<usize> = 3..=8;
33const TRUE_TVALUE: TinyAsciiStr<8> = tinystr::tinystr!(8, "true");
34
35impl Value {
36 pub fn try_from_bytes(input: &[u8]) -> Result<Self, ParserError> {
47 let mut v = ShortBoxSlice::default();
48 let mut has_value = false;
49
50 for subtag in SubtagIterator::new(input) {
51 if !Self::is_type_subtag(subtag) {
52 return Err(ParserError::InvalidExtension);
53 }
54 has_value = true;
55 let val =
56 TinyAsciiStr::from_bytes(subtag).map_err(|_| ParserError::InvalidExtension)?;
57 if val != TRUE_TVALUE {
58 v.push(val);
59 }
60 }
61
62 if !has_value {
63 return Err(ParserError::InvalidExtension);
64 }
65 Ok(Self(v))
66 }
67
68 pub(crate) fn from_short_slice_unchecked(
69 input: ShortBoxSlice<TinyAsciiStr<{ *TYPE_LENGTH.end() }>>,
70 ) -> Self {
71 Self(input)
72 }
73
74 pub(crate) fn is_type_subtag(t: &[u8]) -> bool {
75 TYPE_LENGTH.contains(&t.len()) && t.iter().all(u8::is_ascii_alphanumeric)
76 }
77
78 pub(crate) fn parse_subtag(
79 t: &[u8],
80 ) -> Result<Option<TinyAsciiStr<{ *TYPE_LENGTH.end() }>>, ParserError> {
81 let s = TinyAsciiStr::from_bytes(t).map_err(|_| ParserError::InvalidSubtag)?;
82 if !TYPE_LENGTH.contains(&t.len()) || !s.is_ascii_alphanumeric() {
83 return Err(ParserError::InvalidExtension);
84 }
85
86 let s = s.to_ascii_lowercase();
87
88 if s == TRUE_TVALUE {
89 Ok(None)
90 } else {
91 Ok(Some(s))
92 }
93 }
94
95 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
96 where
97 F: FnMut(&str) -> Result<(), E>,
98 {
99 if self.0.is_empty() {
100 f("true")?;
101 } else {
102 self.0.iter().map(TinyAsciiStr::as_str).try_for_each(f)?;
103 }
104 Ok(())
105 }
106}
107
108impl FromStr for Value {
109 type Err = ParserError;
110
111 fn from_str(source: &str) -> Result<Self, Self::Err> {
112 Self::try_from_bytes(source.as_bytes())
113 }
114}
115
116impl_writeable_for_each_subtag_str_no_test!(Value, selff, selff.0.is_empty() => alloc::borrow::Cow::Borrowed("true"));
117
118#[test]
119fn test_writeable() {
120 use writeable::assert_writeable_eq;
121
122 let hybrid = "hybrid".parse().unwrap();
123 let foobar = "foobar".parse().unwrap();
124
125 assert_writeable_eq!(Value::default(), "true");
126 assert_writeable_eq!(
127 Value::from_short_slice_unchecked(vec![hybrid].into()),
128 "hybrid"
129 );
130 assert_writeable_eq!(
131 Value::from_short_slice_unchecked(vec![hybrid, foobar].into()),
132 "hybrid-foobar"
133 );
134}