systemprompt-database 0.6.1

PostgreSQL infrastructure for systemprompt.io AI governance. SQLx-backed pool, generic repository traits, and compile-time query verification. Part of the systemprompt.io AI governance pipeline.
Documentation
//! Validated `PostgreSQL` identifier wrapper used wherever a raw table name or
//! column name flows from user input into a SQL string.

use std::fmt;

use thiserror::Error;

const MAX_IDENTIFIER_LEN: usize = 63;

#[derive(Debug, Clone, Copy, Error)]
pub enum IdentifierError {
    #[error("identifier is empty")]
    Empty,
    #[error("identifier length {0} exceeds `PostgreSQL` limit of {MAX_IDENTIFIER_LEN}")]
    TooLong(usize),
    #[error("identifier must start with an ASCII letter or underscore")]
    BadLead,
    #[error("identifier contains invalid character {0:?}")]
    InvalidChar(char),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SafeIdentifier(String);

impl SafeIdentifier {
    pub fn parse(raw: &str) -> Result<Self, IdentifierError> {
        if raw.is_empty() {
            return Err(IdentifierError::Empty);
        }
        if raw.len() > MAX_IDENTIFIER_LEN {
            return Err(IdentifierError::TooLong(raw.len()));
        }
        let mut chars = raw.chars();
        let first = chars.next().ok_or(IdentifierError::Empty)?;
        if !(first.is_ascii_alphabetic() || first == '_') {
            return Err(IdentifierError::BadLead);
        }
        for c in chars {
            if !(c.is_ascii_alphanumeric() || c == '_') {
                return Err(IdentifierError::InvalidChar(c));
            }
        }
        Ok(Self(raw.to_string()))
    }

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

impl fmt::Display for SafeIdentifier {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.0)
    }
}