summaryrefslogtreecommitdiffstats
path: root/vendor/icu_locid/src/extensions/transform/value.rs
blob: 84468361a840dfc9b0f02d2b165b824e9359583d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::parser::{get_subtag_iterator, ParserError};
use alloc::vec;
use alloc::vec::Vec;
use core::ops::RangeInclusive;
use core::str::FromStr;
use tinystr::TinyAsciiStr;

/// A value used in a list of [`Fields`](super::Fields).
///
/// The value has to be a sequence of one or more alphanumerical strings
/// separated by `-`.
/// Each part of the sequence has to be no shorter than three characters and no
/// longer than 8.
///
///
/// # Examples
///
/// ```
/// use icu::locid::extensions::transform::Value;
///
/// let value1: Value = "hybrid".parse().expect("Failed to parse a Value.");
/// let value2: Value =
///     "hybrid-foobar".parse().expect("Failed to parse a Value.");
///
/// assert_eq!(&value1.to_string(), "hybrid");
/// assert_eq!(&value2.to_string(), "hybrid-foobar");
/// ```
#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
pub struct Value(Vec<TinyAsciiStr<{ *TYPE_LENGTH.end() }>>);

const TYPE_LENGTH: RangeInclusive<usize> = 3..=8;
const TRUE_TVALUE: TinyAsciiStr<8> = tinystr::tinystr!(8, "true");

impl Value {
    /// A constructor which takes a utf8 slice, parses it and
    /// produces a well-formed [`Value`].
    ///
    /// # Examples
    ///
    /// ```
    /// use icu::locid::extensions::transform::Value;
    ///
    /// let value = Value::try_from_bytes(b"hybrid").expect("Parsing failed.");
    ///
    /// assert_eq!(&value.to_string(), "hybrid");
    /// ```
    pub fn try_from_bytes(input: &[u8]) -> Result<Self, ParserError> {
        let mut v = vec![];
        let mut has_value = false;

        for subtag in get_subtag_iterator(input) {
            if !Self::is_type_subtag(subtag) {
                return Err(ParserError::InvalidExtension);
            }
            has_value = true;
            let val =
                TinyAsciiStr::from_bytes(subtag).map_err(|_| ParserError::InvalidExtension)?;
            if val != TRUE_TVALUE {
                v.push(val);
            }
        }

        if !has_value {
            return Err(ParserError::InvalidExtension);
        }
        Ok(Self(v))
    }

    pub(crate) fn from_vec_unchecked(input: Vec<TinyAsciiStr<{ *TYPE_LENGTH.end() }>>) -> Self {
        Self(input)
    }

    pub(crate) fn is_type_subtag(t: &[u8]) -> bool {
        TYPE_LENGTH.contains(&t.len()) && !t.iter().any(|c: &u8| !c.is_ascii_alphanumeric())
    }

    pub(crate) fn parse_subtag(
        t: &[u8],
    ) -> Result<Option<TinyAsciiStr<{ *TYPE_LENGTH.end() }>>, ParserError> {
        let s = TinyAsciiStr::from_bytes(t).map_err(|_| ParserError::InvalidSubtag)?;
        if !TYPE_LENGTH.contains(&t.len()) || !s.is_ascii_alphanumeric() {
            return Err(ParserError::InvalidExtension);
        }

        let s = s.to_ascii_lowercase();

        if s == TRUE_TVALUE {
            Ok(None)
        } else {
            Ok(Some(s))
        }
    }

    pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
    where
        F: FnMut(&str) -> Result<(), E>,
    {
        if self.0.is_empty() {
            f("true")?;
        } else {
            self.0.iter().map(TinyAsciiStr::as_str).try_for_each(f)?;
        }
        Ok(())
    }
}

impl FromStr for Value {
    type Err = ParserError;

    fn from_str(source: &str) -> Result<Self, Self::Err> {
        Self::try_from_bytes(source.as_bytes())
    }
}

impl_writeable_for_tinystr_list!(Value, "true", "hybrid", "foobar");