1use core::fmt;
2use std::ops::Deref;
3
4use serde::{Deserialize, Serialize};
5
6use crate::{DIDURLBuf, DID};
7
8use super::{Fragment, Query, Unexpected};
9
10#[derive(Debug, thiserror::Error)]
12#[error("invalid relative DID URL `{0}`: {1}")]
13pub struct InvalidRelativeDIDURL<T>(pub T, pub Unexpected);
14
15impl<T> InvalidRelativeDIDURL<T> {
16 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> InvalidRelativeDIDURL<U> {
17 InvalidRelativeDIDURL(f(self.0), self.1)
18 }
19}
20
21#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[repr(transparent)]
24pub struct RelativeDIDURL([u8]);
25
26impl RelativeDIDURL {
27 pub fn new(data: &[u8]) -> Result<&Self, InvalidRelativeDIDURL<&[u8]>> {
32 match Self::validate(data) {
33 Ok(()) => Ok(unsafe {
34 std::mem::transmute::<&[u8], &Self>(data)
38 }),
39 Err(e) => Err(InvalidRelativeDIDURL(data, e)),
40 }
41 }
42
43 pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
50 std::mem::transmute::<&[u8], &Self>(data)
53 }
54
55 pub fn as_str(&self) -> &str {
57 unsafe {
58 std::str::from_utf8_unchecked(&self.0)
60 }
61 }
62
63 pub fn as_bytes(&self) -> &[u8] {
65 &self.0
66 }
67
68 fn query_delimiter_offset(&self) -> usize {
69 self.0
70 .iter()
71 .position(|&b| matches!(b, b'?' | b'#'))
72 .unwrap_or(self.0.len())
73 }
74
75 fn fragment_delimiter_offset(&self) -> usize {
76 self.0
77 .iter()
78 .position(|&b| matches!(b, b'#'))
79 .unwrap_or(self.0.len())
80 }
81
82 fn fragment_delimiter_offset_from(&self, offset: usize) -> usize {
83 self.0[offset..]
84 .iter()
85 .position(|&b| matches!(b, b'#'))
86 .map(|o| o + offset)
87 .unwrap_or(self.0.len())
88 }
89
90 pub fn path(&self) -> &RelativePath {
91 let end = self.query_delimiter_offset();
92 unsafe { RelativePath::new_unchecked(&self.0[..end]) }
93 }
94
95 pub fn query(&self) -> Option<&Query> {
96 let start = self.query_delimiter_offset();
97 let end = self.fragment_delimiter_offset_from(start);
98 if start == end {
99 None
100 } else {
101 Some(unsafe { Query::new_unchecked(&self.0[(start + 1)..end]) })
102 }
103 }
104
105 pub fn fragment(&self) -> Option<&Fragment> {
106 let start = self.fragment_delimiter_offset();
107 let end = self.fragment_delimiter_offset_from(start);
108 if start == end {
109 None
110 } else {
111 Some(unsafe { Fragment::new_unchecked(&self.0[(start + 1)..end]) })
112 }
113 }
114
115 pub fn resolve(&self, base_id: &DID) -> DIDURLBuf {
130 let mut bytes = base_id.as_bytes().to_vec();
131 bytes.extend_from_slice(&self.0);
132 unsafe { DIDURLBuf::new_unchecked(bytes) }
133 }
134}
135
136#[repr(transparent)]
138pub struct RelativePath([u8]);
139
140impl RelativePath {
141 pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
147 std::mem::transmute(data)
148 }
149
150 pub fn as_str(&self) -> &str {
152 unsafe {
153 std::str::from_utf8_unchecked(&self.0)
155 }
156 }
157
158 pub fn as_bytes(&self) -> &[u8] {
160 &self.0
161 }
162}
163
164#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
165pub struct RelativeDIDURLBuf(Vec<u8>);
166
167impl RelativeDIDURLBuf {
168 pub fn new(data: Vec<u8>) -> Result<Self, InvalidRelativeDIDURL<Vec<u8>>> {
169 match RelativeDIDURL::validate(&data) {
170 Ok(()) => Ok(Self(data)),
171 Err(e) => Err(InvalidRelativeDIDURL(data, e)),
172 }
173 }
174
175 pub fn as_relative_did_url(&self) -> &RelativeDIDURL {
176 unsafe { RelativeDIDURL::new_unchecked(&self.0) }
177 }
178}
179
180impl TryFrom<String> for RelativeDIDURLBuf {
181 type Error = InvalidRelativeDIDURL<String>;
182
183 fn try_from(value: String) -> Result<Self, Self::Error> {
184 RelativeDIDURLBuf::new(value.into_bytes()).map_err(|e| {
185 e.map(|bytes| unsafe {
186 String::from_utf8_unchecked(bytes)
189 })
190 })
191 }
192}
193
194impl Deref for RelativeDIDURLBuf {
195 type Target = RelativeDIDURL;
196
197 fn deref(&self) -> &Self::Target {
198 self.as_relative_did_url()
199 }
200}
201
202impl fmt::Display for RelativeDIDURLBuf {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 self.as_str().fmt(f)
205 }
206}
207
208impl fmt::Debug for RelativeDIDURLBuf {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 self.as_str().fmt(f)
211 }
212}
213
214impl Serialize for RelativeDIDURLBuf {
215 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
216 where
217 S: serde::Serializer,
218 {
219 self.as_str().serialize(serializer)
220 }
221}
222
223impl<'de> Deserialize<'de> for RelativeDIDURLBuf {
224 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
225 where
226 D: serde::Deserializer<'de>,
227 {
228 struct Visitor;
229
230 impl serde::de::Visitor<'_> for Visitor {
231 type Value = RelativeDIDURLBuf;
232
233 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
234 write!(f, "a DID URL")
235 }
236
237 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
238 where
239 E: serde::de::Error,
240 {
241 v.try_into().map_err(|e| E::custom(e))
242 }
243
244 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
245 where
246 E: serde::de::Error,
247 {
248 self.visit_string(v.to_string())
249 }
250 }
251
252 deserializer.deserialize_string(Visitor)
253 }
254}
255
256impl RelativeDIDURL {
257 fn validate(data: &[u8]) -> Result<(), Unexpected> {
259 let mut bytes = data.iter().copied();
260 match Self::validate_from(0, &mut bytes)? {
261 (_, None) => Ok(()),
262 (i, Some(c)) => Err(Unexpected(i, Some(c))),
263 }
264 }
265
266 fn validate_from(
268 mut i: usize,
269 bytes: &mut impl Iterator<Item = u8>,
270 ) -> Result<(usize, Option<u8>), Unexpected> {
271 enum State {
272 Path,
273 PathSegment,
274 PathSegmentNc,
275 PathSegmentNzNc,
276 Query,
277 Fragment,
278 Pct1(Part),
279 Pct2(Part),
280 }
281
282 enum Part {
283 PathSegment,
284 PathSegmentNc,
285 Query,
286 Fragment,
287 }
288
289 impl Part {
290 pub fn state(&self) -> State {
291 match self {
292 Self::PathSegment => State::PathSegment,
293 Self::PathSegmentNc => State::PathSegmentNc,
294 Self::Query => State::Query,
295 Self::Fragment => State::Fragment,
296 }
297 }
298 }
299
300 fn is_unreserved(b: u8) -> bool {
301 b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~')
302 }
303
304 fn is_sub_delims(b: u8) -> bool {
305 matches!(
306 b,
307 b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
308 )
309 }
310
311 fn is_pchar(b: u8) -> bool {
312 is_unreserved(b) || is_sub_delims(b) || matches!(b, b':' | b'@')
313 }
314
315 let mut state = State::Path;
316
317 loop {
318 match state {
319 State::Path => match bytes.next() {
320 Some(b'/') => state = State::PathSegmentNzNc, Some(b'?') => state = State::Query, Some(b'#') => state = State::Fragment, Some(b'%') => state = State::Pct1(Part::PathSegmentNc), Some(b':') => break Ok((i, Some(b':'))),
325 Some(c) if is_pchar(c) => (), c => break Ok((i, c)), },
328 State::PathSegment => match bytes.next() {
329 Some(b'/') => (), Some(b'?') => state = State::Query,
331 Some(b'#') => state = State::Fragment,
332 Some(b'%') => state = State::Pct1(Part::PathSegment),
333 Some(c) if is_pchar(c) => (),
334 c => break Ok((i, c)),
335 },
336 State::PathSegmentNc => match bytes.next() {
337 Some(b'/') => state = State::PathSegment,
338 Some(b'?') => state = State::Query,
339 Some(b'#') => state = State::Fragment,
340 Some(b'%') => state = State::Pct1(Part::PathSegmentNc),
341 Some(b':') => break Ok((i, Some(b':'))),
342 Some(c) if is_pchar(c) => (),
343 c => break Ok((i, c)),
344 },
345 State::PathSegmentNzNc => match bytes.next() {
346 Some(b'?') => state = State::Query,
347 Some(b'#') => state = State::Fragment,
348 Some(b'%') => state = State::Pct1(Part::PathSegmentNc),
349 Some(b':') => break Ok((i, Some(b':'))),
350 Some(c) if is_pchar(c) => state = State::PathSegmentNc,
351 c => break Ok((i, c)),
352 },
353 State::Query => match bytes.next() {
354 Some(b'#') => state = State::Fragment,
355 Some(b'%') => state = State::Pct1(Part::Query),
356 Some(c) if is_pchar(c) || matches!(c, b'/' | b'?') => (),
357 c => break Ok((i, c)),
358 },
359 State::Fragment => match bytes.next() {
360 Some(b'%') => state = State::Pct1(Part::Fragment),
361 Some(c) if is_pchar(c) || matches!(c, b'/' | b'?' | b'#') => (),
362 c => break Ok((i, c)),
363 },
364 State::Pct1(q) => match bytes.next() {
365 Some(c) if c.is_ascii_hexdigit() => state = State::Pct2(q),
366 c => break Err(Unexpected(i, c)),
367 },
368 State::Pct2(q) => match bytes.next() {
369 Some(c) if c.is_ascii_hexdigit() => state = q.state(),
370 c => break Err(Unexpected(i, c)),
371 },
372 }
373
374 i += 1
375 }
376 }
377}