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
use bytes::Bytes;
use http::{HeaderMap, StatusCode, Uri};
use snafu::{OptionExt, ResultExt};
use url::Url;
use crate::{
error::{
BuildRequestSnafu, NonSuccessStatusSnafu, ParseUriSnafu, UnauthorizedSnafu,
UrlCannotBeABaseSnafu,
},
Error, FromHttpResponse, HttpRequestBody, HttpRequestQueryParams,
};
/// A trait implemented for types that are sent to the API as parameters
pub trait HttpRequest {
/// The response type that is expected to the request
type Response: FromHttpResponse;
/// The query type that is sent to the API endpoint
type Query: HttpRequestQueryParams;
/// The body type that is sent to the API endpoint
type Body: HttpRequestBody;
/// The method used to send the data to the API endpoint
const METHOD: http::Method;
/// Get the API endpoint path relative to the base URL
fn path(&self) -> String;
/// Get query parameters for the `http::Request`
fn query(&self) -> Option<&Self::Query> {
None
}
/// Get the body for the `http::Request`
fn body(&self) -> Option<&Self::Body> {
None
}
/// Get the headers for the `http::Request`
fn apply_headers(&self, headers: &mut HeaderMap) {
let _ = headers
.entry(http::header::CONTENT_TYPE)
.or_insert_with(|| http::HeaderValue::from_static("application/json"));
}
/// Build a HTTP request from the request type
fn to_http_request(&self, base_url: &Url) -> Result<http::request::Request<Vec<u8>>, Error> {
let body = self
.body()
.map(HttpRequestBody::to_vec)
.transpose()?
.unwrap_or_default();
let uri = {
let mut url = base_url.clone();
{
let mut segments =
url.path_segments_mut()
.ok()
.with_context(|| UrlCannotBeABaseSnafu {
url: base_url.clone(),
})?;
let _ = segments.pop_if_empty().extend(self.path().split('/'));
}
let query = self
.query()
.map(HttpRequestQueryParams::http_request_query_string)
.transpose()?;
if let Some(query) = query {
let query = query.as_deref();
url.set_query(query);
}
url.as_str().parse::<Uri>().context(ParseUriSnafu)?
};
let mut headers = HeaderMap::new();
if let Some(body) = self.body() {
body.apply_headers(&mut headers);
}
let mut builder = http::request::Request::builder()
.method(Self::METHOD)
.uri(uri);
for (name, value) in &headers {
builder = builder.header(name, value);
}
builder.body(body).context(BuildRequestSnafu)
}
/// Convert the response from a `http::Response`
///
/// # Errors
///
/// Usually HTTP response codes that don't indicate success will be converted to the
/// corresponding [`Error`]. For example, a [`StatusCode::UNAUTHORIZED`] is converted
/// to [`Error::Unauthorized`]. This is the behavior found in the default implementation
/// and can be overwritten by a specialized implementation if required.
fn read_response(response: http::Response<Bytes>) -> Result<Self::Response, Error> {
match response.status() {
status if status.is_success() => Self::Response::from_http_response(response),
StatusCode::UNAUTHORIZED => Err(UnauthorizedSnafu.build()),
status => Err(NonSuccessStatusSnafu {
status,
data: response.into_body(),
}
.build()),
}
}
}