use core::fmt;
use core::str::FromStr;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::DidError;
use super::syntax::{
DEFAULT_MAX_LEN, Did, check_len, is_idchar, is_method_char, is_pct_encoded_at,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DidMethod {
name: String,
namespace: Option<String>,
}
impl DidMethod {
pub fn new(name: impl Into<String>) -> Result<Self, DidError> {
let name = name.into();
validate_name(&name, 0)?;
Ok(Self {
name,
namespace: None,
})
}
pub fn with_namespace(self, namespace: impl Into<String>) -> Result<Self, DidError> {
let namespace = namespace.into();
validate_namespace(&namespace, 0)?;
Ok(Self {
name: self.name,
namespace: Some(namespace),
})
}
pub fn parse(s: &str) -> Result<Self, DidError> {
check_len(s, DEFAULT_MAX_LEN)?;
let Some(rest) = s.strip_prefix(SCHEME) else {
return Err(DidError::MissingScheme);
};
let (name, namespace) = match rest.split_once(':') {
Some((name, namespace)) => (name, Some(namespace)),
None => (rest, None),
};
validate_name(name, SCHEME.len())?;
if let Some(namespace) = namespace {
validate_namespace(namespace, SCHEME.len() + name.len() + 1)?;
}
Ok(Self {
name: name.to_owned(),
namespace: namespace.map(str::to_owned),
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn namespace(&self) -> Option<&str> {
self.namespace.as_deref()
}
pub fn matches(&self, did: &Did) -> bool {
if did.method() != self.name {
return false;
}
match &self.namespace {
None => true,
Some(namespace) => {
let id = did.method_specific_id();
let first = id.split_once(':').map_or(id, |(first, _)| first);
first == namespace
}
}
}
}
impl From<&Did> for DidMethod {
fn from(did: &Did) -> Self {
Self {
name: did.method().to_owned(),
namespace: None,
}
}
}
impl fmt::Display for DidMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(SCHEME)?;
f.write_str(&self.name)?;
if let Some(namespace) = &self.namespace {
f.write_str(":")?;
f.write_str(namespace)?;
}
Ok(())
}
}
impl FromStr for DidMethod {
type Err = DidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl TryFrom<&str> for DidMethod {
type Error = DidError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::parse(s)
}
}
impl TryFrom<String> for DidMethod {
type Error = DidError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::parse(&s)
}
}
impl Serialize for DidMethod {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for DidMethod {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Self::parse(&s).map_err(D::Error::custom)
}
}
const SCHEME: &str = "did:";
fn validate_name(name: &str, offset: usize) -> Result<(), DidError> {
if name.is_empty() {
return Err(DidError::InvalidMethodName { position: offset });
}
match name.bytes().position(|c| !is_method_char(c)) {
None => Ok(()),
Some(i) => Err(DidError::InvalidMethodName {
position: offset + i,
}),
}
}
fn validate_namespace(namespace: &str, offset: usize) -> Result<(), DidError> {
let bytes = namespace.as_bytes();
if bytes.is_empty() {
return Err(DidError::InvalidNamespace { position: offset });
}
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'%' if is_pct_encoded_at(bytes, i) => i += 3,
b'%' => {
return Err(DidError::InvalidPercentEncoding {
position: offset + i,
});
}
c if is_idchar(c) => i += 1,
_ => {
return Err(DidError::InvalidNamespace {
position: offset + i,
});
}
}
}
Ok(())
}