use std::{
fmt::{self, Display, Formatter},
str::FromStr,
};
use compose_spec_macros::{DeserializeTryFromString, SerializeDisplay};
use thiserror::Error;
#[derive(
SerializeDisplay, DeserializeTryFromString, Debug, Clone, PartialEq, Eq, PartialOrd, Ord,
)]
pub struct Name(Box<str>);
impl Name {
pub fn new<T>(name: T) -> Result<Self, InvalidNameError>
where
T: AsRef<str> + Into<Box<str>>,
{
let mut chars = name.as_ref().chars();
let first = chars.next().ok_or(InvalidNameError::Empty)?;
if !matches!(first, 'a'..='z' | '0'..='9') {
return Err(InvalidNameError::Start(first));
}
for char in chars {
if !matches!(char, 'a'..='z' | '0'..='9' | '_' | '-') {
return Err(InvalidNameError::Character(char));
}
}
Ok(Self(name.into()))
}
#[must_use]
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvalidNameError {
#[error("name cannot be empty")]
Empty,
#[error(
"invalid character `{0}`, names must start with \
a lowercase ASCII letter (a-z) or digit (0-9)"
)]
Start(char),
#[error(
"invalid character `{0}`, names must contain only \
lowercase ASCII letters (a-z), digits (0-9), underscores (_), or dashes (-)"
)]
Character(char),
}
impl TryFrom<String> for Name {
type Error = InvalidNameError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<Box<str>> for Name {
type Error = InvalidNameError;
fn try_from(value: Box<str>) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<&str> for Name {
type Error = InvalidNameError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl FromStr for Name {
type Err = InvalidNameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for Name {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<Name> for Box<str> {
fn from(value: Name) -> Self {
value.0
}
}
impl From<Name> for String {
fn from(value: Name) -> Self {
value.0.into_string()
}
}