http_request_derive/
http_request.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use bytes::Bytes;
6use http::{HeaderMap, Uri};
7use snafu::{OptionExt, ResultExt};
8use url::Url;
9
10use crate::{
11    Error, FromHttpResponse, HttpRequestBody, HttpRequestQueryParams,
12    error::{BuildRequestSnafu, ParseUriSnafu, UrlCannotBeABaseSnafu},
13};
14
15/// A trait implemented for types that are sent to the API as parameters
16pub trait HttpRequest {
17    /// The response type that is expected to the request
18    type Response: FromHttpResponse;
19
20    /// The query type that is sent to the API endpoint
21    type Query: HttpRequestQueryParams;
22
23    /// The body type that is sent to the API endpoint
24    type Body: HttpRequestBody;
25
26    /// The method used to send the data to the API endpoint
27    const METHOD: http::Method;
28
29    /// Get the API endpoint path relative to the base URL
30    fn path(&self) -> String;
31
32    /// Get query parameters for the `http::Request`
33    fn query(&self) -> Option<&Self::Query> {
34        None
35    }
36
37    /// Get the body for the `http::Request`
38    fn body(&self) -> Option<&Self::Body> {
39        None
40    }
41
42    /// Get the headers for the `http::Request`
43    fn apply_headers(&self, headers: &mut HeaderMap) {
44        if let Some(body) = self.body() {
45            body.apply_headers(headers);
46        }
47    }
48
49    /// Build a HTTP request from the request type
50    fn to_http_request(&self, base_url: &Url) -> Result<http::request::Request<Vec<u8>>, Error> {
51        let body = self
52            .body()
53            .map(HttpRequestBody::to_vec)
54            .transpose()?
55            .unwrap_or_default();
56
57        let uri = {
58            let mut url = base_url.clone();
59            {
60                let mut segments =
61                    url.path_segments_mut()
62                        .ok()
63                        .with_context(|| UrlCannotBeABaseSnafu {
64                            url: base_url.clone(),
65                        })?;
66                let _ = segments
67                    .pop_if_empty()
68                    .extend(self.path().split('/').skip_while(|s| s.is_empty()));
69            }
70
71            let query = self
72                .query()
73                .map(HttpRequestQueryParams::http_request_query_string)
74                .transpose()?;
75            if let Some(query) = query {
76                let query = query.as_deref();
77                url.set_query(query);
78            }
79
80            url.as_str().parse::<Uri>().context(ParseUriSnafu)?
81        };
82
83        let mut headers = HeaderMap::new();
84        self.apply_headers(&mut headers);
85
86        let mut builder = http::request::Request::builder()
87            .method(Self::METHOD)
88            .uri(uri);
89
90        for (name, value) in &headers {
91            builder = builder.header(name, value);
92        }
93
94        builder.body(body).context(BuildRequestSnafu)
95    }
96
97    /// Convert the response from a `http::Response`
98    fn read_response(response: http::Response<Bytes>) -> Result<Self::Response, Error> {
99        Self::Response::from_http_response(response)
100    }
101}