use thiserror::Error;
#[derive(Error, Debug)]
pub enum KeyError {
#[error("Contains non-ascii characters")]
NonAscii,
#[error("Contains non-alphanumeric characters")]
NonAlpha,
#[error("Contains forbidden sequence `..` `--`")]
ForbiddenSequence,
#[error("Invalid start char `.-_` ")]
ForbiddenFirst,
#[error("Invalid last char `.-_`")]
ForbiddenLast,
#[error("Not int or uuid")]
InvalidToken,
}
const fn bad_edge(c: char) -> bool {
matches!(c, '.' | '-' | '_')
}
const fn is_permitted(c: char) -> bool {
matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '_' | '-')
}
pub(crate) fn valid_key(key: &str) -> Result<(), KeyError> {
if key.starts_with(bad_edge) {
return Err(KeyError::ForbiddenFirst);
}
if key.ends_with(bad_edge) {
return Err(KeyError::ForbiddenLast);
}
if !key.chars().all(is_permitted) {
return Err(KeyError::NonAscii);
}
if key.contains("..") || key.contains("--") {
return Err(KeyError::ForbiddenSequence);
}
Ok(())
}
pub(crate) fn valid_token(token: &str) -> Result<(), KeyError> {
use std::str::FromStr;
if i64::from_str(token).is_ok() {
return Ok(());
}
if uuid::Uuid::parse_str(token).is_ok() {
return Ok(());
}
Err(KeyError::InvalidToken)
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn good_cases() -> Result<(), KeyError> {
valid_key("abc")?;
valid_key("a.b.c")?;
valid_key("a.b.c")?;
valid_key("a_b.c")?;
valid_key("a_b_c")?;
valid_key("a1.b2.c3")?;
valid_key("a1.-b2.c3")?;
valid_key("a1.b2-.c3")?;
Ok(())
}
#[test]
fn invalid_starts() {
valid_key(".a.b.c").unwrap_err();
valid_key("-a.b.c").unwrap_err();
valid_key("_a.b.c").unwrap_err();
}
#[test]
fn invalid_ends() {
valid_key("a.b.c-").unwrap_err();
valid_key("a.b.c.").unwrap_err();
valid_key("a.b.c_").unwrap_err();
}
#[test]
fn invalid_sequence() {
valid_key("a..b.c").unwrap_err();
valid_key("a.b--b.c").unwrap_err();
}
#[test]
fn invalid_examples() {
valid_key("a b.c").unwrap_err();
valid_key("a.b.c ").unwrap_err();
valid_key(" a.b.c").unwrap_err();
}
#[test]
fn good_tokens() -> Result<(), KeyError> {
valid_token("0")?;
valid_token("1637108572")?;
valid_token("f1a8e0d2-630b-4100-954a-54f98bf071f3")?;
valid_token("-64")?;
valid_token("98e657b046b64733a68293fb5c7c0b77")?;
Ok(())
}
#[test]
fn bad_tokens() {
valid_token("").unwrap_err();
valid_token("qwerty").unwrap_err();
valid_token("f1abc123").unwrap_err();
valid_token(" ").unwrap_err();
valid_token("\n").unwrap_err();
}
}