modio-logger 0.11.1

modio-logger Dbus service
Documentation
// Author: D.S. Ljungmark <spider@skuggor.se>, Modio AB
// SPDX-License-Identifier: AGPL-3.0-or-later
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,
}

/// A pattern match for bad edge characters
const fn bad_edge(c: char) -> bool {
    matches!(c, '.' | '-' | '_')
}

/// A pattern match for permitted characters
const fn is_permitted(c: char) -> bool {
    matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '_' | '-')
}

pub(crate) fn valid_key(key: &str) -> Result<(), KeyError> {
    // Peek test first as they are cheap
    if key.starts_with(bad_edge) {
        return Err(KeyError::ForbiddenFirst);
    }
    // Peek test first as they are cheap
    if key.ends_with(bad_edge) {
        return Err(KeyError::ForbiddenLast);
    }

    // Now char-wise parse, more expensive as it digs through it all.
    if !key.chars().all(is_permitted) {
        return Err(KeyError::NonAscii);
    }
    // Next up, pattern (substring) search for forbidden sequences.
    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();
    }
}