1use std::convert::TryFrom;
10
11use crate::{
12 EnsureValid,
13 ParseError,
14 TrimmedNonBlankString,
15 NonEmptyString,
16 StringType,
17 NonBlankString,
18 Display,
19 FromStr,
20 string_type
21};
22
23#[derive(Display, FromStr)]
25#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))]
26#[string_type]
27pub struct HexString(TrimmedNonBlankString);
28
29impl HexString {
30 pub fn to_lower(self) -> LowerHexString {
32 let mut s: String = self.0.into();
33 s.make_ascii_lowercase();
34 s.parse().expect("Started as HexString")
35 }
36
37 pub fn to_upper(self) -> UpperHexString {
39 let mut s: String = self.0.into();
40 s.make_ascii_uppercase();
41 s.parse().expect("Started as HexString")
42 }
43}
44
45impl EnsureValid for HexString {
46 type ParseErr = ParseError;
47
48 fn ensure_valid(s: &str) -> Result<(), Self::ParseErr> {
55 if s.is_empty() { return Err(ParseError::EmptyString); }
56
57 if let Some((i, c)) = s.chars().enumerate()
58 .find(|(_i, c)| !c.is_ascii_hexdigit()) {
59 return Err(ParseError::NonHexDigit(i, c));
60 }
61 if (s.chars().count() % 2) == 1 {
62 return Err(ParseError::OddLength);
63 }
64 Ok(())
65 }
66}
67
68impl From<HexString> for NonBlankString {
70 fn from(value: HexString) -> Self { value.to_inner().into() }
71}
72
73impl From<HexString> for NonEmptyString {
75 fn from(value: HexString) -> Self { value.to_inner().into() }
76}
77
78impl TryFrom<NonEmptyString> for HexString {
79 type Error = <Self as EnsureValid>::ParseErr;
80
81 fn try_from(value: NonEmptyString) -> Result<Self, Self::Error> {
82 value.as_str().parse()
83 }
84}
85
86impl TryFrom<TrimmedNonBlankString> for HexString {
87 type Error = <Self as EnsureValid>::ParseErr;
88
89 fn try_from(value: TrimmedNonBlankString) -> Result<Self, Self::Error> {
90 value.as_str().parse()
91 }
92}
93
94#[derive(Display, FromStr)]
96#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))]
97#[string_type]
98pub struct LowerHexString(HexString);
99
100impl EnsureValid for LowerHexString {
101 type ParseErr = ParseError;
102
103 fn ensure_valid(s: &str) -> Result<(), Self::ParseErr> {
110 if s.is_empty() { return Err(ParseError::EmptyString); }
111
112 if let Some((i, c)) = s.chars().enumerate()
113 .find(|(_i, c)| !c.is_ascii_digit() && !c.is_ascii_lowercase()) {
114 return Err(ParseError::NonHexDigit(i, c));
115 }
116 if (s.chars().count() % 2) == 1 {
117 return Err(ParseError::OddLength);
118 }
119 Ok(())
120 }
121}
122
123impl From<LowerHexString> for NonEmptyString {
125 fn from(value: LowerHexString) -> Self { value.to_inner().into() }
126}
127
128impl From<LowerHexString> for NonBlankString {
130 fn from(value: LowerHexString) -> Self { value.to_inner().into() }
131}
132
133impl TryFrom<NonEmptyString> for LowerHexString {
134 type Error = <Self as EnsureValid>::ParseErr;
135
136 fn try_from(value: NonEmptyString) -> Result<Self, Self::Error> {
137 value.as_str().parse()
138 }
139}
140
141impl TryFrom<TrimmedNonBlankString> for LowerHexString {
142 type Error = <Self as EnsureValid>::ParseErr;
143
144 fn try_from(value: TrimmedNonBlankString) -> Result<Self, Self::Error> {
145 value.as_str().parse()
146 }
147}
148
149#[derive(Display, FromStr)]
151#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))]
152#[string_type]
153pub struct UpperHexString(HexString);
154
155impl EnsureValid for UpperHexString {
156 type ParseErr = ParseError;
157
158 fn ensure_valid(s: &str) -> Result<(), Self::ParseErr> {
165 if s.is_empty() { return Err(ParseError::EmptyString); }
166
167 if let Some((i, c)) = s.chars().enumerate()
168 .find(|(_i, c)| !c.is_ascii_digit() && !c.is_ascii_uppercase()) {
169 return Err(ParseError::NonHexDigit(i, c));
170 }
171 if (s.chars().count() % 2) == 1 {
172 return Err(ParseError::OddLength);
173 }
174 Ok(())
175 }
176}
177
178impl From<UpperHexString> for NonEmptyString {
180 fn from(value: UpperHexString) -> Self { value.to_inner().into() }
181}
182
183impl From<UpperHexString> for NonBlankString {
185 fn from(value: UpperHexString) -> Self { value.to_inner().into() }
186}
187
188impl TryFrom<NonEmptyString> for UpperHexString {
189 type Error = <Self as EnsureValid>::ParseErr;
190
191 fn try_from(value: NonEmptyString) -> Result<Self, Self::Error> {
192 value.as_str().parse()
193 }
194}
195
196impl TryFrom<TrimmedNonBlankString> for UpperHexString {
197 type Error = <Self as EnsureValid>::ParseErr;
198
199 fn try_from(value: TrimmedNonBlankString) -> Result<Self, Self::Error> {
200 value.as_str().parse()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::{
207 TrimmedNonBlankString,
208 HexString,
209 LowerHexString,
210 StringType,
211 UpperHexString
212 };
213 use crate::ParseError;
214
215 use assert2::{assert, let_assert};
216 use rstest::rstest;
217
218 #[rstest]
219 #[case("", ParseError::EmptyString, "empty string")]
220 #[case("ertyui", ParseError::NonHexDigit(1, 'r'), "bad character")]
221 #[case("deadbef", ParseError::OddLength, "odd length")]
222 fn hexstring_parse_unsuccess(#[case]val: &str, #[case]err: ParseError, #[case]msg: &str) {
223 let_assert!(Err(e) = val.parse::<HexString>());
224 assert!(e == err, "parse error ({})", msg);
225 }
226
227 #[rstest]
228 #[case("deadbeef", "deadbeef", "lowercase hex string")]
229 #[case("DEADBEEF", "DEADBEEF", "uppercase hex string")]
230 #[case("DeaDbeEF", "DeaDbeEF", "mixed case hex string")]
231 #[case(" deaDbeAF", "deaDbeAF", "leading whitespace trimmed")]
232 #[case(" deaDbeAF ", "deaDbeAF", "surrounding whitespace trimmed")]
233 fn hexstring_parse_success(#[case]val: &str, #[case]slice: &str, #[case]msg: &str) {
234 let_assert!(Ok(s) = val.parse::<HexString>());
235 assert!(s.as_str() == slice, "success ({})", msg);
236 }
237
238 #[test]
239 fn hexstring_convert_to_string() {
240 let_assert!{Ok(s) = "deADBEef".parse::<HexString>()};
241 let st: String = s.into();
242 assert!(st == String::from("deADBEef"));
243 }
244
245 #[test]
246 fn hexstring_convert_to_inner() {
247 let_assert!{Ok(inner) = "deADBEef".parse::<TrimmedNonBlankString>()};
248 let_assert!{Ok(s) = "deADBEef".parse::<HexString>()};
249 assert!(inner == s.into());
250 }
251
252 #[test]
253 fn hexstring_to_lower() {
254 let_assert!{Ok(s) = "deADBEef".parse::<HexString>()};
255 let lhs = s.to_lower();
256 assert!(lhs == "deadbeef".parse::<LowerHexString>().expect("Hardcoded safe string"));
257 }
258
259 #[test]
260 fn hexstring_to_upper() {
261 let_assert!{Ok(s) = "deADBEef".parse::<HexString>()};
262 let lhs = s.to_upper();
263 assert!(lhs == "DEADBEEF".parse::<UpperHexString>().expect("Hardcoded safe string"));
264 }
265
266 #[rstest]
267 #[case("", ParseError::EmptyString, "empty string")]
268 #[case("ertyui", ParseError::NonHexDigit(1, 'r'), "bad character")]
269 #[case("deadbef", ParseError::OddLength, "odd length")]
270 #[case("123DBEEF", ParseError::NonHexDigit(3, 'D'), "upper case")]
271 #[case("deADbeeF", ParseError::NonHexDigit(2, 'A'), "mixed case")]
272 fn lower_hexstring_parse_unsuccess(#[case]val: &str, #[case]err: ParseError, #[case]msg: &str) {
273 let_assert!(Err(e) = val.parse::<LowerHexString>());
274 assert!(e == err, "parse error ({})", msg);
275 }
276
277 #[rstest]
278 #[case("deadbeef", "deadbeef", "lowercase")]
279 #[case(" deadbeef", "deadbeef", "leading whitespace trimmed")]
280 #[case(" deadbeef ", "deadbeef", "surrounding whitespace trimmed")]
281 fn lower_hexstring_success(#[case]val: &str, #[case]slice: &str, #[case]msg: &str) {
282 let_assert!(Ok(s) = val.parse::<LowerHexString>());
283 assert!(s.as_str() == slice, "{msg}");
284 }
285
286 #[test]
287 fn lower_hexstring_convert_to_string() {
288 let_assert!{Ok(s) = "deadbeef".parse::<LowerHexString>()};
289 let st: String = s.into();
290 assert!(st == String::from("deadbeef"));
291 }
292
293 #[test]
294 fn lower_hexstring_convert_to_inner() {
295 let_assert!{Ok(inner) = "deadbeef".parse::<HexString>()};
296 let_assert!{Ok(s) = "deadbeef".parse::<LowerHexString>()};
297 assert!(inner == s.into());
298 }
299
300 #[rstest]
301 #[case("", ParseError::EmptyString, "empty string")]
302 #[case("ERTYUI", ParseError::NonHexDigit(1, 'R'), "bad character")]
303 #[case("DEADBEF", ParseError::OddLength, "odd length")]
304 #[case("1eadbeef", ParseError::NonHexDigit(1, 'e'), "upper case")]
305 #[case("DeaDbeeF", ParseError::NonHexDigit(1, 'e'), "mixed case")]
306 fn upper_hexstring_parse_unsuccess(#[case]val: &str, #[case]err: ParseError, #[case]msg: &str) {
307 let_assert!(Err(e) = val.parse::<UpperHexString>());
308 assert!(e == err, "parse error ({})", msg);
309 }
310
311 #[rstest]
312 #[case("DEADBEEF", "DEADBEEF", "lowercase")]
313 #[case(" DEADBEEF", "DEADBEEF", "leading whitespace trimmed")]
314 #[case(" DEADBEEF ", "DEADBEEF", "surrounding whitespace trimmed")]
315 fn upper_hexstring_success(#[case]val: &str, #[case]slice: &str, #[case]msg: &str) {
316 let_assert!(Ok(s) = val.parse::<UpperHexString>());
317 assert!(s.as_str() == slice, "{msg}");
318 }
319
320 #[test]
321 fn upper_hexstring_convert_to_string() {
322 let_assert!{Ok(s) = "DEADBEEF".parse::<UpperHexString>()};
323 let st: String = s.into();
324 assert!(st == String::from("DEADBEEF"));
325 }
326
327 #[test]
328 fn upper_hexstring_convert_to_inner() {
329 let_assert!{Ok(inner) = "DEADBEEF".parse::<HexString>()};
330 let_assert!{Ok(s) = "DEADBEEF".parse::<UpperHexString>()};
331 assert!(inner == s.into());
332 }
333}