1pub use async_trait::async_trait;
3
4use reqwest::{
5 header::{self, HeaderMap, HeaderValue},
6 tls::CertificateRevocationList,
7 Certificate, Client, Identity, Response,
8};
9pub use reqwest::{Error, StatusCode};
10
11use std::ops::Deref;
12
13use crate::{
14 api_objects::{SclEvent, SclObject},
15 tls_config::TlsConfig,
16};
17
18use crate::LogError;
19
20pub mod error {
22 pub type Result<T> = std::result::Result<T, ClientError>;
24
25 #[derive(Debug)]
27 pub enum ClientError {
28 Custom(String),
30 Http(reqwest::Error),
32 Decode(serde_json::Error),
34 }
35
36 impl std::fmt::Display for ClientError {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 use ClientError::*;
39 match self {
40 Custom(e) => e.fmt(f),
41 Http(e) => e.fmt(f),
42 Decode(e) => e.fmt(f),
43 }
44 }
45 }
46
47 impl std::error::Error for ClientError {}
48
49 impl From<reqwest::Error> for ClientError {
50 fn from(e: reqwest::Error) -> Self {
51 Self::Http(e)
52 }
53 }
54
55 impl From<serde_json::Error> for ClientError {
56 fn from(e: serde_json::Error) -> Self {
57 Self::Decode(e)
58 }
59 }
60}
61
62#[async_trait]
65pub trait EventHandler {
66 type Item: SclObject;
67 async fn handle_event(&mut self, event: SclEvent<Self::Item>);
68}
69
70#[derive(Clone, Debug, Default)]
86pub struct SclRequest<'a, T: SclObject> {
87 pub obj_name: Option<String>,
88 pub sc_name: Option<String>,
89 pub body: Option<&'a T>,
90 pub query: &'a [(&'a str, &'a str)],
91}
92
93impl<'a, T: SclObject> SclRequest<'a, T> {
94 pub fn new() -> Self {
96 Self {
97 obj_name: None,
98 body: None,
99 sc_name: None,
100 query: &[],
101 }
102 }
103
104 pub fn obj_name(mut self, name: &str) -> Self {
106 self.obj_name = Some(name.into());
107 self
108 }
109
110 pub fn body(mut self, obj: &'a T) -> Self {
112 self.body = Some(obj);
113 self
114 }
115
116 pub fn sc(mut self, sc_name: &str) -> Self {
119 self.sc_name = Some(sc_name.into());
120 self
121 }
122
123 pub fn query(mut self, query: &'a [(&'a str, &'a str)]) -> Self {
125 self.query = query;
126 self
127 }
128
129 pub fn url_path(&self) -> String {
131 T::api_endpoint(self.sc_name.as_deref(), self.obj_name.as_deref())
132 }
133}
134
135#[derive(Clone)]
176pub struct SclClient {
177 pub api_url: String,
178 client: Client,
179}
180
181impl SclClient {
182 pub fn new(api_url: String, client: Client) -> Self {
184 Self { api_url, client }
185 }
186
187 pub async fn list<T: SclObject>(&self, req: &SclRequest<'_, T>) -> Result<Vec<T>, Error> {
190 let url = format!("{}{}", self.api_url, req.url_path());
191 self.client
192 .get(url)
193 .query(req.query)
194 .send()
195 .await
196 .log_err()?
197 .error_for_status()
198 .log_err()?
199 .json::<Vec<T>>()
200 .await
201 }
202
203 pub async fn show<T: SclObject>(&self, req: &SclRequest<'_, T>) -> Result<T, Error> {
205 let url = format!("{}{}", self.api_url, req.url_path());
206 self.client
207 .get(url)
208 .send()
209 .await
210 .log_err()?
211 .error_for_status()
212 .log_err()?
213 .json::<T>()
214 .await
215 }
216
217 pub async fn delete<T: SclObject>(&self, req: &SclRequest<'_, T>) -> Result<(), Error> {
219 let url = format!("{}{}", self.api_url, req.url_path());
220 self.client
221 .delete(url)
222 .send()
223 .await
224 .log_err()?
225 .error_for_status()
226 .log_err()?;
227 Ok(())
228 }
229
230 pub async fn create<T: SclObject>(&self, req: &SclRequest<'_, T>) -> Result<(), Error> {
232 let url = format!("{}{}", self.api_url, req.url_path());
233 match req.body {
234 Some(body) => self.client.post(url).json(body),
235 None => self.client.post(url),
236 }
237 .send()
238 .await
239 .log_err()?
240 .error_for_status()
241 .log_err()?;
242 Ok(())
243 }
244
245 pub async fn update<T: SclObject>(&self, req: &SclRequest<'_, T>) -> Result<(), Error> {
247 let url = format!("{}{}", self.api_url, req.url_path());
248 match req.body {
249 Some(body) => self.client.put(url).json(body),
250 None => self.client.put(url),
251 }
252 .send()
253 .await
254 .log_err()?
255 .error_for_status()
256 .log_err()?;
257 Ok(())
258 }
259
260 pub async fn watch<T: EventHandler>(&self, mut handler: T) -> Result<(), reqwest::Error> {
268 let url = format!(
269 "{}/watch{}",
270 self.api_url,
271 T::Item::api_endpoint(None, None)
272 );
273 let mut resp = self.client.get(url).send().await?.error_for_status()?;
274 while let Some(bytes) = resp.chunk().await.log_err()? {
275 match serde_json::from_slice::<SclEvent<T::Item>>(&bytes) {
276 Ok(event) => handler.handle_event(event).await,
277 Err(err) => log::error!("{}", err),
278 };
279 }
280 Ok(())
281 }
282
283 pub async fn recv_event<T: SclObject>(resp: &mut Response) -> error::Result<SclEvent<T>> {
287 if let Some(bytes) = resp.chunk().await.log_err()? {
288 Ok(serde_json::from_slice::<SclEvent<T>>(&bytes)?)
289 } else {
290 Err(error::ClientError::Custom(
291 "Empty Chunk: Response body has been exhausted.".to_string(),
292 ))
293 }
294 }
295}
296
297impl Deref for SclClient {
298 type Target = Client;
299
300 fn deref(&self) -> &Self::Target {
301 &self.client
302 }
303}
304
305pub fn reqwest_client(
306 additional_headers: Option<HeaderMap<HeaderValue>>,
307 tls: Option<&TlsConfig>,
308) -> Result<reqwest::Client, Box<dyn std::error::Error>> {
309 let mut headers = HeaderMap::new();
310 headers.insert(header::ACCEPT, HeaderValue::from_static("application/json"));
312 headers.insert(
313 header::CONTENT_TYPE,
314 HeaderValue::from_static("application/json"),
315 );
316 if let Some(additional_headers) = additional_headers {
318 for (key, value) in additional_headers.iter() {
319 headers.insert(key, value.clone());
320 }
321 }
322
323 let mut client_builder = Client::builder().default_headers(headers);
324
325 if let Some(tls) = tls {
326 let ca_certs = Certificate::from_pem_bundle(&tls.ca_cert)?;
327 let mut id_buf = Vec::new();
328 id_buf.extend(&tls.client_cert);
329 id_buf.extend(&tls.client_key);
330 let identity = Identity::from_pem(&id_buf)?;
331 let crls = CertificateRevocationList::from_pem_bundle(&tls.crl)?;
332
333 client_builder = client_builder
334 .tls_backend_rustls()
335 .tls_certs_only(ca_certs)
336 .identity(identity)
337 .tls_crls_only(crls);
338 } else {
339 #[cfg(test)]
340 {
341 client_builder = client_builder.tls_danger_accept_invalid_certs(true);
343 }
344 }
345
346 Ok(client_builder.build()?)
347}