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 | |
5 | use crate::helpers::ShortSlice; |
6 | use crate::parser::{ParserError, SubtagIterator}; |
7 | use core::ops::RangeInclusive; |
8 | use core::str::FromStr; |
9 | use tinystr::TinyAsciiStr; |
10 | |
11 | /// A value used in a list of [`Fields`](super::Fields). |
12 | /// |
13 | /// The value has to be a sequence of one or more alphanumerical strings |
14 | /// separated by `-`. |
15 | /// Each part of the sequence has to be no shorter than three characters and no |
16 | /// longer than 8. |
17 | /// |
18 | /// # Examples |
19 | /// |
20 | /// ``` |
21 | /// use icu::locid::extensions::transform::Value; |
22 | /// |
23 | /// "hybrid" .parse::<Value>().expect("Valid Value." ); |
24 | /// |
25 | /// "hybrid-foobar" .parse::<Value>().expect("Valid Value." ); |
26 | /// |
27 | /// "no" .parse::<Value>().expect_err("Invalid Value." ); |
28 | /// ``` |
29 | #[derive (Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Default)] |
30 | pub struct Value(ShortSlice<TinyAsciiStr<{ *TYPE_LENGTH.end() }>>); |
31 | |
32 | const TYPE_LENGTH: RangeInclusive<usize> = 3..=8; |
33 | const TRUE_TVALUE: TinyAsciiStr<8> = tinystr::tinystr!(8, "true" ); |
34 | |
35 | impl Value { |
36 | /// A constructor which takes a utf8 slice, parses it and |
37 | /// produces a well-formed [`Value`]. |
38 | /// |
39 | /// # Examples |
40 | /// |
41 | /// ``` |
42 | /// use icu::locid::extensions::transform::Value; |
43 | /// |
44 | /// let value = Value::try_from_bytes(b"hybrid" ).expect("Parsing failed." ); |
45 | /// ``` |
46 | pub fn try_from_bytes(input: &[u8]) -> Result<Self, ParserError> { |
47 | let mut v = ShortSlice::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: ShortSlice<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 | |
108 | impl FromStr for Value { |
109 | type Err = ParserError; |
110 | |
111 | fn from_str(source: &str) -> Result<Self, Self::Err> { |
112 | Self::try_from_bytes(input:source.as_bytes()) |
113 | } |
114 | } |
115 | |
116 | impl_writeable_for_each_subtag_str_no_test!(Value, selff, selff.0.is_empty() => alloc::borrow::Cow::Borrowed("true" )); |
117 | |
118 | #[test ] |
119 | fn 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 | } |
135 | |