1use std::str::FromStr;
4
5use dizzy::DstNewtype;
6use thiserror::Error;
7
8pub use language_tags::ParseError as LanguageTagParseError;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct LanguageTag(language_tags::LanguageTag);
14
15impl LanguageTag {
16 pub fn parse(s: &str) -> Result<Self, language_tags::ParseError> {
18 language_tags::LanguageTag::parse(s).map(LanguageTag)
19 }
20
21 #[inline]
23 pub fn as_str(&self) -> &str {
24 self.0.as_str()
25 }
26
27 #[inline]
29 pub fn primary_language(&self) -> &str {
30 self.0.primary_language()
31 }
32}
33
34impl FromStr for LanguageTag {
35 type Err = language_tags::ParseError;
36
37 fn from_str(s: &str) -> Result<Self, Self::Err> {
38 Self::parse(s)
39 }
40}
41
42impl std::fmt::Display for LanguageTag {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 std::fmt::Display::fmt(&self.0, f)
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
50pub enum InvalidUidError {
51 #[error("expected at least one character")]
52 EmptyString,
53}
54
55#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
61#[dizzy(invariant = Uid::str_is_uid, error = InvalidUidError)]
62#[dizzy(constructor = pub new)]
63#[dizzy(getter = pub const as_str)]
64#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
65#[dizzy(owned = pub UidBuf(String))]
66#[dizzy(derive_owned(Debug, IntoBoxed))]
67#[repr(transparent)]
68pub struct Uid(str);
69
70impl std::fmt::Display for Uid {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.write_str(self.as_str())
73 }
74}
75
76impl Uid {
77 fn str_is_uid(s: &str) -> Result<(), InvalidUidError> {
78 if s.is_empty() {
79 return Err(InvalidUidError::EmptyString);
80 }
81 Ok(())
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
87pub enum InvalidUriError {
88 #[error("expected at least one character")]
89 EmptyString,
90 #[error("missing colon after scheme")]
91 MissingColon,
92 #[error("scheme must start with a letter")]
93 SchemeStartsWithNonLetter,
94 #[error("invalid character in scheme: {c}")]
95 InvalidSchemeChar { index: usize, c: char },
96}
97
98#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, DstNewtype)]
105#[dizzy(invariant = Uri::str_is_uri, error = InvalidUriError)]
106#[dizzy(constructor = pub new)]
107#[dizzy(getter = pub const as_str)]
108#[dizzy(derive(Debug, CloneBoxed, IntoBoxed))]
109#[dizzy(owned = pub UriBuf(String))]
110#[dizzy(derive_owned(Debug, IntoBoxed))]
111#[repr(transparent)]
112pub struct Uri(str);
113
114impl std::fmt::Display for Uri {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.write_str(self.as_str())
117 }
118}
119
120impl Uri {
121 fn str_is_uri(s: &str) -> Result<(), InvalidUriError> {
122 let (scheme, _rest) = s.split_once(':').ok_or(if s.is_empty() {
123 InvalidUriError::EmptyString
124 } else {
125 InvalidUriError::MissingColon
126 })?;
127
128 let mut chars = scheme.chars().enumerate();
129
130 match chars.next() {
131 None => return Err(InvalidUriError::MissingColon),
132 Some((_, c)) if !c.is_ascii_alphabetic() => {
133 return Err(InvalidUriError::SchemeStartsWithNonLetter);
134 }
135 Some(_) => {}
136 }
137
138 for (index, c) in chars {
139 if !c.is_ascii_alphanumeric() && c != '+' && c != '-' && c != '.' {
140 return Err(InvalidUriError::InvalidSchemeChar { index, c });
141 }
142 }
143
144 Ok(())
145 }
146
147 #[inline(always)]
149 pub fn scheme(&self) -> &str {
150 self.as_str()
151 .split_once(':')
152 .expect("a Uri must contain a colon")
153 .0
154 }
155}