icu_locale_core/extensions/transform/
value.rs

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
5use 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/// 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(#[automatically_derived]
impl ::core::fmt::Debug for Value {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Value",
            &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Value {
    #[inline]
    fn eq(&self, other: &Value) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Value {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) -> () {
        let _: ::core::cmp::AssertParamIsEq<ShortBoxSlice<Subtag>>;
    }
}Eq, #[automatically_derived]
impl ::core::clone::Clone for Value {
    #[inline]
    fn clone(&self) -> Value { Value(::core::clone::Clone::clone(&self.0)) }
}Clone, #[automatically_derived]
impl ::core::hash::Hash for Value {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash, #[automatically_derived]
impl ::core::cmp::PartialOrd for Value {
    #[inline]
    fn partial_cmp(&self, other: &Value)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0)
    }
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for Value {
    #[inline]
    fn cmp(&self, other: &Value) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}Ord, #[automatically_derived]
impl ::core::default::Default for Value {
    #[inline]
    fn default() -> Value { Value(::core::default::Default::default()) }
}Default)]
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    /// 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")]
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")]
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
    }
}
/// This trait is implemented for compatibility with [`fmt!`](alloc::fmt).
/// To create a string, [`Writeable::write_to_string`] is usually more efficient.
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 {
    /// Converts the given value to a `String`.
    ///
    /// Under the hood, this uses an efficient [`Writeable`] implementation.
    /// However, in order to avoid allocating a string, it is more efficient
    /// to use [`Writeable`] directly.
    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}