use core::fmt;
use core::hash::{Hash, Hasher};
use core::str::FromStr;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::DidError;
pub const DEFAULT_MAX_LEN: usize = 2048;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Did(String);
impl Did {
pub fn parse(s: &str) -> Result<Self, DidError> {
Self::parse_with_max_len(s, DEFAULT_MAX_LEN)
}
pub fn parse_with_max_len(s: &str, max_len: usize) -> Result<Self, DidError> {
validate_did(s, max_len)?;
Ok(Self(s.to_owned()))
}
pub fn method(&self) -> &str {
&self.0[SCHEME.len()..self.method_end()]
}
pub fn method_specific_id(&self) -> &str {
&self.0[self.method_end() + 1..]
}
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn to_url(&self) -> DidUrl {
DidUrl {
url: self.0.clone(),
did: self.clone(),
query_start: None,
fragment_start: None,
}
}
pub fn join(&self, relative: &str) -> Result<DidUrl, DidError> {
if !relative.is_empty() && !relative.starts_with(['/', '?', '#']) {
return Err(DidError::InvalidPath);
}
let mut url = String::with_capacity(self.0.len() + relative.len());
url.push_str(&self.0);
url.push_str(relative);
check_len(&url, DEFAULT_MAX_LEN)?;
let (query_start, fragment_start) = scan_url_suffix(&url, self.0.len())?;
Ok(DidUrl {
url,
did: self.clone(),
query_start,
fragment_start,
})
}
fn method_end(&self) -> usize {
self.0[SCHEME.len()..]
.find(':')
.map_or(self.0.len(), |i| SCHEME.len() + i)
}
}
impl fmt::Display for Did {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for Did {
fn as_ref(&self) -> &str {
&self.0
}
}
impl FromStr for Did {
type Err = DidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl TryFrom<&str> for Did {
type Error = DidError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::parse(s)
}
}
impl TryFrom<String> for Did {
type Error = DidError;
fn try_from(s: String) -> Result<Self, Self::Error> {
validate_did(&s, DEFAULT_MAX_LEN)?;
Ok(Self(s))
}
}
impl Serialize for Did {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for Did {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Self::try_from(s).map_err(D::Error::custom)
}
}
#[derive(Clone)]
pub struct DidUrl {
url: String,
did: Did,
query_start: Option<usize>,
fragment_start: Option<usize>,
}
impl DidUrl {
pub fn parse(s: &str) -> Result<Self, DidError> {
Self::parse_with_max_len(s, DEFAULT_MAX_LEN)
}
pub fn parse_with_max_len(s: &str, max_len: usize) -> Result<Self, DidError> {
Self::from_string(s.to_owned(), max_len)
}
pub fn did(&self) -> &Did {
&self.did
}
pub fn path(&self) -> Option<&str> {
let end = self
.query_start
.or(self.fragment_start)
.unwrap_or(self.url.len());
let path = &self.url[self.did.0.len()..end];
(!path.is_empty()).then_some(path)
}
pub fn query(&self) -> Option<&str> {
self.query_start
.map(|start| &self.url[start + 1..self.fragment_start.unwrap_or(self.url.len())])
}
pub fn fragment(&self) -> Option<&str> {
self.fragment_start.map(|start| &self.url[start + 1..])
}
pub fn as_str(&self) -> &str {
&self.url
}
#[must_use]
pub fn with_fragment(&self, fragment: &str) -> Self {
let base = self.fragment_start.unwrap_or(self.url.len());
let mut url = String::with_capacity(base + 1 + fragment.len());
url.push_str(&self.url[..base]);
url.push('#');
encode_fragment_into(&mut url, fragment);
Self {
url,
did: self.did.clone(),
query_start: self.query_start,
fragment_start: Some(base),
}
}
#[must_use]
pub fn with_did(&self, did: Did) -> Self {
let old_len = self.did.0.len();
let suffix = &self.url[old_len..];
let mut url = String::with_capacity(did.0.len() + suffix.len());
url.push_str(&did.0);
url.push_str(suffix);
let shift = |offset: usize| offset - old_len + did.0.len();
Self {
url,
query_start: self.query_start.map(shift),
fragment_start: self.fragment_start.map(shift),
did,
}
}
#[must_use]
pub fn without_fragment(&self) -> Self {
let end = self.fragment_start.unwrap_or(self.url.len());
Self {
url: self.url[..end].to_owned(),
did: self.did.clone(),
query_start: self.query_start,
fragment_start: None,
}
}
fn from_string(url: String, max_len: usize) -> Result<Self, DidError> {
check_len(&url, max_len)?;
let did_end = scan_did(&url)?;
let (query_start, fragment_start) = scan_url_suffix(&url, did_end)?;
let did = Did(url[..did_end].to_owned());
Ok(Self {
url,
did,
query_start,
fragment_start,
})
}
}
impl fmt::Debug for DidUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("DidUrl").field(&self.url).finish()
}
}
impl fmt::Display for DidUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.url)
}
}
impl PartialEq for DidUrl {
fn eq(&self, other: &Self) -> bool {
self.url == other.url
}
}
impl Eq for DidUrl {}
impl PartialOrd for DidUrl {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DidUrl {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.url.cmp(&other.url)
}
}
impl Hash for DidUrl {
fn hash<H: Hasher>(&self, state: &mut H) {
self.url.hash(state);
}
}
impl AsRef<str> for DidUrl {
fn as_ref(&self) -> &str {
&self.url
}
}
impl FromStr for DidUrl {
type Err = DidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl TryFrom<&str> for DidUrl {
type Error = DidError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::parse(s)
}
}
impl TryFrom<String> for DidUrl {
type Error = DidError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::from_string(s, DEFAULT_MAX_LEN)
}
}
impl Serialize for DidUrl {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.url)
}
}
impl<'de> Deserialize<'de> for DidUrl {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Self::try_from(s).map_err(D::Error::custom)
}
}
const SCHEME: &str = "did:";
fn validate_did(s: &str, max_len: usize) -> Result<(), DidError> {
check_len(s, max_len)?;
let end = scan_did(s)?;
if end < s.len() {
return Err(invalid_char(s, end));
}
Ok(())
}
pub(super) fn check_len(s: &str, max_len: usize) -> Result<(), DidError> {
if s.len() > max_len {
return Err(DidError::TooLong { max: max_len });
}
Ok(())
}
fn scan_did(s: &str) -> Result<usize, DidError> {
let bytes = s.as_bytes();
if !s.starts_with(SCHEME) {
return Err(DidError::MissingScheme);
}
let mut i = SCHEME.len();
while i < bytes.len() && is_method_char(bytes[i]) {
i += 1;
}
if i == SCHEME.len() {
return Err(DidError::InvalidMethodName { position: i });
}
match bytes.get(i) {
Some(b':') => i += 1,
None | Some(b'/' | b'?' | b'#') => return Err(DidError::EmptyMethodSpecificId),
Some(_) => return Err(DidError::InvalidMethodName { position: i }),
}
let mut segment_len = 0;
while i < bytes.len() {
match bytes[i] {
b':' => {
segment_len = 0;
i += 1;
}
b'%' => {
check_pct_encoded(bytes, i)?;
segment_len += 3;
i += 3;
}
c if is_idchar(c) => {
segment_len += 1;
i += 1;
}
_ => break,
}
}
if segment_len == 0 {
return match bytes.get(i) {
None | Some(b'/' | b'?' | b'#') => Err(DidError::EmptyMethodSpecificId),
Some(_) => Err(invalid_char(s, i)),
};
}
Ok(i)
}
pub(super) fn scan_url_suffix(
s: &str,
start: usize,
) -> Result<(Option<usize>, Option<usize>), DidError> {
let bytes = s.as_bytes();
let mut i = start;
if bytes.get(i) == Some(&b'/') {
while i < bytes.len() {
match bytes[i] {
b'?' | b'#' => break,
b'%' => {
check_pct_encoded(bytes, i)?;
i += 3;
}
c if is_pchar(c) || c == b'/' => i += 1,
_ => return Err(DidError::InvalidPath),
}
}
}
let mut query_start = None;
if bytes.get(i) == Some(&b'?') {
query_start = Some(i);
i += 1;
while i < bytes.len() {
match bytes[i] {
b'#' => break,
b'%' => {
check_pct_encoded(bytes, i)?;
i += 3;
}
c if is_fragment_or_query_char(c) => i += 1,
_ => return Err(DidError::InvalidQuery),
}
}
}
let mut fragment_start = None;
if bytes.get(i) == Some(&b'#') {
fragment_start = Some(i);
i += 1;
while i < bytes.len() {
match bytes[i] {
b'%' => {
check_pct_encoded(bytes, i)?;
i += 3;
}
c if is_fragment_or_query_char(c) => i += 1,
_ => return Err(DidError::InvalidFragment),
}
}
}
if i < bytes.len() {
return Err(invalid_char(s, i));
}
Ok((query_start, fragment_start))
}
fn check_pct_encoded(bytes: &[u8], position: usize) -> Result<(), DidError> {
if is_pct_encoded_at(bytes, position) {
Ok(())
} else {
Err(DidError::InvalidPercentEncoding { position })
}
}
pub(super) fn is_pct_encoded_at(bytes: &[u8], position: usize) -> bool {
matches!(
bytes.get(position..position + 3),
Some([b'%', hi, lo]) if hi.is_ascii_hexdigit() && lo.is_ascii_hexdigit()
)
}
fn encode_fragment_into(out: &mut String, fragment: &str) {
let bytes = fragment.as_bytes();
let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if c == b'%' && is_pct_encoded_at(bytes, i) {
out.push_str(&fragment[i..i + 3]);
i += 3;
} else {
if is_fragment_or_query_char(c) {
out.push(char::from(c));
} else {
out.push('%');
out.push(char::from(HEX_UPPER[usize::from(c >> 4)]));
out.push(char::from(HEX_UPPER[usize::from(c & 0x0f)]));
}
i += 1;
}
}
}
const HEX_UPPER: &[u8; 16] = b"0123456789ABCDEF";
fn invalid_char(s: &str, position: usize) -> DidError {
let ch = s
.get(position..)
.and_then(|rest| rest.chars().next())
.unwrap_or(char::REPLACEMENT_CHARACTER);
DidError::InvalidChar { position, ch }
}
pub(super) fn is_method_char(c: u8) -> bool {
c.is_ascii_lowercase() || c.is_ascii_digit()
}
pub(super) fn is_idchar(c: u8) -> bool {
c.is_ascii_alphanumeric() || matches!(c, b'.' | b'-' | b'_')
}
fn is_pchar(c: u8) -> bool {
c.is_ascii_alphanumeric()
|| matches!(
c,
b'-' | b'.'
| b'_'
| b'~'
| b'!'
| b'$'
| b'&'
| b'\''
| b'('
| b')'
| b'*'
| b'+'
| b','
| b';'
| b'='
| b':'
| b'@'
)
}
fn is_fragment_or_query_char(c: u8) -> bool {
is_pchar(c) || c == b'/' || c == b'?'
}