1use std::num::NonZero;
4
5use dizzy::DstNewtype;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
9#[error("invalid character {c:?} at index {index}")]
10pub struct InvalidParamTextError {
11 pub index: usize,
13 pub c: char,
15}
16
17#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
26#[dizzy(invariant = ParamText::str_is_paramtext, error = InvalidParamTextError)]
27#[dizzy(constructor = pub new)]
28#[dizzy(getter = pub const as_str)]
29#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
30#[dizzy(owned = pub ParamTextBuf(String))]
31#[dizzy(derive_owned(Debug, IntoBoxed))]
32#[repr(transparent)]
33pub struct ParamText(str);
34
35impl ParamText {
36 fn str_is_paramtext(s: &str) -> Result<(), InvalidParamTextError> {
37 for (index, c) in s.chars().enumerate() {
38 if !char_is_safe_char(c) {
39 return Err(InvalidParamTextError { index, c });
40 }
41 }
42 Ok(())
43 }
44}
45
46const fn char_is_safe_char(c: char) -> bool {
55 match c {
56 '\t' | ' ' | '!' | '#'..='+' | '-'..='9' | '<'..='~' => true,
57 _ => !c.is_ascii(),
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
67#[error("invalid character {c:?} at byte index {index}")]
68pub struct InvalidTextError {
69 pub index: usize,
71 pub c: char,
73}
74
75#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
81#[dizzy(invariant = Text::str_is_text, error = InvalidTextError)]
82#[dizzy(constructor = pub new)]
83#[dizzy(getter = pub const as_str)]
84#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
85#[dizzy(owned = pub TextBuf(String))]
86#[dizzy(derive_owned(Debug, IntoBoxed))]
87#[repr(transparent)]
88pub struct Text(str);
89
90impl Text {
91 pub const fn char_is_valid(c: char) -> bool {
93 char_is_text(c)
94 }
95
96 pub(crate) fn str_is_text(s: &str) -> Result<(), InvalidTextError> {
97 for (index, c) in s.char_indices() {
98 if !char_is_text(c) {
99 return Err(InvalidTextError { index, c });
100 }
101 }
102 Ok(())
103 }
104}
105
106impl std::fmt::Display for Text {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.write_str(self.as_str())
109 }
110}
111
112impl TextBuf {
113 pub fn into_string(self) -> String {
115 self.__dizzy_owned_inner
116 }
117
118 pub fn from_string(s: String) -> Result<Self, InvalidTextError> {
120 Text::str_is_text(&s)?;
121 Ok(Self {
123 __data: std::marker::PhantomData,
124 __dizzy_owned_inner: s,
125 })
126 }
127
128 pub unsafe fn from_string_unchecked(s: String) -> Self {
133 debug_assert!(Text::str_is_text(&s).is_ok());
134 Self {
135 __data: std::marker::PhantomData,
136 __dizzy_owned_inner: s,
137 }
138 }
139}
140
141impl std::fmt::Display for TextBuf {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.write_str(self.as_str())
144 }
145}
146
147const fn char_is_text(c: char) -> bool {
152 if c.is_ascii_control() {
153 c == '\t' || c == '\n'
155 } else {
156 true
157 }
158}
159
160#[derive(Debug, Eq)]
169#[repr(transparent)]
170pub struct CaselessStr(str);
171
172impl CaselessStr {
173 #[inline(always)]
175 pub fn new(s: &str) -> &Self {
176 unsafe { &*(s as *const str as *const CaselessStr) }
178 }
179
180 #[inline(always)]
182 pub fn as_str(&self) -> &str {
183 &self.0
184 }
185
186 #[inline(always)]
188 pub fn from_box_str(value: Box<str>) -> Box<CaselessStr> {
189 unsafe { Box::from_raw(Box::into_raw(value) as *mut CaselessStr) }
191 }
192
193 #[inline(always)]
195 pub fn into_box_str(self: Box<Self>) -> Box<str> {
196 unsafe { Box::from_raw(Box::into_raw(self) as *mut str) }
198 }
199}
200
201impl Clone for Box<CaselessStr> {
202 fn clone(&self) -> Self {
203 CaselessStr::from_box_str(Box::<str>::from(&self.0))
204 }
205}
206
207impl<'a> From<&'a str> for &'a CaselessStr {
208 fn from(value: &'a str) -> Self {
209 CaselessStr::new(value)
210 }
211}
212
213impl From<&str> for Box<CaselessStr> {
214 fn from(value: &str) -> Self {
215 CaselessStr::from_box_str(Box::<str>::from(value))
216 }
217}
218
219impl From<Box<str>> for Box<CaselessStr> {
220 fn from(value: Box<str>) -> Self {
221 CaselessStr::from_box_str(value)
222 }
223}
224
225impl From<String> for Box<CaselessStr> {
226 fn from(value: String) -> Self {
227 CaselessStr::from_box_str(value.into_boxed_str())
228 }
229}
230
231impl PartialEq for CaselessStr {
232 fn eq(&self, other: &Self) -> bool {
233 self.0.eq_ignore_ascii_case(&other.0)
234 }
235}
236
237impl PartialEq<str> for CaselessStr {
238 fn eq(&self, other: &str) -> bool {
239 self.0.eq_ignore_ascii_case(other)
240 }
241}
242
243impl std::hash::Hash for CaselessStr {
244 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
245 self.0.len().hash(state);
246 for byte in self.0.as_bytes() {
247 byte.to_ascii_lowercase().hash(state);
248 }
249 }
250}
251
252impl std::fmt::Display for CaselessStr {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 f.write_str(&self.0)
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
264pub enum InvalidNameError {
265 #[error("name must not be empty")]
267 EmptyString,
268 #[error("invalid character {c:?} at byte index {index}")]
270 InvalidChar {
271 index: usize,
273 c: char,
275 },
276}
277
278#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
283#[dizzy(invariant = Name::str_is_name, error = InvalidNameError)]
284#[dizzy(constructor = pub new)]
285#[dizzy(getter = pub const as_str)]
286#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
287#[repr(transparent)]
288pub struct Name(str);
289
290impl Name {
291 fn str_is_name(s: &str) -> Result<(), InvalidNameError> {
292 if s.is_empty() {
293 return Err(InvalidNameError::EmptyString);
294 }
295 for (index, c) in s.char_indices() {
296 if !c.is_ascii_alphanumeric() && c != '-' {
297 return Err(InvalidNameError::InvalidChar { index, c });
298 }
299 }
300 Ok(())
301 }
302
303 pub fn len(&self) -> NonZero<usize> {
305 unsafe { NonZero::new_unchecked(self.0.len()) }
307 }
308
309 pub fn kind(&self) -> NameKind {
311 NameKind::of(self.as_str()).expect("Name is non-empty")
312 }
313}
314
315impl std::fmt::Display for Name {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 f.write_str(self.as_str())
318 }
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
323pub enum NameKind {
324 Iana,
326 X,
328}
329
330impl NameKind {
331 pub fn of(s: &str) -> Option<Self> {
333 match s.as_bytes().first_chunk::<2>() {
334 Some(b"x-" | b"X-") => Some(Self::X),
335 _ if !s.is_empty() => Some(Self::Iana),
336 _ => None,
337 }
338 }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344pub struct InvalidCharError {
345 pub byte_index: usize,
347 pub invalid_char: char,
349}
350
351impl InvalidCharError {
352 pub const fn from_char_index((byte_index, invalid_char): (usize, char)) -> Self {
355 Self {
356 byte_index,
357 invalid_char,
358 }
359 }
360}