Skip to main content

base_sensible_wrapper/
client.rs

1use super::interface::ApiInterface;
2use anyhow::{Context, Result};
3use reqwest::{
4    Method,
5    header::{HeaderName, HeaderValue},
6};
7use reqwest_middleware::ClientWithMiddleware;
8use serde::{Serialize, de::DeserializeOwned};
9use std::collections::HashMap;
10use std::fmt::Display;
11
12/// The wrapper around an api definition that handles building and making requests correctly.
13/// Internally stores a copy of the current reqwest client, the global config, and the
14/// api interface to use. Can be safely sent across threads thanks to reqwest's internal
15/// architecture. Note that interfaces impl Send + Sync by default as well.
16pub struct ApiClient<T: ApiInterface> {
17    pub(super) interface: T,
18    pub(super) http_client: ClientWithMiddleware,
19}
20unsafe impl<T: ApiInterface> Send for ApiClient<T> {}
21unsafe impl<T: ApiInterface> Sync for ApiClient<T> {}
22
23impl<T: ApiInterface> ApiClient<T> {
24    /// This member builds and executes a new request based on params passed in. It will call
25    /// the underlying interface for the base api url and authentication methods, and collect
26    /// all relevant info before executing. Return type is determined by the callee.
27    pub async fn request<
28        R: DeserializeOwned,
29        E: Display,
30        HK: Into<HeaderName>,
31        HV: Into<HeaderValue>,
32        P: Serialize,
33        B: Serialize,
34    >(
35        &self,
36        endpoint: E,
37        method: Method,
38        headers: HashMap<HK, HV>,
39        params: Option<P>,
40        body: Option<B>,
41    ) -> Result<R> {
42        let request = self.interface.build_request(
43            self.http_client.clone(),
44            endpoint,
45            method,
46            headers,
47            params,
48            body,
49        )?;
50        trace!("Sending request for json response: {request:?}");
51
52        self.http_client
53            .execute(request)
54            .await
55            .context("(JSON) Cannot send request")?
56            .json()
57            .await
58            .context("(JSON) Cannot decode request")
59    }
60
61    /// A debug version of `Self::request` that returns the body as text instead of deserializing
62    /// it. Useful if there is an issue with the call
63    pub async fn string_request<
64        E: Display,
65        HK: Into<HeaderName>,
66        HV: Into<HeaderValue>,
67        P: Serialize,
68        B: Serialize,
69    >(
70        &self,
71        endpoint: E,
72        method: Method,
73        headers: HashMap<HK, HV>,
74        params: Option<P>,
75        body: Option<B>,
76    ) -> Result<String> {
77        let request = self.interface.build_request(
78            self.http_client.clone(),
79            endpoint,
80            method,
81            headers,
82            params,
83            body,
84        )?;
85        trace!("Sending request for text response: {request:?}");
86
87        let response = self
88            .http_client
89            .execute(request)
90            .await
91            .context("(TEXT) Cannot send request")?;
92
93        trace!("Response for text request: {response:?}");
94
95        response
96            .text()
97            .await
98            .context("(TEXT) Cannot decode request")
99    }
100
101    /// A simple get method. Auto-sets specific params.
102    pub async fn simple_get<R: DeserializeOwned, E: Display>(&self, endpoint: E) -> Result<R> {
103        self.request(
104            endpoint,
105            Method::GET,
106            HashMap::<HeaderName, HeaderValue>::new(),
107            None::<()>,
108            None::<()>,
109        )
110        .await
111    }
112    /// A simple get method that allows also sending query params
113    pub async fn get_params<R: DeserializeOwned, E: Display, P: Serialize>(
114        &self,
115        endpoint: E,
116        params: P,
117    ) -> Result<R> {
118        self.request(
119            endpoint,
120            Method::GET,
121            HashMap::<HeaderName, HeaderValue>::new(),
122            Some(params),
123            None::<()>,
124        )
125        .await
126    }
127
128    /// A simple post method that requires body
129    pub async fn simple_post<R: DeserializeOwned, E: Display, B: Serialize>(
130        &self,
131        endpoint: E,
132        body: B,
133    ) -> Result<R> {
134        self.request(
135            endpoint,
136            Method::POST,
137            HashMap::<HeaderName, HeaderValue>::new(),
138            None::<()>,
139            Some(body),
140        )
141        .await
142    }
143    /// A simple post method that requires body and allows optional query params
144    pub async fn post_params<R: DeserializeOwned, E: Display, P: Serialize, B: Serialize>(
145        &self,
146        endpoint: E,
147        params: P,
148        body: B,
149    ) -> Result<R> {
150        self.request(
151            endpoint,
152            Method::POST,
153            HashMap::<HeaderName, HeaderValue>::new(),
154            Some(params),
155            Some(body),
156        )
157        .await
158    }
159}