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        let client = reqwest::Client::builder()
33            .timeout(std::time::Duration::from_secs(15))
34            .build()
35            .unwrap_or_else(|_| reqwest::Client::new());
36
37        Self {
38            gateway_url: url,
39            client,
40        }
41    }
42}
43
44#[cfg(not(target_arch = "wasm32"))]
45#[async_trait]
46impl DidResolver for GatewayResolver {
47    async fn resolve(&self, did: &str) -> crate::error::Result<Document> {
48        let parsed = did_ma::Did::try_from(did).map_err(crate::error::Error::Validation)?;
49        let url = format!("{}ipns/{}", self.gateway_url, parsed.ipns);
50
51        let response =
52            self.client
53                .get(&url)
54                .send()
55                .await
56                .map_err(|e| crate::error::Error::Resolution {
57                    did: did.to_string(),
58                    detail: e.to_string(),
59                })?;
60
61        if !response.status().is_success() {
62            return Err(crate::error::Error::Resolution {
63                did: did.to_string(),
64                detail: format!("gateway returned {}", response.status()),
65            });
66        }
67
68        let body = response
69            .text()
70            .await
71            .map_err(|e| crate::error::Error::Resolution {
72                did: did.to_string(),
73                detail: e.to_string(),
74            })?;
75
76        Document::unmarshal(&body).map_err(|e| crate::error::Error::Resolution {
77            did: did.to_string(),
78            detail: e.to_string(),
79        })
80    }
81}