http_request_derive/
http_request.rs1use async_trait::async_trait;
6use bytes::Bytes;
7use http::{HeaderMap, StatusCode, Uri};
8use snafu::{OptionExt, ResultExt};
9use url::Url;
10
11use crate::{
12 error::{
13 BuildRequestSnafu, NonSuccessStatusSnafu, ParseUriSnafu, UnauthorizedSnafu,
14 UrlCannotBeABaseSnafu,
15 },
16 Error, FromHttpResponse, HttpRequestBody, HttpRequestQueryParams,
17};
18
19#[async_trait]
21pub trait HttpRequest {
22 type Response: FromHttpResponse;
24
25 type Query: HttpRequestQueryParams;
27
28 type Body: HttpRequestBody;
30
31 const METHOD: http::Method;
33
34 fn path(&self) -> String;
36
37 fn query(&self) -> Option<&Self::Query> {
39 None
40 }
41
42 fn body(&self) -> Option<&Self::Body> {
44 None
45 }
46
47 fn apply_headers(&self, headers: &mut HeaderMap) {
49 if let Some(body) = self.body() {
50 body.apply_headers(headers);
51 }
52 }
53
54 fn to_http_request(&self, base_url: &Url) -> Result<http::request::Request<Vec<u8>>, Error> {
56 let body = self
57 .body()
58 .map(HttpRequestBody::to_vec)
59 .transpose()?
60 .unwrap_or_default();
61
62 let uri = {
63 let mut url = base_url.clone();
64 {
65 let mut segments =
66 url.path_segments_mut()
67 .ok()
68 .with_context(|| UrlCannotBeABaseSnafu {
69 url: base_url.clone(),
70 })?;
71 let _ = segments.pop_if_empty().extend(self.path().split('/'));
72 }
73
74 let query = self
75 .query()
76 .map(HttpRequestQueryParams::http_request_query_string)
77 .transpose()?;
78 if let Some(query) = query {
79 let query = query.as_deref();
80 url.set_query(query);
81 }
82
83 url.as_str().parse::<Uri>().context(ParseUriSnafu)?
84 };
85
86 let mut headers = HeaderMap::new();
87 self.apply_headers(&mut headers);
88
89 let mut builder = http::request::Request::builder()
90 .method(Self::METHOD)
91 .uri(uri);
92
93 for (name, value) in &headers {
94 builder = builder.header(name, value);
95 }
96
97 builder.body(body).context(BuildRequestSnafu)
98 }
99
100 fn read_response(response: http::Response<Bytes>) -> Result<Self::Response, Error> {
109 match response.status() {
110 status if status.is_success() => Self::Response::from_http_response(response),
111
112 StatusCode::UNAUTHORIZED => Err(UnauthorizedSnafu.build()),
113 status => Err(NonSuccessStatusSnafu {
114 status,
115 data: response.into_body(),
116 }
117 .build()),
118 }
119 }
120
121 #[cfg(feature = "reqwest")]
130 async fn read_reqwest_response(response: reqwest::Response) -> Result<Self::Response, Error> {
131 match response.status() {
132 status if status.is_success() => Self::Response::from_reqwest_response(response).await,
133
134 StatusCode::UNAUTHORIZED => Err(UnauthorizedSnafu.build()),
135 status => Err(NonSuccessStatusSnafu {
136 status,
137 data: response.bytes().await.context(crate::error::ReqwestSnafu {
138 message: "Failed to receive error response",
139 })?,
140 }
141 .build()),
142 }
143 }
144}