aws_iam/model/
qstring.rs

1/*!
2Provides a namespace-qualified string.
3*/
4
5use regex::Regex;
6use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
7use std::fmt::Display;
8use std::fmt::{self, Error, Formatter};
9use std::str::FromStr;
10use std::string::ToString;
11
12// ------------------------------------------------------------------------------------------------
13// Public Types
14// ------------------------------------------------------------------------------------------------
15
16///
17/// A Qualified String, i.e. `prefix:value`
18///
19#[derive(Clone, Debug, PartialEq, Eq, Hash)]
20pub struct QString {
21    pub(crate) qualifier: Option<String>,
22    pub(crate) value: String,
23}
24
25///
26/// Errors that may arise when parsing using `FromStr::from_str()`.
27///
28#[derive(Clone, Debug)]
29pub enum QStringError {
30    /// One part of the qualified string is invalid.
31    ComponentInvalid,
32    /// Only one ':' is allowed.
33    TooManySeparators,
34}
35
36// ------------------------------------------------------------------------------------------------
37// Implementations
38// ------------------------------------------------------------------------------------------------
39
40const SEPARATOR: &str = ":";
41
42impl QString {
43    ///
44    /// Create a new qualified string with both `qualifier` and `value`.
45    ///
46    pub fn new(qualifier: String, value: String) -> Self {
47        match (validate_part(&qualifier), validate_part(&value)) {
48            (Ok(_), Ok(_)) => QString {
49                qualifier: Some(qualifier),
50                value,
51            },
52            _ => panic!("Invalid format for qualifier or value"),
53        }
54    }
55
56    ///
57    /// Create a new qualified string with only a `value`.
58    ///
59    pub fn unqualified(value: String) -> Self {
60        match validate_part(&value) {
61            Ok(_) => QString {
62                qualifier: None,
63                value,
64            },
65            _ => panic!("Invalid format for value: '{}'", value),
66        }
67    }
68
69    ///
70    /// Construct an empty qualified string
71    ///
72    pub fn empty() -> Self {
73        QString {
74            qualifier: None,
75            value: "".to_string(),
76        }
77    }
78
79    ///
80    /// Determines if the `value` part of this qualified string is empty.
81    ///
82    pub fn is_empty(&self) -> bool {
83        self.value.is_empty()
84    }
85
86    ///
87    /// Return the `qualifier` part of this qualified string.
88    ///
89    pub fn qualifier(&self) -> &Option<String> {
90        &self.qualifier
91    }
92
93    ///
94    /// Return the `value` part of this qualified string.
95    ///
96    pub fn value(&self) -> &String {
97        &self.value
98    }
99}
100
101impl Display for QString {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
103        match &self.qualifier {
104            Some(qualifier) => write!(f, "{}{}{}", qualifier, SEPARATOR, &self.value),
105            None => write!(f, "{}", &self.value),
106        }
107    }
108}
109
110impl FromStr for QString {
111    type Err = QStringError;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        if s.is_empty() {
115            Ok(QString::unqualified(s.to_string()))
116        } else {
117            let parts = s.split(SEPARATOR).collect::<Vec<&str>>();
118            match parts.len() {
119                1 => Ok(QString::unqualified(validate_part(parts.get(0).unwrap())?)),
120                2 => Ok(QString::new(
121                    validate_part(parts.get(0).unwrap())?,
122                    validate_part(parts.get(1).unwrap())?,
123                )),
124                _ => Err(QStringError::TooManySeparators),
125            }
126        }
127    }
128}
129impl Serialize for QString {
130    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131    where
132        S: Serializer,
133    {
134        serializer.serialize_str(&self.to_string())
135    }
136}
137
138impl<'de> Deserialize<'de> for QString {
139    fn deserialize<D>(deserializer: D) -> Result<QString, D::Error>
140    where
141        D: Deserializer<'de>,
142    {
143        deserializer.deserialize_string(QStringVisitor)
144    }
145}
146
147struct QStringVisitor;
148
149impl<'de> Visitor<'de> for QStringVisitor {
150    type Value = QString;
151
152    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
153        formatter.write_str("a qualified string")
154    }
155
156    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
157    where
158        E: de::Error,
159    {
160        QString::from_str(value).map_err(de::Error::custom)
161    }
162
163    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
164    where
165        E: de::Error,
166    {
167        self.visit_str(&value)
168    }
169}
170
171impl Display for QStringError {
172    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
173        write!(f, "{:?}", self)
174    }
175}
176
177// ------------------------------------------------------------------------------------------------
178// Private Functions
179// ------------------------------------------------------------------------------------------------
180
181fn validate_part(part: &str) -> Result<String, QStringError> {
182    lazy_static! {
183        static ref ID: Regex = Regex::new(r"^(\*|[a-zA-Z\*][a-zA-Z0-9\-_\*/]*)$").unwrap();
184    }
185    if part.is_empty() || ID.is_match(part) {
186        Ok(part.to_string())
187    } else {
188        Err(QStringError::ComponentInvalid)
189    }
190}
191
192// ------------------------------------------------------------------------------------------------
193// Unit Tests
194// ------------------------------------------------------------------------------------------------
195
196#[cfg(test)]
197mod test {
198    use super::*;
199
200    #[test]
201    fn test_valid_new() {
202        let q_string = QString::new(String::from("foo"), String::from("bar"));
203        assert_eq!(q_string.qualifier(), &Some(String::from("foo")));
204        assert_eq!(q_string.value(), &String::from("bar"));
205    }
206
207    #[test]
208    fn test_valid_unqualified() {
209        let q_string = QString::unqualified(String::from("bar"));
210        assert_eq!(q_string.qualifier(), &None);
211        assert_eq!(q_string.value(), &String::from("bar"));
212    }
213
214    #[test]
215    fn test_valid_from_str() {
216        let q_string = QString::from_str("foo:bar").unwrap();
217        assert_eq!(q_string.qualifier(), &Some(String::from("foo")));
218        assert_eq!(q_string.value(), &String::from("bar"));
219
220        let q_string = QString::from_str("bar").unwrap();
221        assert_eq!(q_string.qualifier(), &None);
222        assert_eq!(q_string.value(), &String::from("bar"));
223    }
224}