use core::convert::TryFrom;
use core::fmt::Debug;
use core::fmt::Display;
use core::fmt::Formatter;
use core::str::FromStr;
use std::hash::Hash;
use did_url_parser::DID as BaseDIDUrl;
use identity_core::common::KeyComparable;
use crate::DIDUrl;
use crate::Error;
pub trait DID:
Clone
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ Hash
+ FromStr
+ TryFrom<CoreDID>
+ Into<String>
+ Into<CoreDID>
+ AsRef<CoreDID>
{
const SCHEME: &'static str = BaseDIDUrl::SCHEME;
fn scheme(&self) -> &'static str {
self.as_ref().0.scheme()
}
fn authority(&self) -> &str {
self.as_ref().0.authority()
}
fn method(&self) -> &str {
self.as_ref().0.method()
}
fn method_id(&self) -> &str {
self.as_ref().0.method_id()
}
fn as_str(&self) -> &str {
self.as_ref().0.as_str()
}
fn into_string(self) -> String {
self.into()
}
fn join(self, value: impl AsRef<str>) -> Result<DIDUrl, Error> {
let url = DIDUrl::from(self);
url.join(value)
}
fn to_url(&self) -> DIDUrl {
DIDUrl::new(self.clone().into(), None)
}
fn into_url(self) -> DIDUrl {
DIDUrl::from(self)
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
#[repr(transparent)]
#[serde(into = "BaseDIDUrl", try_from = "BaseDIDUrl")]
pub struct CoreDID(BaseDIDUrl);
impl CoreDID {
pub fn parse(input: impl AsRef<str>) -> Result<Self, Error> {
BaseDIDUrl::parse(input).map(Self).map_err(Error::from)
}
pub fn set_method_name(&mut self, value: impl AsRef<str>) -> Result<(), Error> {
Self::valid_method_name(value.as_ref())?;
self.0.set_method(value);
Ok(())
}
pub fn valid_method_name(value: &str) -> Result<(), Error> {
if !value.chars().all(is_char_method_name) {
return Err(Error::InvalidMethodName);
}
Ok(())
}
pub fn set_method_id(&mut self, value: impl AsRef<str>) -> Result<(), Error> {
Self::valid_method_id(value.as_ref())?;
self.0.set_method_id(value);
Ok(())
}
pub fn valid_method_id(value: &str) -> Result<(), Error> {
let mut chars = value.chars();
while let Some(c) = chars.next() {
match c {
'%' => {
let digits = chars.clone().take(2).collect::<String>();
u8::from_str_radix(&digits, 16).map_err(|_| Error::InvalidMethodId)?;
chars.next();
chars.next();
}
c if is_char_method_id(c) => (),
_ => return Err(Error::InvalidMethodId),
}
}
Ok(())
}
pub fn check_validity(did: &BaseDIDUrl) -> Result<(), Error> {
Self::valid_method_name(did.method())?;
Self::valid_method_id(did.method_id())?;
if did.scheme() != Self::SCHEME {
return Err(Error::InvalidScheme);
}
if !did.path().is_empty() || did.fragment().is_some() || did.query().is_some() {
return Err(Error::InvalidMethodId);
}
Ok(())
}
}
impl AsRef<CoreDID> for CoreDID {
fn as_ref(&self) -> &CoreDID {
self
}
}
impl From<CoreDID> for BaseDIDUrl {
fn from(did: CoreDID) -> Self {
did.0
}
}
impl TryFrom<BaseDIDUrl> for CoreDID {
type Error = Error;
fn try_from(base_did_url: BaseDIDUrl) -> Result<Self, Self::Error> {
Ok(Self(base_did_url))
}
}
impl Debug for CoreDID {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("{}", self.as_str()))
}
}
impl Display for CoreDID {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("{}", self.as_str()))
}
}
impl AsRef<str> for CoreDID {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl FromStr for CoreDID {
type Err = Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
Self::parse(string)
}
}
impl TryFrom<&str> for CoreDID {
type Error = Error;
fn try_from(other: &str) -> Result<Self, Self::Error> {
Self::parse(other)
}
}
impl TryFrom<String> for CoreDID {
type Error = Error;
fn try_from(other: String) -> Result<Self, Self::Error> {
Self::parse(other)
}
}
impl From<CoreDID> for String {
fn from(did: CoreDID) -> Self {
did.0.into_string()
}
}
impl PartialEq<str> for CoreDID {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&'_ str> for CoreDID {
fn eq(&self, other: &&'_ str) -> bool {
self == *other
}
}
impl KeyComparable for CoreDID {
type Key = CoreDID;
#[inline]
fn key(&self) -> &Self::Key {
self
}
}
#[inline(always)]
pub(crate) const fn is_char_method_name(ch: char) -> bool {
matches!(ch, '0'..='9' | 'a'..='z')
}
#[inline(always)]
pub(crate) const fn is_char_method_id(ch: char) -> bool {
matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '.' | '-' | '_' | ':')
}
impl<D> DID for D where
D: Clone
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ Hash
+ FromStr
+ TryFrom<CoreDID>
+ Into<String>
+ Into<CoreDID>
+ AsRef<CoreDID>
{
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_core_did_valid() {
assert_eq!(
CoreDID::parse("did:example:123456890").unwrap(),
"did:example:123456890"
);
assert_eq!(
CoreDID::parse("did:iota:main:123456890").unwrap(),
"did:iota:main:123456890"
);
}
#[test]
fn test_core_did_invalid() {
assert!(CoreDID::parse("").is_err());
assert!(CoreDID::parse("did:").is_err());
assert!(CoreDID::parse("dad:example:123456890").is_err());
}
proptest::proptest! {
#[test]
fn test_fuzz_core_did_valid(s in r"did:[a-z0-9]{1,10}:[a-zA-Z0-9\.\-_:]{1,60}") {
assert_eq!(CoreDID::parse(&s).unwrap().as_str(), &s);
}
#[test]
fn test_fuzz_core_did_no_panic(s in "\\PC*") {
assert!(CoreDID::parse(s).is_err());
}
#[test]
fn test_fuzz_set_method_name_no_panic(s in "\\PC*") {
let mut did = CoreDID::parse("did:example:1234567890").unwrap();
let _ = did.set_method_id(&s);
}
#[test]
fn test_fuzz_set_method_id_no_panic(s in "\\PC*") {
let mut did = CoreDID::parse("did:example:1234567890").unwrap();
let _ = did.set_method_name(&s);
}
}
}