1use tracing::instrument;
13
14use super::errors::WebDIDError;
15use super::model::Document;
16
17pub 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#[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#[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}