Skip to main content

ma_core/
resolve.rs

1//! DID document resolution traits and implementations.
2
3use async_trait::async_trait;
4use did_ma::Document;
5
6/// Trait for resolving a DID to its DID document.
7///
8/// Ship with `GatewayResolver` for HTTP gateway resolution.
9/// Implement this trait for custom resolution strategies.
10#[async_trait]
11pub trait DidResolver: Send + Sync {
12    async fn resolve(&self, did: &str) -> crate::error::Result<Document>;
13}
14
15/// Resolves DID documents via an IPFS/IPNS HTTP gateway.
16///
17/// The gateway must serve DID documents at `/ipns/<key-id>`.
18#[cfg(not(target_arch = "wasm32"))]
19pub struct GatewayResolver {
20    gateway_url: String,
21    client: reqwest::Client,
22}
23
24#[cfg(not(target_arch = "wasm32"))]
25impl GatewayResolver {
26    pub fn new(gateway_url: impl Into<String>) -> Self {
27        let mut url = gateway_url.into();
28        // Ensure trailing slash
29        if !url.ends_with('/') {
30            url.push('/');
31        }
32        Self {
33            gateway_url: url,
34            client: reqwest::Client::builder()
35                .timeout(std::time::Duration::from_secs(15))
36                .build()
37                .expect("failed to build HTTP client"),
38        }
39    }
40}
41
42#[cfg(not(target_arch = "wasm32"))]
43#[async_trait]
44impl DidResolver for GatewayResolver {
45    async fn resolve(&self, did: &str) -> crate::error::Result<Document> {
46        let parsed = did_ma::Did::try_from(did).map_err(crate::error::Error::Validation)?;
47        let url = format!("{}ipns/{}", self.gateway_url, parsed.ipns);
48
49        let response =
50            self.client
51                .get(&url)
52                .send()
53                .await
54                .map_err(|e| crate::error::Error::Resolution {
55                    did: did.to_string(),
56                    detail: e.to_string(),
57                })?;
58
59        if !response.status().is_success() {
60            return Err(crate::error::Error::Resolution {
61                did: did.to_string(),
62                detail: format!("gateway returned {}", response.status()),
63            });
64        }
65
66        let body = response
67            .text()
68            .await
69            .map_err(|e| crate::error::Error::Resolution {
70                did: did.to_string(),
71                detail: e.to_string(),
72            })?;
73
74        Document::unmarshal(&body).map_err(|e| crate::error::Error::Resolution {
75            did: did.to_string(),
76            detail: e.to_string(),
77        })
78    }
79}