1use backon::{ExponentialBuilder, Retryable};
2use serde::{Serialize, de::DeserializeOwned};
3
4use crate::{config::Config, error::ExaError, retry};
5
6#[derive(Debug, Clone)]
11pub struct Client<C: Config> {
12 http: reqwest::Client,
13 config: C,
14 backoff: ExponentialBuilder,
15}
16
17impl Client<crate::config::ExaConfig> {
18 #[must_use]
24 pub fn new() -> Self {
25 Self::with_config(crate::config::ExaConfig::new())
26 }
27}
28
29impl<C: Config + Default> Default for Client<C> {
30 fn default() -> Self {
31 Self::with_config(C::default())
32 }
33}
34
35impl<C: Config> Client<C> {
36 #[must_use]
42 pub fn with_config(config: C) -> Self {
43 Self {
44 http: reqwest::Client::builder()
45 .connect_timeout(std::time::Duration::from_secs(5))
46 .timeout(std::time::Duration::from_secs(60))
47 .build()
48 .expect("reqwest client"),
49 config,
50 backoff: retry::default_backoff_builder(),
51 }
52 }
53
54 #[must_use]
56 pub fn with_http_client(mut self, http: reqwest::Client) -> Self {
57 self.http = http;
58 self
59 }
60
61 #[must_use]
63 pub const fn with_backoff(mut self, backoff: ExponentialBuilder) -> Self {
64 self.backoff = backoff;
65 self
66 }
67
68 #[must_use]
70 pub const fn config(&self) -> &C {
71 &self.config
72 }
73
74 pub(crate) async fn post<I, O>(&self, path: &str, body: I) -> Result<O, ExaError>
75 where
76 I: Serialize + Send + Sync,
77 O: DeserializeOwned,
78 {
79 let mk = || async {
80 let headers = self.config.headers()?;
81 Ok(self
82 .http
83 .post(self.config.url(path))
84 .headers(headers)
85 .query(&self.config.query())
86 .json(&body)
87 .build()?)
88 };
89 self.execute(mk).await
90 }
91
92 async fn execute<O, M, Fut>(&self, mk: M) -> Result<O, ExaError>
93 where
94 O: DeserializeOwned,
95 M: Fn() -> Fut + Send + Sync,
96 Fut: core::future::Future<Output = Result<reqwest::Request, ExaError>> + Send,
97 {
98 self.config.validate_auth()?;
100
101 let bytes = self.execute_raw(mk).await?;
102 let resp: O =
103 serde_json::from_slice(&bytes).map_err(|e| crate::error::map_deser(&e, &bytes))?;
104 Ok(resp)
105 }
106
107 async fn execute_raw<M, Fut>(&self, mk: M) -> Result<bytes::Bytes, ExaError>
108 where
109 M: Fn() -> Fut + Send + Sync,
110 Fut: core::future::Future<Output = Result<reqwest::Request, ExaError>> + Send,
111 {
112 let http_client = self.http.clone();
113
114 (|| async {
115 let request = mk().await?;
116 let response = http_client
117 .execute(request)
118 .await
119 .map_err(ExaError::Reqwest)?;
120
121 let status = response.status();
122 let bytes = response.bytes().await.map_err(ExaError::Reqwest)?;
123
124 if status.is_success() {
125 return Ok(bytes);
126 }
127
128 Err(crate::error::deserialize_api_error(status, &bytes))
129 })
130 .retry(self.backoff)
131 .when(ExaError::is_retryable)
132 .await
133 }
134}