• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::parser::ParseError;
6 #[cfg(feature = "alloc")]
7 use crate::parser::SubtagIterator;
8 use crate::shortvec::ShortBoxSlice;
9 use crate::subtags::{subtag, Subtag};
10 use core::ops::RangeInclusive;
11 #[cfg(feature = "alloc")]
12 use core::str::FromStr;
13 
14 /// A value used in a list of [`Fields`](super::Fields).
15 ///
16 /// The value has to be a sequence of one or more alphanumerical strings
17 /// separated by `-`.
18 /// Each part of the sequence has to be no shorter than three characters and no
19 /// longer than 8.
20 ///
21 /// # Examples
22 ///
23 /// ```
24 /// use icu::locale::extensions::transform::Value;
25 ///
26 /// "hybrid".parse::<Value>().expect("Valid Value.");
27 ///
28 /// "hybrid-foobar".parse::<Value>().expect("Valid Value.");
29 ///
30 /// "no".parse::<Value>().expect_err("Invalid Value.");
31 /// ```
32 #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Default)]
33 pub struct Value(ShortBoxSlice<Subtag>);
34 
35 #[allow(dead_code)]
36 const TYPE_LENGTH: RangeInclusive<usize> = 3..=8;
37 const TRUE_TVALUE: Subtag = subtag!("true");
38 
39 impl Value {
40     /// A constructor which takes a str slice, parses it and
41     /// produces a well-formed [`Value`].
42     ///
43     /// # Examples
44     ///
45     /// ```
46     /// use icu::locale::extensions::transform::Value;
47     ///
48     /// let value = Value::try_from_str("hybrid").expect("Parsing failed.");
49     /// ```
50     #[inline]
51     #[cfg(feature = "alloc")]
try_from_str(s: &str) -> Result<Self, ParseError>52     pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
53         Self::try_from_utf8(s.as_bytes())
54     }
55 
56     /// See [`Self::try_from_str`]
57     #[cfg(feature = "alloc")]
try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError>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)]
from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self80     pub(crate) fn from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self {
81         Self(input)
82     }
83 
84     #[allow(dead_code)]
is_type_subtag(t: &[u8]) -> bool85     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)]
parse_subtag(t: &[u8]) -> Result<Option<Subtag>, ParseError>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 
for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E> where F: FnMut(&str) -> Result<(), E>,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")]
119 impl FromStr for Value {
120     type Err = ParseError;
121 
122     #[inline]
from_str(s: &str) -> Result<Self, Self::Err>123     fn from_str(s: &str) -> Result<Self, Self::Err> {
124         Self::try_from_str(s)
125     }
126 }
127 
128 impl_writeable_for_each_subtag_str_no_test!(Value, selff, selff.0.is_empty() => alloc::borrow::Cow::Borrowed("true"));
129 
130 #[test]
test_writeable()131 fn 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]
test_short_tvalue()149 fn 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 }
161