icu_locale_core/extensions/transform/
value.rs1use crate::parser::ParseError;
6#[cfg(feature = "alloc")]
7use crate::parser::SubtagIterator;
8use crate::shortvec::ShortBoxSlice;
9use crate::subtags::{subtag, Subtag};
10use core::ops::RangeInclusive;
11#[cfg(feature = "alloc")]
12use core::str::FromStr;
13
14)]
33pub struct Value(ShortBoxSlice<Subtag>);
34
35#[allow(dead_code)]
36const TYPE_LENGTH: RangeInclusive<usize> = 3..=8;
37const TRUE_TVALUE: Subtag = const {
use crate::subtags::Subtag;
match Subtag::try_from_utf8("true".as_bytes()) {
Ok(r) =>
r,
#[allow(clippy :: panic)]
_ => {
::core::panicking::panic_fmt(format_args!("Invalid subtags::Subtag: true"));
}
}
}subtag!("true");
38
39impl Value {
40 #[inline]
51 #[cfg(feature = "alloc")]
52 pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
53 Self::try_from_utf8(s.as_bytes())
54 }
55
56 #[cfg(feature = "alloc")]
58 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
59 let mut v = ShortBoxSlice::default();
60 let mut has_value = false;
61
62 for subtag in SubtagIterator::new(code_units) {
63 if !Self::is_type_subtag(subtag) {
64 return Err(ParseError::InvalidExtension);
65 }
66 has_value = true;
67 let val = Subtag::try_from_utf8(subtag).map_err(|_| ParseError::InvalidExtension)?;
68 if val != TRUE_TVALUE {
69 v.push(val);
70 }
71 }
72
73 if !has_value {
74 return Err(ParseError::InvalidExtension);
75 }
76 Ok(Self(v))
77 }
78
79 #[allow(dead_code)]
80 pub(crate) fn from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self {
81 Self(input)
82 }
83
84 #[allow(dead_code)]
85 pub(crate) fn is_type_subtag(t: &[u8]) -> bool {
86 TYPE_LENGTH.contains(&t.len()) && t.iter().all(u8::is_ascii_alphanumeric)
87 }
88
89 #[allow(dead_code)]
90 pub(crate) fn parse_subtag(t: &[u8]) -> Result<Option<Subtag>, ParseError> {
91 if !TYPE_LENGTH.contains(&t.len()) {
92 return Err(ParseError::InvalidExtension);
93 }
94 let s = Subtag::try_from_utf8(t).map_err(|_| ParseError::InvalidSubtag)?;
95
96 let s = s.to_ascii_lowercase();
97
98 if s == TRUE_TVALUE {
99 Ok(None)
100 } else {
101 Ok(Some(s))
102 }
103 }
104
105 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
106 where
107 F: FnMut(&str) -> Result<(), E>,
108 {
109 if self.0.is_empty() {
110 f(TRUE_TVALUE.as_str())?;
111 } else {
112 self.0.iter().map(Subtag::as_str).try_for_each(f)?;
113 }
114 Ok(())
115 }
116}
117
118#[cfg(feature = "alloc")]
119impl FromStr for Value {
120 type Err = ParseError;
121
122 #[inline]
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 Self::try_from_str(s)
125 }
126}
127
128impl writeable::Writeable for Value {
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W)
-> core::fmt::Result {
let mut initial = true;
self.for_each_subtag_str(&mut |subtag|
{
if initial {
initial = false;
} else { sink.write_char('-')?; }
sink.write_str(subtag)
})
}
#[inline]
fn writeable_length_hint(&self) -> writeable::LengthHint {
let mut result = writeable::LengthHint::exact(0);
let mut initial = true;
self.for_each_subtag_str::<core::convert::Infallible,
_>(&mut |subtag|
{
if initial { initial = false; } else { result += 1; }
result += subtag.len();
Ok(())
}).expect("infallible");
result
}
}
impl core::fmt::Display for Value {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
::writeable::Writeable::write_to(&self, f)
}
}
impl Value {
pub fn to_string(&self) -> ::writeable::_internal::String {
::writeable::Writeable::write_to_string(self).into_owned()
}
}impl_writeable_for_each_subtag_str_no_test!(Value, selff, selff.0.is_empty() => alloc::borrow::Cow::Borrowed("true"));
129
130#[test]
131fn test_writeable() {
132 use writeable::assert_writeable_eq;
133
134 let hybrid = "hybrid".parse().unwrap();
135 let foobar = "foobar".parse().unwrap();
136
137 assert_writeable_eq!(Value::default(), "true");
138 assert_writeable_eq!(
139 Value::from_short_slice_unchecked(vec![hybrid].into()),
140 "hybrid"
141 );
142 assert_writeable_eq!(
143 Value::from_short_slice_unchecked(vec![hybrid, foobar].into()),
144 "hybrid-foobar"
145 );
146}
147
148#[test]
149fn test_short_tvalue() {
150 let value = Value::try_from_str("foo-longstag");
151 assert!(value.is_ok());
152 let value = value.unwrap();
153 assert_eq!(value.0.len(), 2);
154 for (s, reference) in value.0.iter().zip(&[subtag!("foo"), subtag!("longstag")]) {
155 assert_eq!(s, reference);
156 }
157
158 let value = Value::try_from_str("foo-ba");
159 assert!(value.is_err());
160}