use core::fmt;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use super::{DidResolver, DocumentCache};
use crate::did::{
Did, DidDocument, ResolutionError, ResolutionErrorKind, ResolutionOptions, ResolutionResult,
};
pub struct Resolver {
methods: HashMap<String, Arc<dyn DidResolver>>,
cache: Option<DocumentCache>,
}
impl Resolver {
pub fn builder() -> ResolverBuilder {
ResolverBuilder::new()
}
pub fn methods(&self) -> Vec<&str> {
let mut methods: Vec<&str> = self.methods.keys().map(String::as_str).collect();
methods.sort_unstable();
methods
}
pub fn supports(&self, method: &str) -> bool {
self.methods.contains_key(method)
}
pub fn resolver(&self, method: &str) -> Option<&Arc<dyn DidResolver>> {
self.methods.get(method)
}
pub fn cache(&self) -> Option<&DocumentCache> {
self.cache.as_ref()
}
pub async fn resolve(&self, did: &Did) -> Result<ResolutionResult, ResolutionError> {
self.resolve_with(did, &ResolutionOptions::default()).await
}
pub async fn resolve_with(
&self,
did: &Did,
options: &ResolutionOptions,
) -> Result<ResolutionResult, ResolutionError> {
let resolver = self.methods.get(did.method()).ok_or_else(|| {
ResolutionError::new(ResolutionErrorKind::MethodNotSupported).with_detail(format!(
"no resolver is registered for `did:{}`",
did.method()
))
})?;
let cache = self
.cache
.as_ref()
.filter(|_| options.accept.is_none() && options.properties.is_empty());
if let Some(hit) = cache.and_then(|cache| cache.get(did)) {
return Ok(hit);
}
let result = check_result(did, resolver.resolve(did, options).await?)?;
if let Some(cache) = cache {
cache.insert(did.clone(), result.clone());
}
Ok(result)
}
pub async fn resolve_document(&self, did: &Did) -> Result<DidDocument, ResolutionError> {
let result = self.resolve(did).await?;
if result.document_metadata.is_deactivated() {
return Err(ResolutionError::new(ResolutionErrorKind::NotFound)
.with_detail(format!("`{did}` is deactivated")));
}
result.into_document()
}
}
fn check_result(did: &Did, result: ResolutionResult) -> Result<ResolutionResult, ResolutionError> {
if let Some(error) = &result.resolution_metadata.error {
return Err(error.clone());
}
match &result.document {
Some(document) => {
let canonical = result.document_metadata.canonical_id.as_ref();
if document.id != *did && canonical != Some(&document.id) {
return Err(
ResolutionError::new(ResolutionErrorKind::InvalidDidDocument).with_detail(
format!(
"the resolver returned the document of `{}` for `{did}`",
document.id
),
),
);
}
}
None if result.document_metadata.is_deactivated() => {}
None => {
return Err(ResolutionError::new(ResolutionErrorKind::NotFound)
.with_detail("the resolver returned neither a document nor an error"));
}
}
Ok(result)
}
impl fmt::Debug for Resolver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Resolver")
.field("methods", &self.methods())
.field("cache", &self.cache)
.finish()
}
}
#[derive(Default)]
pub struct ResolverBuilder {
methods: HashMap<String, Arc<dyn DidResolver>>,
cache: Option<DocumentCache>,
}
impl ResolverBuilder {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with(self, resolver: impl DidResolver + 'static) -> Self {
self.with_arc(Arc::new(resolver))
}
#[must_use]
pub fn with_arc(mut self, resolver: Arc<dyn DidResolver>) -> Self {
self.methods.insert(resolver.method().to_owned(), resolver);
self
}
#[must_use]
pub fn cache(self, ttl: Duration) -> Self {
self.with_cache(DocumentCache::new(ttl))
}
#[must_use]
pub fn with_cache(mut self, cache: DocumentCache) -> Self {
self.cache = Some(cache);
self
}
pub fn build(self) -> Resolver {
Resolver {
methods: self.methods,
cache: self.cache,
}
}
}
impl fmt::Debug for ResolverBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut methods: Vec<&str> = self.methods.keys().map(String::as_str).collect();
methods.sort_unstable();
f.debug_struct("ResolverBuilder")
.field("methods", &methods)
.field("cache", &self.cache)
.finish()
}
}