use crate::DomainError;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use smol_str::SmolStr;
use std::{convert::TryFrom, fmt, str::FromStr};
#[inline]
fn invalid_symbol(value: &str) -> DomainError {
DomainError::InvalidSymbol {
value: value.to_string(),
}
}
fn normalize_symbol(input: &str) -> Result<SmolStr, DomainError> {
let trimmed = input.trim_ascii();
if trimmed.is_empty() {
return Err(invalid_symbol(input));
}
if trimmed.len() > 64 {
return Err(invalid_symbol(input));
}
let mut has_lowercase = false;
for byte in trimmed.bytes() {
if !byte.is_ascii() || byte.is_ascii_whitespace() || byte.is_ascii_control() {
return Err(invalid_symbol(input));
}
has_lowercase |= byte.is_ascii_lowercase();
}
if has_lowercase {
let mut buf = trimmed.to_owned();
buf.make_ascii_uppercase();
Ok(SmolStr::new(buf))
} else {
Ok(SmolStr::new(trimmed))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Symbol(SmolStr);
impl Symbol {
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", err))]
pub fn new(value: &str) -> Result<Self, DomainError> {
let normalized = normalize_symbol(value)?;
Ok(Self(normalized))
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
false
}
}
impl AsRef<str> for Symbol {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Symbol {
type Err = DomainError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl TryFrom<String> for Symbol {
type Error = DomainError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(&value)
}
}
impl From<Symbol> for String {
fn from(value: Symbol) -> Self {
value.0.into()
}
}
impl Serialize for Symbol {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Symbol {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Self::try_from(raw).map_err(de::Error::custom)
}
}