#[macro_export]
macro_rules! define_string_type {
(
$(#[$struct_meta:meta])*
$struct_vis:vis struct $struct_name:ident($inner_ty:ty);
$(
#[error($ck_const:ident)]
$(#[$err_meta:meta])*
$err_vis:vis enum $err_name:ident {
$(
#[$($ck_meta:tt)*]
$ck_name:ident,
)*
}
)?
$(
#[macro]
$(#[$macro_meta:meta])*
$macro_name:ident;
)?
) => {
$(#[$struct_meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
$struct_vis struct $struct_name<TyInner: ?Sized = $inner_ty>(TyInner);
$(
$(#[$err_meta])*
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
$err_vis enum $err_name {
$($ck_name,)*
}
)?
$crate::define_string_type!(
@impl_new $struct_name($inner_ty)
$($err_name($ck_const) {
$(
#[$($ck_meta)*]
$ck_name,
)*
})?
);
impl<TyInner: ?Sized> $struct_name<TyInner> {
pub fn as_str(&self) -> &str
where TyInner: ::core::convert::AsRef<str>,
{
self.0.as_ref()
}
pub fn as_view(&self) -> &$struct_name<str>
where TyInner: ::core::convert::AsRef<str>,
{
$struct_name(self.as_str()).transpose()
}
pub fn into_inner(self) -> TyInner
where TyInner: Sized
{
self.0
}
pub const fn as_inner(&self) -> &TyInner {
&self.0
}
}
impl<'s> $struct_name<&'s str> {
pub const fn into_inner_str(self) -> &'s str {
self.0
}
pub const fn transpose(self) -> &'s $struct_name<str> {
let s: &'s str = self.into_inner_str();
let str_ptr: *const str = core::ptr::from_ref(s);
let wrapped_ptr: *const $struct_name<str> = str_ptr as *const $struct_name<str>;
unsafe {
&*wrapped_ptr
}
}
}
impl $struct_name<Box<str>> {
pub fn transpose(self) -> Box<$struct_name<str>> {
let s: Box<str> = self.into_inner();
let str_ptr: *mut str = Box::into_raw(s);
let wrapped_ptr: *mut $struct_name<str> = str_ptr as *mut $struct_name<str>;
unsafe {
Box::from_raw(wrapped_ptr)
}
}
}
};
(@impl_new $struct_name:ident($inner_ty:ty)) => {
impl<TyInner> $struct_name<TyInner> {
pub const fn new(inner: TyInner) -> Self
where TyInner: ::core::ops::Deref<Target = str>,
{
Self(inner)
}
}
impl ::core::str::FromStr for $struct_name<$inner_ty> {
type Err = ::core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(<$inner_ty>::from(s)))
}
}
};
(
@impl_new $struct_name:ident($inner_ty:ty)
$err_name:ident(const) {
$(
#[$($ck_meta:tt)*]
$ck_name:ident,
)*
}
) => {
impl<TyInner> $struct_name<TyInner> {
pub fn new(input: TyInner) -> Result<Self, $err_name>
where TyInner: ::core::ops::Deref<Target = str>,
{
match $struct_name::new_ref(&*input) {
Ok(_) => Ok(Self(input)),
Err(e) => Err(e),
}
}
}
impl<'s> $struct_name<&'s str>
{
pub const fn new_ref(input: &'s str) -> Result<&'s $struct_name<str>, $err_name> {
$(
$crate::define_string_type!(@check $err_name::$ck_name($($ck_meta)*)(input));
)*
Ok(Self(input).transpose())
}
}
impl $struct_name<Box<str>>
{
pub fn new_box(input: Box<str>) -> Result<Box<$struct_name<str>>, $err_name> {
match $struct_name::new_ref(&*input) {
Ok(_) => Ok(Self(input).transpose()),
Err(e) => Err(e),
}
}
}
impl ::core::str::FromStr for $struct_name<$inner_ty> {
type Err = $err_name;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(<$inner_ty>::from(s))
}
}
};
(
@impl_new $struct_name:ident($inner_ty:ty)
$err_name:ident(dyn) {
$(
#[$($ck_meta:tt)*]
$ck_name:ident,
)*
}
) => {
impl<TyInner> $struct_name<TyInner> {
pub fn new(input: TyInner) -> Result<Self, $err_name>
where TyInner: ::core::ops::Deref<Target = str>,
{
match $struct_name::new_ref(&*input) {
Ok(_) => Ok(Self(input)),
Err(e) => Err(e),
}
}
}
impl<'s> $struct_name<&'s str>
{
pub fn new_ref(input: &'s str) -> Result<&'s $struct_name<str>, $err_name> {
$(
$crate::define_string_type!(@check $err_name::$ck_name($($ck_meta)*)(input));
)*
Ok(Self(input).transpose())
}
}
impl ::core::str::FromStr for $struct_name<$inner_ty> {
type Err = $err_name;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(<$inner_ty>::from(s))
}
}
};
(@check $err_name:ident::$ck_name:ident(non_empty)($input:expr)) => {
if $input.is_empty() {
return Err($err_name::$ck_name);
}
};
(@check $err_name:ident::$ck_name:ident(ascii_trimmed)($input:expr)) => {
if $input.trim_ascii().len() != $input.len() {
return Err($err_name::$ck_name);
}
};
(@check $err_name:ident::$ck_name:ident(min_len($l:expr))($input:expr)) => {
if $input.len() < $l {
return Err($err_name::$ck_name);
}
};
(@check $err_name:ident::$ck_name:ident(max_len($l:expr))($input:expr)) => {
if $input.len() > $l {
return Err($err_name::$ck_name);
}
};
(@check $err_name:ident::$ck_name:ident(len($l:expr))($input:expr)) => {
if $input.len() != $l {
return Err($err_name::$ck_name);
}
};
(@check $err_name:ident::$ck_name:ident(regex($pattern:expr))($input:expr)) => {
#[allow(clippy::trivial_regex)]
static PATTERN: ::std::sync::LazyLock<::regex::Regex> = ::std::sync::LazyLock::new(|| {
let pat: &str = $pattern;
match ::regex::Regex::new(pat) {
Ok(pat) => pat,
Err(e) => panic!("regex check {}::{} pattern {pat:?} should be valid: {e}", stringify!($err_name), stringify!($ck_name)),
}
});
if !PATTERN.is_match($input) {
return Err($err_name::$ck_name);
}
};
}