1use core::fmt;
4use core::str::FromStr;
5
6use serde::de::Error as _;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use super::{Did, DidError, DidUrl};
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct Uri(String);
36
37impl Uri {
38 pub fn parse(s: &str) -> Result<Self, DidError> {
45 validate_uri(s)?;
46 Ok(Self(s.to_owned()))
47 }
48
49 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53
54 pub fn scheme(&self) -> &str {
56 self.0.split(':').next().unwrap_or_default()
58 }
59
60 pub fn is_did_url(&self) -> bool {
62 self.scheme() == "did"
63 }
64}
65
66fn validate_uri(s: &str) -> Result<(), DidError> {
69 let bytes = s.as_bytes();
70 let colon = bytes
71 .iter()
72 .position(|&b| b == b':')
73 .ok_or(DidError::InvalidChar {
74 position: bytes.len(),
75 ch: ':',
76 })?;
77 if colon == 0 || !bytes[0].is_ascii_alphabetic() {
78 return Err(DidError::InvalidChar {
79 position: 0,
80 ch: s.chars().next().unwrap_or(':'),
81 });
82 }
83 if let Some(i) = bytes[..colon]
84 .iter()
85 .position(|&b| !(b.is_ascii_alphanumeric() || matches!(b, b'+' | b'-' | b'.')))
86 {
87 return Err(DidError::InvalidChar {
88 position: i,
89 ch: char::from(bytes[i]),
90 });
91 }
92 let mut hashes = 0;
93 let mut i = colon + 1;
94 while i < bytes.len() {
95 match bytes[i] {
96 b'%' => {
97 let ok = bytes.len() >= i + 3
98 && bytes[i + 1].is_ascii_hexdigit()
99 && bytes[i + 2].is_ascii_hexdigit();
100 if !ok {
101 return Err(DidError::InvalidPercentEncoding { position: i });
102 }
103 i += 3;
104 continue;
105 }
106 b'#' => {
107 hashes += 1;
108 if hashes > 1 {
109 return Err(DidError::InvalidChar {
110 position: i,
111 ch: '#',
112 });
113 }
114 }
115 b if is_uri_char(b) => {}
116 _ => {
117 let ch = s[i..].chars().next().unwrap_or('\0');
119 return Err(DidError::InvalidChar { position: i, ch });
120 }
121 }
122 i += 1;
123 }
124 Ok(())
125}
126
127fn is_uri_char(b: u8) -> bool {
129 b.is_ascii_alphanumeric()
130 || matches!(
131 b,
132 b'-' | b'.'
133 | b'_'
134 | b'~'
135 | b':'
136 | b'/'
137 | b'?'
138 | b'['
139 | b']'
140 | b'@'
141 | b'!'
142 | b'$'
143 | b'&'
144 | b'\''
145 | b'('
146 | b')'
147 | b'*'
148 | b'+'
149 | b','
150 | b';'
151 | b'='
152 )
153}
154
155impl fmt::Display for Uri {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.write_str(&self.0)
158 }
159}
160
161impl AsRef<str> for Uri {
162 fn as_ref(&self) -> &str {
163 &self.0
164 }
165}
166
167impl FromStr for Uri {
168 type Err = DidError;
169
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 Self::parse(s)
172 }
173}
174
175impl TryFrom<&str> for Uri {
176 type Error = DidError;
177
178 fn try_from(s: &str) -> Result<Self, Self::Error> {
179 Self::parse(s)
180 }
181}
182
183impl TryFrom<String> for Uri {
184 type Error = DidError;
185
186 fn try_from(s: String) -> Result<Self, Self::Error> {
187 validate_uri(&s)?;
188 Ok(Self(s))
189 }
190}
191
192impl From<Did> for Uri {
193 fn from(did: Did) -> Self {
194 Self(did.as_str().to_owned())
195 }
196}
197
198impl From<DidUrl> for Uri {
199 fn from(url: DidUrl) -> Self {
200 Self(url.as_str().to_owned())
201 }
202}
203
204impl From<&Did> for Uri {
205 fn from(did: &Did) -> Self {
206 Self(did.as_str().to_owned())
207 }
208}
209
210impl From<&DidUrl> for Uri {
211 fn from(url: &DidUrl) -> Self {
212 Self(url.as_str().to_owned())
213 }
214}
215
216impl PartialEq<str> for Uri {
217 fn eq(&self, other: &str) -> bool {
218 self.0 == other
219 }
220}
221
222impl PartialEq<&str> for Uri {
223 fn eq(&self, other: &&str) -> bool {
224 self.0 == *other
225 }
226}
227
228impl Serialize for Uri {
229 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
230 serializer.serialize_str(&self.0)
231 }
232}
233
234impl<'de> Deserialize<'de> for Uri {
235 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
236 let s = String::deserialize(deserializer)?;
237 Self::try_from(s).map_err(D::Error::custom)
238 }
239}