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