romm_api/client/
request.rs1use reqwest::header::HeaderMap;
2use reqwest::Method;
3use serde_json::Value;
4use std::time::Instant;
5
6use crate::endpoints::Endpoint;
7use crate::error::ApiError;
8
9use super::response::{
10 api_error_from_response, decode_json_response_body, read_error_response_text,
11};
12use super::RommClient;
13
14impl RommClient {
15 pub async fn call<E>(&self, ep: &E) -> Result<E::Output, ApiError>
17 where
18 E: Endpoint,
19 E::Output: serde::de::DeserializeOwned,
20 {
21 let method = ep.method();
22 let path = ep.path();
23 let query = ep.query();
24 let body = ep.body();
25
26 let value = self.request_json(method, &path, &query, body).await?;
27 let output = serde_json::from_value(value).map_err(|e| {
28 ApiError::UnexpectedResponse(format!(
29 "failed to decode response for {method} {path}: {e}"
30 ))
31 })?;
32
33 Ok(output)
34 }
35
36 pub async fn request_json(
38 &self,
39 method: &str,
40 path: &str,
41 query: &[(String, String)],
42 body: Option<Value>,
43 ) -> Result<Value, ApiError> {
44 self.request_json_with_headers(method, path, query, body, self.build_headers()?)
45 .await
46 }
47
48 pub async fn request_json_unauthenticated(
49 &self,
50 method: &str,
51 path: &str,
52 query: &[(String, String)],
53 body: Option<Value>,
54 ) -> Result<Value, ApiError> {
55 self.request_json_with_headers(method, path, query, body, HeaderMap::new())
56 .await
57 }
58
59 async fn request_json_with_headers(
60 &self,
61 method: &str,
62 path: &str,
63 query: &[(String, String)],
64 body: Option<Value>,
65 headers: HeaderMap,
66 ) -> Result<Value, ApiError> {
67 let url = format!(
68 "{}/{}",
69 self.base_url.trim_end_matches('/'),
70 path.trim_start_matches('/')
71 );
72
73 let http_method = Method::from_bytes(method.as_bytes())
74 .map_err(|_| ApiError::InvalidMethod(method.to_string()))?;
75
76 let query_refs: Vec<(&str, &str)> = query
77 .iter()
78 .map(|(k, v)| (k.as_str(), v.as_str()))
79 .collect();
80
81 let mut req = self
82 .http
83 .request(http_method, &url)
84 .headers(headers)
85 .query(&query_refs);
86
87 if let Some(body) = body {
88 req = req.json(&body);
89 }
90
91 let t0 = Instant::now();
92 let resp = req.send().await?;
93
94 let status = resp.status();
95 if self.verbose {
96 let keys: Vec<&str> = query.iter().map(|(k, _)| k.as_str()).collect();
97 tracing::info!(
98 "[romm-cli] {} {} query_keys={:?} -> {} ({}ms)",
99 method,
100 path,
101 keys,
102 status.as_u16(),
103 t0.elapsed().as_millis()
104 );
105 }
106 if !status.is_success() {
107 let body = read_error_response_text(resp).await;
108 return Err(api_error_from_response(status, &body));
109 }
110
111 let bytes = resp.bytes().await?;
112 Ok(decode_json_response_body(&bytes))
113 }
114
115 pub async fn get_bytes(
117 &self,
118 path: &str,
119 query: &[(String, String)],
120 ) -> Result<Vec<u8>, ApiError> {
121 let url = format!(
122 "{}/{}",
123 self.base_url.trim_end_matches('/'),
124 path.trim_start_matches('/')
125 );
126 let headers = self.build_headers()?;
127 let query_refs: Vec<(&str, &str)> = query
128 .iter()
129 .map(|(k, v)| (k.as_str(), v.as_str()))
130 .collect();
131 let resp = self
132 .http
133 .get(&url)
134 .headers(headers)
135 .query(&query_refs)
136 .send()
137 .await?;
138 let status = resp.status();
139 if !status.is_success() {
140 let body = read_error_response_text(resp).await;
141 return Err(api_error_from_response(status, &body));
142 }
143 Ok(resp.bytes().await?.to_vec())
144 }
145
146 pub async fn post_bytes(
148 &self,
149 path: &str,
150 query: &[(String, String)],
151 json_body: Option<Value>,
152 ) -> Result<Vec<u8>, ApiError> {
153 let url = format!(
154 "{}/{}",
155 self.base_url.trim_end_matches('/'),
156 path.trim_start_matches('/')
157 );
158 let headers = self.build_headers()?;
159 let query_refs: Vec<(&str, &str)> = query
160 .iter()
161 .map(|(k, v)| (k.as_str(), v.as_str()))
162 .collect();
163 let mut req = self.http.post(&url).headers(headers).query(&query_refs);
164 if let Some(b) = json_body {
165 req = req.json(&b);
166 }
167 let resp = req.send().await?;
168 let status = resp.status();
169 if !status.is_success() {
170 let body = read_error_response_text(resp).await;
171 return Err(api_error_from_response(status, &body));
172 }
173 Ok(resp.bytes().await?.to_vec())
174 }
175}