use std::num::NonZero;
use dizzy::DstNewtype;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("invalid character {c:?} at index {index}")]
pub struct InvalidParamTextError {
pub index: usize,
pub c: char,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
#[dizzy(invariant = ParamText::str_is_paramtext, error = InvalidParamTextError)]
#[dizzy(constructor = pub new)]
#[dizzy(getter = pub const as_str)]
#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
#[dizzy(owned = pub ParamTextBuf(String))]
#[dizzy(derive_owned(Debug, IntoBoxed))]
#[repr(transparent)]
pub struct ParamText(str);
impl ParamText {
fn str_is_paramtext(s: &str) -> Result<(), InvalidParamTextError> {
for (index, c) in s.chars().enumerate() {
if !char_is_safe_char(c) {
return Err(InvalidParamTextError { index, c });
}
}
Ok(())
}
}
const fn char_is_safe_char(c: char) -> bool {
match c {
'\t' | ' ' | '!' | '#'..='+' | '-'..='9' | '<'..='~' => true,
_ => !c.is_ascii(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("invalid character {c:?} at byte index {index}")]
pub struct InvalidTextError {
pub index: usize,
pub c: char,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
#[dizzy(invariant = Text::str_is_text, error = InvalidTextError)]
#[dizzy(constructor = pub new)]
#[dizzy(getter = pub const as_str)]
#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
#[dizzy(owned = pub TextBuf(String))]
#[dizzy(derive_owned(Debug, IntoBoxed))]
#[repr(transparent)]
pub struct Text(str);
impl Text {
pub const fn char_is_valid(c: char) -> bool {
char_is_text(c)
}
pub(crate) fn str_is_text(s: &str) -> Result<(), InvalidTextError> {
for (index, c) in s.char_indices() {
if !char_is_text(c) {
return Err(InvalidTextError { index, c });
}
}
Ok(())
}
}
impl std::fmt::Display for Text {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl TextBuf {
pub fn into_string(self) -> String {
self.__dizzy_owned_inner
}
pub fn from_string(s: String) -> Result<Self, InvalidTextError> {
Text::str_is_text(&s)?;
Ok(Self {
__data: std::marker::PhantomData,
__dizzy_owned_inner: s,
})
}
pub unsafe fn from_string_unchecked(s: String) -> Self {
debug_assert!(Text::str_is_text(&s).is_ok());
Self {
__data: std::marker::PhantomData,
__dizzy_owned_inner: s,
}
}
}
impl std::fmt::Display for TextBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
const fn char_is_text(c: char) -> bool {
if c.is_ascii_control() {
c == '\t' || c == '\n'
} else {
true
}
}
#[derive(Debug, Eq)]
#[repr(transparent)]
pub struct CaselessStr(str);
impl CaselessStr {
#[inline(always)]
pub fn new(s: &str) -> &Self {
unsafe { &*(s as *const str as *const CaselessStr) }
}
#[inline(always)]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline(always)]
pub fn from_box_str(value: Box<str>) -> Box<CaselessStr> {
unsafe { Box::from_raw(Box::into_raw(value) as *mut CaselessStr) }
}
#[inline(always)]
pub fn into_box_str(self: Box<Self>) -> Box<str> {
unsafe { Box::from_raw(Box::into_raw(self) as *mut str) }
}
}
impl Clone for Box<CaselessStr> {
fn clone(&self) -> Self {
CaselessStr::from_box_str(Box::<str>::from(&self.0))
}
}
impl<'a> From<&'a str> for &'a CaselessStr {
fn from(value: &'a str) -> Self {
CaselessStr::new(value)
}
}
impl From<&str> for Box<CaselessStr> {
fn from(value: &str) -> Self {
CaselessStr::from_box_str(Box::<str>::from(value))
}
}
impl From<Box<str>> for Box<CaselessStr> {
fn from(value: Box<str>) -> Self {
CaselessStr::from_box_str(value)
}
}
impl From<String> for Box<CaselessStr> {
fn from(value: String) -> Self {
CaselessStr::from_box_str(value.into_boxed_str())
}
}
impl PartialEq for CaselessStr {
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
}
impl PartialEq<str> for CaselessStr {
fn eq(&self, other: &str) -> bool {
self.0.eq_ignore_ascii_case(other)
}
}
impl std::hash::Hash for CaselessStr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.len().hash(state);
for byte in self.0.as_bytes() {
byte.to_ascii_lowercase().hash(state);
}
}
}
impl std::fmt::Display for CaselessStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum InvalidNameError {
#[error("name must not be empty")]
EmptyString,
#[error("invalid character {c:?} at byte index {index}")]
InvalidChar {
index: usize,
c: char,
},
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
#[dizzy(invariant = Name::str_is_name, error = InvalidNameError)]
#[dizzy(constructor = pub new)]
#[dizzy(getter = pub const as_str)]
#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
#[repr(transparent)]
pub struct Name(str);
impl Name {
fn str_is_name(s: &str) -> Result<(), InvalidNameError> {
if s.is_empty() {
return Err(InvalidNameError::EmptyString);
}
for (index, c) in s.char_indices() {
if !c.is_ascii_alphanumeric() && c != '-' {
return Err(InvalidNameError::InvalidChar { index, c });
}
}
Ok(())
}
pub fn len(&self) -> NonZero<usize> {
unsafe { NonZero::new_unchecked(self.0.len()) }
}
pub fn kind(&self) -> NameKind {
NameKind::of(self.as_str()).expect("Name is non-empty")
}
}
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NameKind {
Iana,
X,
}
impl NameKind {
pub fn of(s: &str) -> Option<Self> {
match s.as_bytes().first_chunk::<2>() {
Some(b"x-" | b"X-") => Some(Self::X),
_ if !s.is_empty() => Some(Self::Iana),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidCharError {
pub byte_index: usize,
pub invalid_char: char,
}
impl InvalidCharError {
pub const fn from_char_index((byte_index, invalid_char): (usize, char)) -> Self {
Self {
byte_index,
invalid_char,
}
}
}