ssi_dids_core/resolution/
http.rs1use iref::{Uri, UriBuf};
2use reqwest::{header, StatusCode};
3
4use crate::{
5 document::{self, representation::MediaType},
6 DIDResolver, DID,
7};
8
9use super::{Error, Metadata, Output};
10
11pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
12
13#[derive(Debug, Clone)]
16pub struct HTTPDIDResolver {
17 endpoint: UriBuf,
19}
20
21impl HTTPDIDResolver {
22 pub fn new(url: &Uri) -> Self {
24 Self {
25 endpoint: url.to_owned(),
26 }
27 }
28
29 pub fn endpoint(&self) -> &Uri {
30 &self.endpoint
31 }
32}
33
34#[derive(Debug, thiserror::Error)]
35pub enum InternalError {
36 #[error("unable to initialize HTTP client")]
37 Initialization,
38
39 #[error("HTTP error: {0}")]
40 Reqwest(reqwest::Error),
41
42 #[error("HTTP server returned error code {0}")]
43 Error(StatusCode),
44
45 #[error("missing content-type header")]
46 MissingContentType,
47
48 #[error("content type mismatch")]
49 ContentTypeMismatch,
50
51 #[error("invalid content type")]
52 InvalidContentType,
53}
54
55impl DIDResolver for HTTPDIDResolver {
56 async fn resolve_representation<'a>(
58 &'a self,
59 did: &'a DID,
60 options: super::Options,
61 ) -> Result<Output<Vec<u8>>, Error> {
62 let query = serde_urlencoded::to_string(&options.parameters).unwrap();
63
64 let did_urlencoded =
65 percent_encoding::utf8_percent_encode(did, percent_encoding::CONTROLS).to_string();
66
67 let mut url = self.endpoint.to_string() + &did_urlencoded;
68 if !query.is_empty() {
69 url.push('?');
70 url.push_str(&query)
71 }
72
73 let url: reqwest::Url = url.parse().unwrap();
74
75 let client = reqwest::Client::builder()
76 .build()
77 .map_err(|_| Error::internal(InternalError::Initialization))?;
78
79 let mut request = client.get(url);
80 if let Some(accept) = options.accept {
81 request = request.header("Accept", accept.to_string());
82 }
83
84 let response = request
85 .header("User-Agent", USER_AGENT)
86 .send()
87 .await
88 .map_err(|e| Error::internal(InternalError::Reqwest(e)))?;
89
90 match response.status() {
91 StatusCode::OK => {
92 let content_type: Option<String> =
93 match (response.headers().get(header::CONTENT_TYPE), options.accept) {
94 (Some(content_type), Some(accept)) => {
95 if content_type == accept.name() {
96 Some(accept.name().to_string())
97 } else {
98 return Err(Error::internal(InternalError::ContentTypeMismatch));
99 }
100 }
101 (Some(content_type), None) => Some(
102 content_type
103 .to_str()
104 .map_err(|_| Error::internal(InternalError::InvalidContentType))?
105 .to_string(),
106 ),
107 (None, Some(_)) => {
108 return Err(Error::internal(InternalError::MissingContentType))
109 }
110 (None, None) => None,
111 };
112
113 Ok(Output::new(
114 response
115 .bytes()
116 .await
117 .map_err(|e| Error::internal(InternalError::Reqwest(e)))?
118 .to_vec(),
119 document::Metadata::default(),
120 Metadata::from_content_type(content_type),
121 ))
122 }
123 StatusCode::NOT_FOUND => Err(Error::NotFound),
124 StatusCode::NOT_IMPLEMENTED => {
125 Err(Error::MethodNotSupported(did.method_name().to_string()))
126 }
127 StatusCode::VARIANT_ALSO_NEGOTIATES => Err(Error::RepresentationNotSupported(
128 options
129 .accept
130 .map(MediaType::into_name)
131 .unwrap_or_default()
132 .to_string(),
133 )),
134 error_code => Err(Error::internal(InternalError::Error(error_code))),
135 }
136 }
137}