Skip to main content

systemprompt_database/admin/
identifier.rs

1//! Validated `PostgreSQL` identifier wrapper used wherever a raw table name or
2//! column name flows from user input into a SQL string.
3
4use std::fmt;
5
6use thiserror::Error;
7
8const MAX_IDENTIFIER_LEN: usize = 63;
9
10#[derive(Debug, Clone, Copy, Error)]
11pub enum IdentifierError {
12    #[error("identifier is empty")]
13    Empty,
14    #[error("identifier length {0} exceeds `PostgreSQL` limit of {MAX_IDENTIFIER_LEN}")]
15    TooLong(usize),
16    #[error("identifier must start with an ASCII letter or underscore")]
17    BadLead,
18    #[error("identifier contains invalid character {0:?}")]
19    InvalidChar(char),
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SafeIdentifier(String);
24
25impl SafeIdentifier {
26    pub fn parse(raw: &str) -> Result<Self, IdentifierError> {
27        if raw.is_empty() {
28            return Err(IdentifierError::Empty);
29        }
30        if raw.len() > MAX_IDENTIFIER_LEN {
31            return Err(IdentifierError::TooLong(raw.len()));
32        }
33        let mut chars = raw.chars();
34        let first = chars.next().ok_or(IdentifierError::Empty)?;
35        if !(first.is_ascii_alphabetic() || first == '_') {
36            return Err(IdentifierError::BadLead);
37        }
38        for c in chars {
39            if !(c.is_ascii_alphanumeric() || c == '_') {
40                return Err(IdentifierError::InvalidChar(c));
41            }
42        }
43        Ok(Self(raw.to_string()))
44    }
45
46    pub fn as_str(&self) -> &str {
47        &self.0
48    }
49}
50
51impl fmt::Display for SafeIdentifier {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        f.write_str(&self.0)
54    }
55}