linode_api/endpoints/v4/
common.rs1use 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
11type Body = Vec<u8>;
13
14pub 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#[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
77pub 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#[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}