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