1use tracing::instrument;
19
20use super::errors::WebDIDError;
21use super::model::Document;
22
23pub fn did_web_to_url(did: &str) -> Result<String, WebDIDError> {
26 let parts = did
27 .strip_prefix("did:web:")
28 .ok_or(WebDIDError::InvalidDIDPrefix)?
29 .split(':')
30 .collect::<Vec<&str>>();
31
32 let hostname = parts.first().ok_or(WebDIDError::MissingHostname)?;
33 if hostname.is_empty() {
34 return Err(WebDIDError::MissingHostname);
35 }
36 let path_parts = &parts[1..];
37
38 let url = if path_parts.is_empty() {
39 format!("https://{}/.well-known/did.json", hostname)
40 } else {
41 format!("https://{}/{}/did.json", hostname, path_parts.join("/"))
42 };
43
44 Ok(url)
45}
46
47#[instrument(skip(http_client), err)]
50pub async fn query(http_client: &reqwest::Client, did: &str) -> Result<Document, WebDIDError> {
51 let url = did_web_to_url(did)?;
52
53 http_client
54 .get(&url)
55 .send()
56 .await
57 .map_err(|error| WebDIDError::HttpRequestFailed {
58 url: url.clone(),
59 error,
60 })?
61 .json::<Document>()
62 .await
63 .map_err(|error| WebDIDError::DocumentParseFailed { url, error })
64}
65
66#[instrument(skip(http_client), err)]
69pub async fn query_hostname(
70 http_client: &reqwest::Client,
71 hostname: &str,
72) -> Result<Document, WebDIDError> {
73 let url = format!("https://{}/.well-known/did.json", hostname);
74
75 http_client
76 .get(&url)
77 .send()
78 .await
79 .map_err(|error| WebDIDError::HttpRequestFailed {
80 url: url.clone(),
81 error,
82 })?
83 .json::<Document>()
84 .await
85 .map_err(|error| WebDIDError::DocumentParseFailed { url, error })
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_did_web_to_url_simple_hostname() {
94 let result = did_web_to_url("did:web:example.com");
95 assert_eq!(result.unwrap(), "https://example.com/.well-known/did.json");
96 }
97
98 #[test]
99 fn test_did_web_to_url_with_path() {
100 let result = did_web_to_url("did:web:example.com:path");
101 assert_eq!(result.unwrap(), "https://example.com/path/did.json");
102 }
103
104 #[test]
105 fn test_did_web_to_url_with_nested_path() {
106 let result = did_web_to_url("did:web:example.com:path:subpath");
107 assert_eq!(result.unwrap(), "https://example.com/path/subpath/did.json");
108 }
109
110 #[test]
111 fn test_did_web_to_url_invalid_prefix() {
112 let result = did_web_to_url("did:plc:example.com");
113 assert!(matches!(result, Err(WebDIDError::InvalidDIDPrefix)));
114 }
115
116 #[test]
117 fn test_did_web_to_url_missing_hostname() {
118 let result = did_web_to_url("did:web:");
119 assert!(matches!(result, Err(WebDIDError::MissingHostname)));
120 }
121
122 #[test]
123 fn test_did_web_to_url_no_prefix() {
124 let result = did_web_to_url("example.com");
125 assert!(matches!(result, Err(WebDIDError::InvalidDIDPrefix)));
126 }
127}