1use 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
20pub struct QString {
21 pub(crate) qualifier: Option<String>,
22 pub(crate) value: String,
23}
24
25#[derive(Clone, Debug)]
29pub enum QStringError {
30 ComponentInvalid,
32 TooManySeparators,
34}
35
36const SEPARATOR: &str = ":";
41
42impl QString {
43 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 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 pub fn empty() -> Self {
73 QString {
74 qualifier: None,
75 value: "".to_string(),
76 }
77 }
78
79 pub fn is_empty(&self) -> bool {
83 self.value.is_empty()
84 }
85
86 pub fn qualifier(&self) -> &Option<String> {
90 &self.qualifier
91 }
92
93 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
177fn 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#[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}