ndn_security/did/
resolver.rs1pub mod key;
10pub mod ndn;
11
12pub use key::KeyDidResolver;
13pub use ndn::NdnDidResolver;
14
15use std::{collections::HashMap, future::Future, pin::Pin};
16
17use crate::did::{
18 document::DidDocument,
19 metadata::{DidResolutionError, DidResolutionResult},
20};
21
22#[derive(Debug, thiserror::Error)]
30pub enum DidError {
31 #[error("invalid DID: {0}")]
32 InvalidDid(String),
33 #[error("unsupported DID method: {0}")]
34 UnsupportedMethod(String),
35 #[error("DID document not found: {0}")]
36 NotFound(String),
37 #[error("resolution failed: {0}")]
38 Resolution(String),
39 #[error("invalid DID document: {0}")]
40 InvalidDocument(String),
41}
42
43pub trait DidResolver: Send + Sync {
51 fn method(&self) -> &str;
53
54 fn resolve<'a>(
56 &'a self,
57 did: &'a str,
58 ) -> Pin<Box<dyn Future<Output = DidResolutionResult> + Send + 'a>>;
59}
60
61pub struct UniversalResolver {
68 resolvers: HashMap<String, Box<dyn DidResolver>>,
69}
70
71impl UniversalResolver {
72 pub fn new() -> Self {
74 let mut r = Self {
75 resolvers: HashMap::new(),
76 };
77 r.register(KeyDidResolver);
78 r.register(NdnDidResolver::default());
79 r
80 }
81
82 pub fn with(mut self, resolver: impl DidResolver + 'static) -> Self {
84 self.register(resolver);
85 self
86 }
87
88 fn register(&mut self, resolver: impl DidResolver + 'static) {
89 self.resolvers
90 .insert(resolver.method().to_string(), Box::new(resolver));
91 }
92
93 pub async fn resolve(&self, did: &str) -> DidResolutionResult {
95 let method = match parse_method(did) {
96 Some(m) => m,
97 None => {
98 return DidResolutionResult::err(
99 DidResolutionError::InvalidDid,
100 format!("cannot parse DID method from: {did}"),
101 );
102 }
103 };
104
105 match self.resolvers.get(method) {
106 Some(resolver) => resolver.resolve(did).await,
107 None => DidResolutionResult::err(
108 DidResolutionError::MethodNotSupported,
109 format!("no resolver registered for did:{method}"),
110 ),
111 }
112 }
113
114 pub async fn resolve_document(&self, did: &str) -> Result<DidDocument, DidError> {
118 self.resolve(did).await.into_document()
119 }
120}
121
122impl Default for UniversalResolver {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128pub(crate) fn parse_method(did: &str) -> Option<&str> {
130 let rest = did.strip_prefix("did:")?;
131 let colon = rest.find(':')?;
132 Some(&rest[..colon])
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn parse_method_valid() {
141 assert_eq!(parse_method("did:ndn:com:acme:alice"), Some("ndn"));
142 assert_eq!(parse_method("did:key:z6Mk..."), Some("key"));
143 assert_eq!(parse_method("did:web:example.com"), Some("web"));
144 }
145
146 #[test]
147 fn parse_method_invalid() {
148 assert_eq!(parse_method("not-a-did"), None);
149 assert_eq!(parse_method("did:"), None);
150 assert_eq!(parse_method("did:no-colon"), None);
151 }
152
153 #[tokio::test]
154 async fn unsupported_method_returns_error_result() {
155 let resolver = UniversalResolver::new();
156 let result = resolver.resolve("did:web:example.com").await;
157 assert!(result.did_resolution_metadata.error.is_some());
158 }
159}