eventuary-sqlite 0.1.0

SQLite event backend for eventuary
Documentation
use eventuary_core::{Error, Result};

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SqliteRelationName {
    table: String,
}

impl SqliteRelationName {
    pub fn new(name: impl Into<String>) -> Result<Self> {
        let raw = name.into();
        validate_identifier(&raw)?;
        Ok(Self { table: raw })
    }

    pub fn table(&self) -> &str {
        &self.table
    }

    pub fn render(&self) -> String {
        format!("\"{}\"", self.table)
    }
}

fn validate_identifier(identifier: &str) -> Result<()> {
    if identifier.is_empty() {
        return Err(Error::Config("empty sqlite identifier".to_owned()));
    }
    let mut chars = identifier.chars();
    let Some(first) = chars.next() else {
        return Err(Error::Config("empty sqlite identifier".to_owned()));
    };
    if !(first == '_' || first.is_ascii_alphabetic()) {
        return Err(Error::Config(format!(
            "sqlite identifier must start with letter or underscore: {identifier:?}"
        )));
    }
    if !chars.all(|c| c == '_' || c.is_ascii_alphanumeric()) {
        return Err(Error::Config(format!(
            "sqlite identifier may only contain letters, digits, underscores: {identifier:?}"
        )));
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn renders_simple_relation() {
        let relation = SqliteRelationName::new("events").unwrap();
        assert_eq!(relation.render(), "\"events\"");
    }

    #[test]
    fn rejects_invalid_relation() {
        assert!(SqliteRelationName::new("events;drop").is_err());
        assert!(SqliteRelationName::new("9events").is_err());
        assert!(SqliteRelationName::new("").is_err());
    }
}