use core::fmt;
use std::ops::Deref;
use serde::{Deserialize, Serialize};
use crate::{DIDURLBuf, DID};
use super::{Fragment, Query, Unexpected};
#[derive(Debug, thiserror::Error)]
#[error("invalid relative DID URL `{0}`: {1}")]
pub struct InvalidRelativeDIDURL<T>(pub T, pub Unexpected);
impl<T> InvalidRelativeDIDURL<T> {
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> InvalidRelativeDIDURL<U> {
InvalidRelativeDIDURL(f(self.0), self.1)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct RelativeDIDURL([u8]);
impl RelativeDIDURL {
pub fn new(data: &[u8]) -> Result<&Self, InvalidRelativeDIDURL<&[u8]>> {
match Self::validate(data) {
Ok(()) => Ok(unsafe {
std::mem::transmute::<&[u8], &Self>(data)
}),
Err(e) => Err(InvalidRelativeDIDURL(data, e)),
}
}
pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
std::mem::transmute::<&[u8], &Self>(data)
}
pub fn as_str(&self) -> &str {
unsafe {
std::str::from_utf8_unchecked(&self.0)
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
fn query_delimiter_offset(&self) -> usize {
self.0
.iter()
.position(|&b| matches!(b, b'?' | b'#'))
.unwrap_or(self.0.len())
}
fn fragment_delimiter_offset(&self) -> usize {
self.0
.iter()
.position(|&b| matches!(b, b'#'))
.unwrap_or(self.0.len())
}
fn fragment_delimiter_offset_from(&self, offset: usize) -> usize {
self.0[offset..]
.iter()
.position(|&b| matches!(b, b'#'))
.map(|o| o + offset)
.unwrap_or(self.0.len())
}
pub fn path(&self) -> &RelativePath {
let end = self.query_delimiter_offset();
unsafe { RelativePath::new_unchecked(&self.0[..end]) }
}
pub fn query(&self) -> Option<&Query> {
let start = self.query_delimiter_offset();
let end = self.fragment_delimiter_offset_from(start);
if start == end {
None
} else {
Some(unsafe { Query::new_unchecked(&self.0[(start + 1)..end]) })
}
}
pub fn fragment(&self) -> Option<&Fragment> {
let start = self.fragment_delimiter_offset();
let end = self.fragment_delimiter_offset_from(start);
if start == end {
None
} else {
Some(unsafe { Fragment::new_unchecked(&self.0[(start + 1)..end]) })
}
}
pub fn resolve(&self, base_id: &DID) -> DIDURLBuf {
let mut bytes = base_id.as_bytes().to_vec();
bytes.extend_from_slice(&self.0);
unsafe { DIDURLBuf::new_unchecked(bytes) }
}
}
#[repr(transparent)]
pub struct RelativePath([u8]);
impl RelativePath {
pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
std::mem::transmute(data)
}
pub fn as_str(&self) -> &str {
unsafe {
std::str::from_utf8_unchecked(&self.0)
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RelativeDIDURLBuf(Vec<u8>);
impl RelativeDIDURLBuf {
pub fn new(data: Vec<u8>) -> Result<Self, InvalidRelativeDIDURL<Vec<u8>>> {
match RelativeDIDURL::validate(&data) {
Ok(()) => Ok(Self(data)),
Err(e) => Err(InvalidRelativeDIDURL(data, e)),
}
}
pub fn as_relative_did_url(&self) -> &RelativeDIDURL {
unsafe { RelativeDIDURL::new_unchecked(&self.0) }
}
}
impl TryFrom<String> for RelativeDIDURLBuf {
type Error = InvalidRelativeDIDURL<String>;
fn try_from(value: String) -> Result<Self, Self::Error> {
RelativeDIDURLBuf::new(value.into_bytes()).map_err(|e| {
e.map(|bytes| unsafe {
String::from_utf8_unchecked(bytes)
})
})
}
}
impl Deref for RelativeDIDURLBuf {
type Target = RelativeDIDURL;
fn deref(&self) -> &Self::Target {
self.as_relative_did_url()
}
}
impl fmt::Display for RelativeDIDURLBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl fmt::Debug for RelativeDIDURLBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl Serialize for RelativeDIDURLBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for RelativeDIDURLBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = RelativeDIDURLBuf;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a DID URL")
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.try_into().map_err(|e| E::custom(e))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_string(v.to_string())
}
}
deserializer.deserialize_string(Visitor)
}
}
impl RelativeDIDURL {
fn validate(data: &[u8]) -> Result<(), Unexpected> {
let mut bytes = data.iter().copied();
match Self::validate_from(0, &mut bytes)? {
(_, None) => Ok(()),
(i, Some(c)) => Err(Unexpected(i, Some(c))),
}
}
fn validate_from(
mut i: usize,
bytes: &mut impl Iterator<Item = u8>,
) -> Result<(usize, Option<u8>), Unexpected> {
enum State {
Path,
PathSegment,
PathSegmentNc,
PathSegmentNzNc,
Query,
Fragment,
Pct1(Part),
Pct2(Part),
}
enum Part {
PathSegment,
PathSegmentNc,
Query,
Fragment,
}
impl Part {
pub fn state(&self) -> State {
match self {
Self::PathSegment => State::PathSegment,
Self::PathSegmentNc => State::PathSegmentNc,
Self::Query => State::Query,
Self::Fragment => State::Fragment,
}
}
}
fn is_unreserved(b: u8) -> bool {
b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~')
}
fn is_sub_delims(b: u8) -> bool {
matches!(
b,
b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
)
}
fn is_pchar(b: u8) -> bool {
is_unreserved(b) || is_sub_delims(b) || matches!(b, b':' | b'@')
}
let mut state = State::Path;
loop {
match state {
State::Path => match bytes.next() {
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':'))),
Some(c) if is_pchar(c) => (), c => break Ok((i, c)), },
State::PathSegment => match bytes.next() {
Some(b'/') => (), Some(b'?') => state = State::Query,
Some(b'#') => state = State::Fragment,
Some(b'%') => state = State::Pct1(Part::PathSegment),
Some(c) if is_pchar(c) => (),
c => break Ok((i, c)),
},
State::PathSegmentNc => match bytes.next() {
Some(b'/') => state = State::PathSegment,
Some(b'?') => state = State::Query,
Some(b'#') => state = State::Fragment,
Some(b'%') => state = State::Pct1(Part::PathSegmentNc),
Some(b':') => break Ok((i, Some(b':'))),
Some(c) if is_pchar(c) => (),
c => break Ok((i, c)),
},
State::PathSegmentNzNc => match bytes.next() {
Some(b'?') => state = State::Query,
Some(b'#') => state = State::Fragment,
Some(b'%') => state = State::Pct1(Part::PathSegmentNc),
Some(b':') => break Ok((i, Some(b':'))),
Some(c) if is_pchar(c) => state = State::PathSegmentNc,
c => break Ok((i, c)),
},
State::Query => match bytes.next() {
Some(b'#') => state = State::Fragment,
Some(b'%') => state = State::Pct1(Part::Query),
Some(c) if is_pchar(c) || matches!(c, b'/' | b'?') => (),
c => break Ok((i, c)),
},
State::Fragment => match bytes.next() {
Some(b'%') => state = State::Pct1(Part::Fragment),
Some(c) if is_pchar(c) || matches!(c, b'/' | b'?' | b'#') => (),
c => break Ok((i, c)),
},
State::Pct1(q) => match bytes.next() {
Some(c) if c.is_ascii_hexdigit() => state = State::Pct2(q),
c => break Err(Unexpected(i, c)),
},
State::Pct2(q) => match bytes.next() {
Some(c) if c.is_ascii_hexdigit() => state = q.state(),
c => break Err(Unexpected(i, c)),
},
}
i += 1
}
}
}