basyx_rs/
id_short_from_string.rs1use regex::{Error, Regex};
6
7#[derive(Debug, PartialEq)]
8pub enum IdShortErrorReason {
9 TooShort,
10 TooLong,
11 NoMatch(String),
12 RegexError(regex::Error),
13}
14
15#[derive(Debug, PartialEq)]
16pub struct IdShortError {
17 reason: IdShortErrorReason,
18}
19
20impl IdShortError {
21 pub fn new(reason: IdShortErrorReason) -> IdShortError {
22 IdShortError { reason }
23 }
24}
25
26impl From<regex::Error> for IdShortError {
27 fn from(value: Error) -> Self {
28 IdShortError::new(IdShortErrorReason::RegexError(value))
29 }
30}
31
32pub fn id_short_from_str(hopefully_id_short: &str) -> Result<String, IdShortError> {
36 id_short_from_string(hopefully_id_short.to_string())
37}
38
39pub fn id_short_from_string(hopefully_id_short: String) -> Result<String, IdShortError> {
43 if hopefully_id_short.is_empty() {
44 Err(IdShortError::new(IdShortErrorReason::TooShort))
45 } else if hopefully_id_short.len() > 128 {
46 Err(IdShortError::new(IdShortErrorReason::TooLong))
47 } else {
48 let re = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]*$")?;
49 let re2 = Regex::new(
50 r"^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$",
51 )?;
52
53 if re.is_match(hopefully_id_short.as_str()) && re2.is_match(hopefully_id_short.as_str()) {
54 Ok(hopefully_id_short)
55 } else {
56 Err(IdShortError::new(IdShortErrorReason::NoMatch(
57 hopefully_id_short,
58 )))
59 }
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn given_empty_string_then_test_fails() {
69 let result = id_short_from_str("");
70 assert_eq!(result, Err(IdShortError::new(IdShortErrorReason::TooShort)));
71 }
72
73 #[test]
74 fn given_string_too_long_then_test_fails() {
75 let result = id_short_from_str(
76 "a123456789\
77 0123456789\
78 0123456789\
79 0123456789\
80 0123456789\
81 0123456789\
82 0123456789\
83 0123456789\
84 0123456789\
85 0123456789\
86 0123456789\
87 0123456789\
88 012345678",
89 ); assert_eq!(result, Err(IdShortError::new(IdShortErrorReason::TooLong)));
91 }
92
93 #[test]
94 fn given_length_max_then_test_passes() {
95 let input = "a123456789\
97 0123456789\
98 0123456789\
99 0123456789\
100 0123456789\
101 0123456789\
102 0123456789\
103 0123456789\
104 0123456789\
105 0123456789\
106 0123456789\
107 0123456789\
108 01234567"
109 .to_string();
110
111 let result = id_short_from_string(input.clone());
112 assert_eq!(result, Ok(input));
113 }
114
115 #[test]
116 fn given_first_character_when_question_mark_then_test_fails() {
117 let input = "?Hello";
118 let result = id_short_from_str(input);
119 assert_eq!(
120 result,
121 Err(IdShortError::new(IdShortErrorReason::NoMatch(
122 input.to_string()
123 )))
124 );
125 }
126
127 #[test]
128 fn given_first_character_when_underscore_then_test_fails() {
129 let input = "_Hello";
130 let result = id_short_from_str(input);
131 assert_eq!(
132 result,
133 Err(IdShortError::new(IdShortErrorReason::NoMatch(
134 input.to_string()
135 )))
136 );
137 }
138
139 #[test]
140 fn given_valid_str_then_passes() {
141 let result = id_short_from_str("my_new_id_short");
142 assert_eq!(result, Ok(String::from("my_new_id_short")));
143 }
144
145 #[test]
146 fn given_single_digit_then_test_fails() {
147 let input = "1";
148 let result = id_short_from_str(input);
149 assert_eq!(
150 result,
151 Err(IdShortError::new(IdShortErrorReason::NoMatch(
152 input.to_string()
153 )))
154 );
155 }
156
157 #[test]
158 fn given_string_len_1_lowercase_test_passes() {
159 let result = id_short_from_str("a");
160 assert_eq!(result, Ok("a".to_string()));
161 }
162
163 #[test]
164 fn given_string_len_1_uppercase_test_passes() {
165 let result = id_short_from_str("A");
166 assert_eq!(result, Ok("A".to_string()));
167 }
168
169 #[test]
171 fn given_space_then_test_fails() {
172 let input = " ";
173 let result = id_short_from_str(input);
174 assert_eq!(
175 result,
176 Err(IdShortError::new(IdShortErrorReason::NoMatch(
177 input.to_string()
178 )))
179 );
180 }
181
182 #[test]
183 fn given_leading_space_then_test_fails() {
184 let input = " hello";
185 let result = id_short_from_str(input);
186 assert_eq!(
187 result,
188 Err(IdShortError::new(IdShortErrorReason::NoMatch(
189 input.to_string()
190 )))
191 );
192 }
193
194 #[test]
195 fn given_trailing_space_then_test_fails() {
196 let input = "hello ";
197 let result = id_short_from_str(input);
198 assert_eq!(
199 result,
200 Err(IdShortError::new(IdShortErrorReason::NoMatch(
201 input.to_string()
202 )))
203 );
204 }
205
206 #[test]
207 fn given_space_in_middle_then_test_fails() {
208 let input = "hello world";
209 let result = id_short_from_str(input);
210 assert_eq!(
211 result,
212 Err(IdShortError::new(IdShortErrorReason::NoMatch(
213 input.to_string()
214 )))
215 );
216 }
217
218 #[test]
219 fn given_invalid_character_then_test_fails() {
220 let input = "hello?world";
221 let result = id_short_from_str(input);
222 assert_eq!(
223 result,
224 Err(IdShortError::new(IdShortErrorReason::NoMatch(
225 input.to_string()
226 )))
227 );
228 }
229}