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