use super::DidMethod;
use crate::did::{Did, DidDocument};
use crate::{DidError, DidResult};
use async_trait::async_trait;
#[cfg(feature = "did-web")]
pub struct DidWebMethod {
client: reqwest::Client,
timeout_secs: u64,
}
#[cfg(feature = "did-web")]
impl Default for DidWebMethod {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "did-web")]
impl DidWebMethod {
pub fn new() -> Self {
Self {
client: reqwest::Client::new(),
timeout_secs: 30,
}
}
pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
self.timeout_secs = timeout_secs;
self
}
pub fn did_to_url(&self, did: &Did) -> DidResult<String> {
let method_specific_id = did.method_specific_id();
if method_specific_id.is_empty() {
return Err(DidError::InvalidFormat(
"Empty method-specific-id".to_string(),
));
}
let parts: Vec<&str> = method_specific_id.split(':').collect();
let domain = parts[0].replace("%3A", ":").replace("%2F", "/");
let path = if parts.len() > 1 {
parts[1..]
.iter()
.map(|p| p.replace("%2F", "/"))
.collect::<Vec<_>>()
.join("/")
} else {
".well-known".to_string()
};
let url = if path == ".well-known" {
format!("https://{}/.well-known/did.json", domain)
} else {
format!("https://{}/{}/did.json", domain, path)
};
Ok(url)
}
}
#[cfg(feature = "did-web")]
#[async_trait]
impl DidMethod for DidWebMethod {
fn method_name(&self) -> &str {
"web"
}
async fn resolve(&self, did: &Did) -> DidResult<DidDocument> {
if !self.supports(did) {
return Err(DidError::UnsupportedMethod(did.method().to_string()));
}
let url = self.did_to_url(did)?;
let response = self
.client
.get(&url)
.timeout(std::time::Duration::from_secs(self.timeout_secs))
.header("Accept", "application/did+json, application/json")
.send()
.await
.map_err(|e| DidError::NetworkError(e.to_string()))?;
if !response.status().is_success() {
return Err(DidError::ResolutionFailed(format!(
"HTTP {} from {}",
response.status(),
url
)));
}
let body = response
.text()
.await
.map_err(|e| DidError::NetworkError(e.to_string()))?;
DidDocument::from_json(&body)
}
}
#[cfg(all(test, feature = "did-web"))]
mod tests {
use super::*;
#[test]
fn test_did_to_url_simple() {
let method = DidWebMethod::new();
let did = Did::new("did:web:example.com").expect("valid did:web DID");
let url = method.did_to_url(&did).expect("valid URL from did:web DID");
assert_eq!(url, "https://example.com/.well-known/did.json");
}
#[test]
fn test_did_to_url_with_path() {
let method = DidWebMethod::new();
let did = Did::new("did:web:example.com:users:alice").expect("valid did:web DID with path");
let url = method
.did_to_url(&did)
.expect("valid URL from did:web DID with path");
assert_eq!(url, "https://example.com/users/alice/did.json");
}
#[test]
fn test_did_to_url_with_port() {
let method = DidWebMethod::new();
let did = Did::new("did:web:example.com%3A8080").expect("valid did:web DID with port");
let url = method
.did_to_url(&did)
.expect("valid URL from did:web DID with port");
assert_eq!(url, "https://example.com:8080/.well-known/did.json");
}
}