A library for ATProtocol identities.
22
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 121 lines 3.8 kB view raw
1//! Web DID client for did:web resolution. 2//! 3//! Resolves did:web identifiers by converting them to HTTPS URLs and fetching 4//! DID documents from well-known locations on web servers. 5//! - **`query_hostname()`**: Direct document retrieval from hostname well-known endpoints 6//! 7//! ## URL Conversion 8//! 9//! Transforms DIDs like `did:web:example.com:path:subpath` into HTTPS URLs following 10//! the did:web specification for well-known document locations. 11 12use tracing::instrument; 13 14use super::errors::WebDIDError; 15use super::model::Document; 16 17/// Converts a did:web DID to its corresponding HTTPS URL. 18/// Transforms DID format to the expected well-known document location. 19pub fn did_web_to_url(did: &str) -> Result<String, WebDIDError> { 20 let parts = did 21 .strip_prefix("did:web:") 22 .ok_or(WebDIDError::InvalidDIDPrefix)? 23 .split(':') 24 .collect::<Vec<&str>>(); 25 26 let hostname = parts.first().ok_or(WebDIDError::MissingHostname)?; 27 if hostname.is_empty() { 28 return Err(WebDIDError::MissingHostname); 29 } 30 let path_parts = &parts[1..]; 31 32 let url = if path_parts.is_empty() { 33 format!("https://{}/.well-known/did.json", hostname) 34 } else { 35 format!("https://{}/{}/did.json", hostname, path_parts.join("/")) 36 }; 37 38 Ok(url) 39} 40 41/// Queries a did:web DID document from its hosting location. 42/// Resolves the DID to HTTPS URL and fetches the JSON document. 43#[instrument(skip(http_client), err)] 44pub async fn query(http_client: &reqwest::Client, did: &str) -> Result<Document, WebDIDError> { 45 let url = did_web_to_url(did)?; 46 47 http_client 48 .get(&url) 49 .send() 50 .await 51 .map_err(|error| WebDIDError::HttpRequestFailed { 52 url: url.clone(), 53 error, 54 })? 55 .json::<Document>() 56 .await 57 .map_err(|error| WebDIDError::DocumentParseFailed { url, error }) 58} 59 60/// Queries a DID document directly from a hostname's well-known location. 61/// Fetches from https://{hostname}/.well-known/did.json 62#[instrument(skip(http_client), err)] 63pub async fn query_hostname( 64 http_client: &reqwest::Client, 65 hostname: &str, 66) -> Result<Document, WebDIDError> { 67 let url = format!("https://{}/.well-known/did.json", hostname); 68 69 http_client 70 .get(&url) 71 .send() 72 .await 73 .map_err(|error| WebDIDError::HttpRequestFailed { 74 url: url.clone(), 75 error, 76 })? 77 .json::<Document>() 78 .await 79 .map_err(|error| WebDIDError::DocumentParseFailed { url, error }) 80} 81 82#[cfg(test)] 83mod tests { 84 use super::*; 85 86 #[test] 87 fn test_did_web_to_url_simple_hostname() { 88 let result = did_web_to_url("did:web:example.com"); 89 assert_eq!(result.unwrap(), "https://example.com/.well-known/did.json"); 90 } 91 92 #[test] 93 fn test_did_web_to_url_with_path() { 94 let result = did_web_to_url("did:web:example.com:path"); 95 assert_eq!(result.unwrap(), "https://example.com/path/did.json"); 96 } 97 98 #[test] 99 fn test_did_web_to_url_with_nested_path() { 100 let result = did_web_to_url("did:web:example.com:path:subpath"); 101 assert_eq!(result.unwrap(), "https://example.com/path/subpath/did.json"); 102 } 103 104 #[test] 105 fn test_did_web_to_url_invalid_prefix() { 106 let result = did_web_to_url("did:plc:example.com"); 107 assert!(matches!(result, Err(WebDIDError::InvalidDIDPrefix))); 108 } 109 110 #[test] 111 fn test_did_web_to_url_missing_hostname() { 112 let result = did_web_to_url("did:web:"); 113 assert!(matches!(result, Err(WebDIDError::MissingHostname))); 114 } 115 116 #[test] 117 fn test_did_web_to_url_no_prefix() { 118 let result = did_web_to_url("example.com"); 119 assert!(matches!(result, Err(WebDIDError::InvalidDIDPrefix))); 120 } 121}