use regex::{Error, Regex};
#[derive(Debug, PartialEq)]
pub enum IdShortErrorReason {
TooShort,
TooLong,
NoMatch(String),
RegexError(regex::Error),
}
#[derive(Debug, PartialEq)]
pub struct IdShortError {
reason: IdShortErrorReason,
}
impl IdShortError {
pub fn new(reason: IdShortErrorReason) -> IdShortError {
IdShortError { reason }
}
}
impl From<regex::Error> for IdShortError {
fn from(value: Error) -> Self {
IdShortError::new(IdShortErrorReason::RegexError(value))
}
}
pub fn id_short_from_str(hopefully_id_short: &str) -> Result<String, IdShortError> {
id_short_from_string(hopefully_id_short.to_string())
}
pub fn id_short_from_string(hopefully_id_short: String) -> Result<String, IdShortError> {
if hopefully_id_short.is_empty() {
Err(IdShortError::new(IdShortErrorReason::TooShort))
} else if hopefully_id_short.len() > 128 {
Err(IdShortError::new(IdShortErrorReason::TooLong))
} else {
let re = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]*$")?;
let re2 = Regex::new(
r"^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$",
)?;
if re.is_match(hopefully_id_short.as_str()) && re2.is_match(hopefully_id_short.as_str()) {
Ok(hopefully_id_short)
} else {
Err(IdShortError::new(IdShortErrorReason::NoMatch(
hopefully_id_short,
)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn given_empty_string_then_test_fails() {
let result = id_short_from_str("");
assert_eq!(result, Err(IdShortError::new(IdShortErrorReason::TooShort)));
}
#[test]
fn given_string_too_long_then_test_fails() {
let result = id_short_from_str(
"a123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
012345678",
); assert_eq!(result, Err(IdShortError::new(IdShortErrorReason::TooLong)));
}
#[test]
fn given_length_max_then_test_passes() {
let input = "a123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
0123456789\
01234567"
.to_string();
let result = id_short_from_string(input.clone());
assert_eq!(result, Ok(input));
}
#[test]
fn given_first_character_when_question_mark_then_test_fails() {
let input = "?Hello";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
#[test]
fn given_first_character_when_underscore_then_test_fails() {
let input = "_Hello";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
#[test]
fn given_valid_str_then_passes() {
let result = id_short_from_str("my_new_id_short");
assert_eq!(result, Ok(String::from("my_new_id_short")));
}
#[test]
fn given_single_digit_then_test_fails() {
let input = "1";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
#[test]
fn given_string_len_1_lowercase_test_passes() {
let result = id_short_from_str("a");
assert_eq!(result, Ok("a".to_string()));
}
#[test]
fn given_string_len_1_uppercase_test_passes() {
let result = id_short_from_str("A");
assert_eq!(result, Ok("A".to_string()));
}
#[test]
fn given_space_then_test_fails() {
let input = " ";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
#[test]
fn given_leading_space_then_test_fails() {
let input = " hello";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
#[test]
fn given_trailing_space_then_test_fails() {
let input = "hello ";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
#[test]
fn given_space_in_middle_then_test_fails() {
let input = "hello world";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
#[test]
fn given_invalid_character_then_test_fails() {
let input = "hello?world";
let result = id_short_from_str(input);
assert_eq!(
result,
Err(IdShortError::new(IdShortErrorReason::NoMatch(
input.to_string()
)))
);
}
}