use http_api_client::http::{
self,
header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, USER_AGENT},
HeaderMap, Method, Request, Response, StatusCode,
};
use serde::{de::DeserializeOwned, ser::Serialize};
use url::Url;
use crate::endpoints::v4::{AccessToken, ErrorResponseBody, BASE_URL};
type Body = Vec<u8>;
pub fn render_request<ReqQuery, ReqBody>(
method: Method,
path: &str,
query: Option<&ReqQuery>,
body: Option<&ReqBody>,
access_token: Option<&AccessToken>,
base_url: Option<&Url>,
) -> Result<Request<Body>, RenderRequestError>
where
ReqQuery: Serialize,
ReqBody: Serialize,
{
let path = path.strip_prefix('/').unwrap_or(path);
let path = path.strip_prefix("v4/").unwrap_or(path);
let base_url = base_url.unwrap_or(&BASE_URL);
let request = Request::builder().method(method);
let mut url = base_url.join(path).map_err(RenderRequestError::UrlJoin)?;
if let Some(query) = query {
let query = serde_qs::to_string(query).map_err(RenderRequestError::QuerySer)?;
url.set_query(Some(&query));
}
let request = request.uri(url.as_str());
let request = if let Some(access_token) = access_token {
request.header(AUTHORIZATION, format!("Bearer {}", access_token))
} else {
request
};
let request = request
.header(ACCEPT, "application/json")
.header(USER_AGENT, "linode-api");
let request = if let Some(body) = body {
let body = serde_json::to_vec(body).map_err(RenderRequestError::BodySer)?;
request.header(CONTENT_TYPE, "application/json").body(body)
} else {
request.body(vec![])
};
let request = request.map_err(RenderRequestError::RequestBuild)?;
Ok(request)
}
#[derive(Debug)]
pub enum RenderRequestError {
UrlJoin(url::ParseError),
QuerySer(serde_qs::Error),
BodySer(serde_json::Error),
RequestBuild(http::Error),
}
impl core::fmt::Display for RenderRequestError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for RenderRequestError {}
pub fn parse_response<RespBody>(
response: Response<Body>,
) -> Result<(StatusCode, Result<RespBody, ErrorResponseBody>, HeaderMap), ParseResponseError>
where
RespBody: DeserializeOwned,
{
let status = response.status();
let headers = response.headers().to_owned();
match status {
StatusCode::NO_CONTENT => {
let json =
serde_json::from_slice::<RespBody>(&[]).map_err(ParseResponseError::BodyDe)?;
Ok((status, Ok(json), headers))
}
status if status.is_success() => {
let json = serde_json::from_slice::<RespBody>(response.body())
.map_err(ParseResponseError::BodyDe)?;
Ok((status, Ok(json), headers))
}
status => {
let json = serde_json::from_slice::<ErrorResponseBody>(response.body())
.map_err(ParseResponseError::BodyDe)?;
Ok((status, Err(json), headers))
}
}
}
#[derive(Debug)]
pub enum ParseResponseError {
BodyDe(serde_json::Error),
}
impl core::fmt::Display for ParseResponseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for ParseResponseError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_render_request() {
for (path, uri) in &[
("foo", "https://api.linode.com/v4/foo"),
("/foo", "https://api.linode.com/v4/foo"),
("foo/bar", "https://api.linode.com/v4/foo/bar"),
("/foo/bar", "https://api.linode.com/v4/foo/bar"),
("v4/foo", "https://api.linode.com/v4/foo"),
("/v4/foo", "https://api.linode.com/v4/foo"),
("v4/foo/bar", "https://api.linode.com/v4/foo/bar"),
("/v4/foo/bar", "https://api.linode.com/v4/foo/bar"),
] {
match render_request::<(), ()>(Method::GET, path, None, None, None, None) {
Ok(req) => {
assert_eq!(req.uri().to_string(), *uri);
}
Err(err) => panic!("{err}"),
}
}
}
}