linode_api/endpoints/v4/
common.rs

1use http_api_client::http::{
2    self,
3    header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, USER_AGENT},
4    HeaderMap, Method, Request, Response, StatusCode,
5};
6use serde::{de::DeserializeOwned, ser::Serialize};
7use url::Url;
8
9use crate::endpoints::v4::{AccessToken, ErrorResponseBody, BASE_URL};
10
11//
12type Body = Vec<u8>;
13
14//
15pub fn render_request<ReqQuery, ReqBody>(
16    method: Method,
17    path: &str,
18    query: Option<&ReqQuery>,
19    body: Option<&ReqBody>,
20    access_token: Option<&AccessToken>,
21    base_url: Option<&Url>,
22) -> Result<Request<Body>, RenderRequestError>
23where
24    ReqQuery: Serialize,
25    ReqBody: Serialize,
26{
27    let path = path.strip_prefix('/').unwrap_or(path);
28    let path = path.strip_prefix("v4/").unwrap_or(path);
29    let base_url = base_url.unwrap_or(&BASE_URL);
30
31    let request = Request::builder().method(method);
32
33    let mut url = base_url.join(path).map_err(RenderRequestError::UrlJoin)?;
34
35    if let Some(query) = query {
36        let query = serde_qs::to_string(query).map_err(RenderRequestError::QuerySer)?;
37        url.set_query(Some(&query));
38    }
39    let request = request.uri(url.as_str());
40
41    let request = if let Some(access_token) = access_token {
42        request.header(AUTHORIZATION, format!("Bearer {}", access_token))
43    } else {
44        request
45    };
46
47    let request = request
48        .header(ACCEPT, "application/json")
49        .header(USER_AGENT, "linode-api");
50
51    let request = if let Some(body) = body {
52        let body = serde_json::to_vec(body).map_err(RenderRequestError::BodySer)?;
53        request.header(CONTENT_TYPE, "application/json").body(body)
54    } else {
55        request.body(vec![])
56    };
57    let request = request.map_err(RenderRequestError::RequestBuild)?;
58
59    Ok(request)
60}
61
62//
63#[derive(Debug)]
64pub enum RenderRequestError {
65    UrlJoin(url::ParseError),
66    QuerySer(serde_qs::Error),
67    BodySer(serde_json::Error),
68    RequestBuild(http::Error),
69}
70impl core::fmt::Display for RenderRequestError {
71    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72        write!(f, "{self:?}")
73    }
74}
75impl std::error::Error for RenderRequestError {}
76
77//
78pub fn parse_response<RespBody>(
79    response: Response<Body>,
80) -> Result<(StatusCode, Result<RespBody, ErrorResponseBody>, HeaderMap), ParseResponseError>
81where
82    RespBody: DeserializeOwned,
83{
84    let status = response.status();
85    let headers = response.headers().to_owned();
86
87    match status {
88        StatusCode::NO_CONTENT => {
89            let json =
90                serde_json::from_slice::<RespBody>(&[]).map_err(ParseResponseError::BodyDe)?;
91            Ok((status, Ok(json), headers))
92        }
93        status if status.is_success() => {
94            let json = serde_json::from_slice::<RespBody>(response.body())
95                .map_err(ParseResponseError::BodyDe)?;
96            Ok((status, Ok(json), headers))
97        }
98        status => {
99            let json = serde_json::from_slice::<ErrorResponseBody>(response.body())
100                .map_err(ParseResponseError::BodyDe)?;
101            Ok((status, Err(json), headers))
102        }
103    }
104}
105
106//
107#[derive(Debug)]
108pub enum ParseResponseError {
109    BodyDe(serde_json::Error),
110}
111impl core::fmt::Display for ParseResponseError {
112    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113        write!(f, "{self:?}")
114    }
115}
116impl std::error::Error for ParseResponseError {}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_render_request() {
124        for (path, uri) in &[
125            ("foo", "https://api.linode.com/v4/foo"),
126            ("/foo", "https://api.linode.com/v4/foo"),
127            ("foo/bar", "https://api.linode.com/v4/foo/bar"),
128            ("/foo/bar", "https://api.linode.com/v4/foo/bar"),
129            ("v4/foo", "https://api.linode.com/v4/foo"),
130            ("/v4/foo", "https://api.linode.com/v4/foo"),
131            ("v4/foo/bar", "https://api.linode.com/v4/foo/bar"),
132            ("/v4/foo/bar", "https://api.linode.com/v4/foo/bar"),
133        ] {
134            match render_request::<(), ()>(Method::GET, path, None, None, None, None) {
135                Ok(req) => {
136                    assert_eq!(req.uri().to_string(), *uri);
137                }
138                Err(err) => panic!("{err}"),
139            }
140        }
141    }
142}