use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::ops::Deref;
use uuid::Uuid;
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, PartialOrd, Ord)]
pub struct Identifier(pub(crate) &'static str);
impl Identifier {
pub fn new(s: &'static str) -> Result<Self, IdentifierError> {
if !Self::is_valid_identifier(s) {
return Err(IdentifierError::new(&format!("Invalid identifier: {s}")));
}
Ok(Self(s))
}
pub const unsafe fn unsafe_new(s: &'static str) -> Self {
Self(s)
}
pub fn is_valid_identifier(s: &str) -> bool {
lazy_static! {
static ref RE: Regex = Regex::new("^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap();
}
RE.is_match(s)
}
pub const fn into_inner(&self) -> &str {
self.0
}
}
#[macro_export]
macro_rules! ident {
(#$id:ident) => {{
let ident;
unsafe { ident = $crate::Identifier::unsafe_new(stringify!($id)) }
ident
}};
}
impl Display for Identifier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug)]
pub struct IdentifierError(String);
impl IdentifierError {
fn new(s: &str) -> Self {
IdentifierError(s.to_string())
}
}
impl Display for IdentifierError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<&'static str> for Identifier {
type Error = IdentifierError;
fn try_from(value: &'static str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl Deref for Identifier {
type Target = &'static str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Error for IdentifierError {}
macro_rules! impl_identifier_type {
($($type:ident),+) =>{
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum IdentifierType{
$($type,)+
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[allow(non_camel_case_types)]
pub enum IdentifierValue{
$($type($type),)+
}
impl std::fmt::Display for IdentifierValue{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self{
$(Self::$type(value) => write!(f, "{}", value),)+
}
}
}
$(impl IntoIdentifierValue for $type{
const TYPE: IdentifierType = IdentifierType::$type;
fn into_identifier_value(self) -> IdentifierValue {
IdentifierValue::$type(self)
}
})+
$(impl IntoIdentifierValue for &$type{
const TYPE: IdentifierType = IdentifierType::$type;
fn into_identifier_value(self) -> IdentifierValue {
IdentifierValue::$type(self.clone())
}
})+
impl IntoIdentifierValue for &str{
const TYPE: IdentifierType = IdentifierType::String;
fn into_identifier_value(self) -> IdentifierValue {
IdentifierValue::String(self.to_string())
}
}
};
}
impl_identifier_type! {String, i64, Uuid}
pub trait IntoIdentifierValue {
const TYPE: IdentifierType;
fn into_identifier_value(self) -> IdentifierValue;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_can_create_valid_identifier() {
let identifier = Identifier::new("hello_world").unwrap();
assert_eq!(*identifier, "hello_world".to_string());
}
#[test]
fn it_cannot_create_empty_identifier() {
let err = Identifier::new("").unwrap_err();
assert_eq!(err.to_string(), "Invalid identifier: ");
}
#[test]
fn it_cannot_create_identifier_with_spaces() {
let err = Identifier::new("hello world").unwrap_err();
assert_eq!(err.to_string(), "Invalid identifier: hello world");
}
#[test]
fn it_cannot_create_non_ascii_identifier() {
let err = Identifier::new("héllo").unwrap_err();
assert_eq!(err.to_string(), "Invalid identifier: héllo");
}
#[test]
fn it_can_parse_identifier_from_string() {
let identifier: Identifier = "hello_world".try_into().unwrap();
assert_eq!(*identifier, "hello_world".to_string());
}
#[test]
fn it_cannot_parse_empty_identifier() {
let err = TryInto::<Identifier>::try_into("").unwrap_err();
assert_eq!(err.to_string(), "Invalid identifier: ");
}
#[test]
fn it_cannot_parse_identifier_with_spaces() {
let err = TryInto::<Identifier>::try_into("hello world").unwrap_err();
assert_eq!(err.to_string(), "Invalid identifier: hello world");
}
#[test]
fn it_cannot_parse_non_ascii_identifier() {
let err = TryInto::<Identifier>::try_into("héllo").unwrap_err();
assert_eq!(err.to_string(), "Invalid identifier: héllo");
}
#[test]
fn it_converts_string_into_identifier_value() {
let string_value: &'static str = "my_string";
let identifier_value = string_value.into_identifier_value();
assert_eq!(
identifier_value,
IdentifierValue::String("my_string".to_string())
);
}
#[test]
fn it_converts_integer_into_identifier_value() {
let number: i64 = 42;
let identifier_value = number.into_identifier_value();
assert_eq!(identifier_value, IdentifierValue::i64(42));
}
#[test]
fn it_converts_uuid_into_identifier_value() {
let uuid_value = uuid::Uuid::new_v4();
let identifier_value = uuid_value.into_identifier_value();
assert_eq!(identifier_value, IdentifierValue::Uuid(uuid_value));
}
}