1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use iref::{Uri, UriBuf};
use reqwest::{header, StatusCode};

use crate::{
    document::{self, representation::MediaType},
    DIDResolver, DID,
};

use super::{Error, Metadata, Output};

pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));

/// A DID Resolver implementing a client for the [DID Resolution HTTP(S)
/// Binding](https://w3c-ccg.github.io/did-resolution/#bindings-https).
#[derive(Debug, Clone)]
pub struct HTTPDIDResolver {
    /// HTTP(S) URL for DID resolver HTTP(S) endpoint.
    endpoint: UriBuf,
}

impl HTTPDIDResolver {
    /// Construct a new HTTP DID Resolver with a given [endpoint][Self::endpoint] URL.
    pub fn new(url: &Uri) -> Self {
        Self {
            endpoint: url.to_owned(),
        }
    }

    pub fn endpoint(&self) -> &Uri {
        &self.endpoint
    }
}

#[derive(Debug, thiserror::Error)]
pub enum InternalError {
    #[error("unable to initialize HTTP client")]
    Initialization,

    #[error("HTTP error: {0}")]
    Reqwest(reqwest::Error),

    #[error("HTTP server returned error code {0}")]
    Error(StatusCode),

    #[error("missing content-type header")]
    MissingContentType,

    #[error("content type mismatch")]
    ContentTypeMismatch,

    #[error("invalid content type")]
    InvalidContentType,
}

impl DIDResolver for HTTPDIDResolver {
    /// Resolve a DID over HTTP(S), using the [DID Resolution HTTP(S) Binding](https://w3c-ccg.github.io/did-resolution/#bindings-https).
    async fn resolve_representation<'a>(
        &'a self,
        did: &'a DID,
        options: super::Options,
    ) -> Result<Output<Vec<u8>>, Error> {
        let query = serde_urlencoded::to_string(&options.parameters).unwrap();

        let did_urlencoded =
            percent_encoding::utf8_percent_encode(did, percent_encoding::CONTROLS).to_string();

        let mut url = self.endpoint.to_string() + &did_urlencoded;
        if !query.is_empty() {
            url.push('?');
            url.push_str(&query)
        }

        let url: reqwest::Url = url.parse().unwrap();

        let client = reqwest::Client::builder()
            .build()
            .map_err(|_| Error::internal(InternalError::Initialization))?;

        let mut request = client.get(url);
        if let Some(accept) = options.accept {
            request = request.header("Accept", accept.to_string());
        }

        let response = request
            .header("User-Agent", USER_AGENT)
            .send()
            .await
            .map_err(|e| Error::internal(InternalError::Reqwest(e)))?;

        match response.status() {
            StatusCode::OK => {
                let content_type: Option<String> =
                    match (response.headers().get(header::CONTENT_TYPE), options.accept) {
                        (Some(content_type), Some(accept)) => {
                            if content_type == accept.name() {
                                Some(accept.name().to_string())
                            } else {
                                return Err(Error::internal(InternalError::ContentTypeMismatch));
                            }
                        }
                        (Some(content_type), None) => Some(
                            content_type
                                .to_str()
                                .map_err(|_| Error::internal(InternalError::InvalidContentType))?
                                .to_string(),
                        ),
                        (None, Some(_)) => {
                            return Err(Error::internal(InternalError::MissingContentType))
                        }
                        (None, None) => None,
                    };

                Ok(Output::new(
                    response
                        .bytes()
                        .await
                        .map_err(|e| Error::internal(InternalError::Reqwest(e)))?
                        .to_vec(),
                    document::Metadata::default(),
                    Metadata::from_content_type(content_type),
                ))
            }
            StatusCode::NOT_FOUND => Err(Error::NotFound),
            StatusCode::NOT_IMPLEMENTED => {
                Err(Error::MethodNotSupported(did.method_name().to_string()))
            }
            StatusCode::VARIANT_ALSO_NEGOTIATES => Err(Error::RepresentationNotSupported(
                options
                    .accept
                    .map(MediaType::into_name)
                    .unwrap_or_default()
                    .to_string(),
            )),
            error_code => Err(Error::internal(InternalError::Error(error_code))),
        }
    }
}