use std::fmt;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct EntityKind(Arc<str>);
pub mod kinds {
pub const EMAIL: &str = "EMAIL";
pub const PHONE: &str = "PHONE";
pub const CARD: &str = "CARD";
pub const IBAN: &str = "IBAN";
pub const SECRET: &str = "SECRET";
pub const IP: &str = "IP";
pub const URL: &str = "URL";
pub const NATID: &str = "NATID";
pub const PERSON: &str = "PERSON";
pub const ORG: &str = "ORG";
pub const LOCATION: &str = "LOCATION";
}
#[derive(Debug, thiserror::Error)]
#[error("invalid type name {0:?}: use 1 to 32 characters A-Z, 0-9 and _, starting with a letter")]
pub struct InvalidKind(pub String);
impl EntityKind {
pub fn new(name: &str) -> Result<Self, InvalidKind> {
let valid = (1..=32).contains(&name.len())
&& name.starts_with(|c: char| c.is_ascii_uppercase())
&& name.bytes().all(|b| b.is_ascii_uppercase() || b.is_ascii_digit() || b == b'_');
if valid {
Ok(EntityKind(name.into()))
} else {
Err(InvalidKind(name.to_owned()))
}
}
pub(crate) fn builtin(name: &'static str) -> Self {
EntityKind(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for EntityKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_names() {
assert!(EntityKind::new("EMAIL").is_ok());
assert!(EntityKind::new("NATIONAL_ID2").is_ok());
for bad in ["", "email", "1A", "A-B", "A B", &"A".repeat(33)] {
assert!(EntityKind::new(bad).is_err(), "{bad}");
}
}
}