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
72 .pop_if_empty()
73 .extend(self.path().split('/').skip_while(|s| s.is_empty()));
74 }
75
76 let query = self
77 .query()
78 .map(HttpRequestQueryParams::http_request_query_string)
79 .transpose()?;
80 if let Some(query) = query {
81 let query = query.as_deref();
82 url.set_query(query);
83 }
84
85 url.as_str().parse::<Uri>().context(ParseUriSnafu)?
86 };
87
88 let mut headers = HeaderMap::new();
89 self.apply_headers(&mut headers);
90
91 let mut builder = http::request::Request::builder()
92 .method(Self::METHOD)
93 .uri(uri);
94
95 for (name, value) in &headers {
96 builder = builder.header(name, value);
97 }
98
99 builder.body(body).context(BuildRequestSnafu)
100 }
101
102 fn read_response(response: http::Response<Bytes>) -> Result<Self::Response, Error> {
111 match response.status() {
112 status if status.is_success() => Self::Response::from_http_response(response),
113
114 StatusCode::UNAUTHORIZED => Err(UnauthorizedSnafu.build()),
115 status => Err(NonSuccessStatusSnafu {
116 status,
117 data: response.into_body(),
118 }
119 .build()),
120 }
121 }
122}