anthropic_async/
client.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::{config::Config, error::AnthropicError, retry};
4
5/// Anthropic API client
6///
7/// The client is generic over a [`Config`] implementation that provides authentication
8/// and API configuration.
9#[derive(Debug, Clone)]
10pub struct Client<C: Config> {
11    http: reqwest::Client,
12    config: C,
13    backoff: backoff::ExponentialBackoff,
14}
15
16impl Client<crate::config::AnthropicConfig> {
17    /// Creates a new client with default configuration
18    ///
19    /// Uses environment variables for authentication:
20    /// - `ANTHROPIC_API_KEY` for API key authentication
21    /// - `ANTHROPIC_AUTH_TOKEN` for bearer token authentication
22    #[must_use]
23    pub fn new() -> Self {
24        Self::with_config(crate::config::AnthropicConfig::new())
25    }
26}
27
28impl<C: Config + Default> Default for Client<C> {
29    fn default() -> Self {
30        Self::with_config(C::default())
31    }
32}
33
34impl<C: Config> Client<C> {
35    /// Creates a new client with the given configuration.
36    ///
37    /// # Panics
38    ///
39    /// Panics if the reqwest client cannot be built.
40    #[must_use]
41    pub fn with_config(config: C) -> Self {
42        Self {
43            http: reqwest::Client::builder()
44                .connect_timeout(std::time::Duration::from_secs(5))
45                .timeout(std::time::Duration::from_secs(600))
46                .build()
47                .expect("reqwest client"),
48            config,
49            backoff: retry::default_backoff(),
50        }
51    }
52
53    /// Replaces the HTTP client with a custom one
54    ///
55    /// Useful for setting custom timeouts, proxies, or other HTTP configuration.
56    #[must_use]
57    pub fn with_http_client(mut self, http: reqwest::Client) -> Self {
58        self.http = http;
59        self
60    }
61
62    /// Replaces the backoff configuration for retry logic
63    ///
64    /// By default, the client uses exponential backoff with jitter.
65    #[must_use]
66    pub const fn with_backoff(mut self, backoff: backoff::ExponentialBackoff) -> Self {
67        self.backoff = backoff;
68        self
69    }
70
71    /// Returns a reference to the client's configuration
72    #[must_use]
73    pub const fn config(&self) -> &C {
74        &self.config
75    }
76
77    pub(crate) async fn get<O: DeserializeOwned>(&self, path: &str) -> Result<O, AnthropicError> {
78        let mk = || async {
79            let headers = self.config.headers()?;
80            Ok(self
81                .http
82                .get(self.config.url(path))
83                .headers(headers)
84                .query(&self.config.query())
85                .build()?)
86        };
87        self.execute(mk).await
88    }
89
90    pub(crate) async fn get_with_query<Q, O>(
91        &self,
92        path: &str,
93        query: &Q,
94    ) -> Result<O, AnthropicError>
95    where
96        Q: Serialize + Sync + ?Sized,
97        O: DeserializeOwned,
98    {
99        let mk = || async {
100            let headers = self.config.headers()?;
101            Ok(self
102                .http
103                .get(self.config.url(path))
104                .headers(headers)
105                .query(&self.config.query())
106                .query(query)
107                .build()?)
108        };
109        self.execute(mk).await
110    }
111
112    pub(crate) async fn post<I, O>(&self, path: &str, body: I) -> Result<O, AnthropicError>
113    where
114        I: Serialize + Send + Sync,
115        O: DeserializeOwned,
116    {
117        let mk = || async {
118            let headers = self.config.headers()?;
119            Ok(self
120                .http
121                .post(self.config.url(path))
122                .headers(headers)
123                .query(&self.config.query())
124                .json(&body)
125                .build()?)
126        };
127        self.execute(mk).await
128    }
129
130    /// Sends a POST request and returns the raw response for streaming.
131    ///
132    /// This method does not retry on error, as streaming responses cannot be replayed.
133    #[cfg(feature = "streaming")]
134    pub(crate) async fn post_stream<I: Serialize + Send + Sync>(
135        &self,
136        path: &str,
137        body: I,
138    ) -> Result<reqwest::Response, AnthropicError> {
139        // Validate auth before any request
140        self.config.validate_auth()?;
141
142        let headers = self.config.headers()?;
143        let request = self
144            .http
145            .post(self.config.url(path))
146            .headers(headers)
147            .query(&self.config.query())
148            .json(&body)
149            .build()?;
150
151        let response = self
152            .http
153            .execute(request)
154            .await
155            .map_err(AnthropicError::Reqwest)?;
156
157        let status = response.status();
158        if status.is_success() {
159            Ok(response)
160        } else {
161            let bytes = response.bytes().await.map_err(AnthropicError::Reqwest)?;
162            Err(crate::error::deserialize_api_error(status, &bytes))
163        }
164    }
165
166    async fn execute<O, M, Fut>(&self, mk: M) -> Result<O, AnthropicError>
167    where
168        O: DeserializeOwned,
169        M: Fn() -> Fut + Send + Sync,
170        Fut: core::future::Future<Output = Result<reqwest::Request, AnthropicError>> + Send,
171    {
172        // Validate auth before any request
173        self.config.validate_auth()?;
174
175        let bytes = self.execute_raw(mk).await?;
176        let resp: O =
177            serde_json::from_slice(&bytes).map_err(|e| crate::error::map_deser(&e, &bytes))?;
178        Ok(resp)
179    }
180
181    async fn execute_raw<M, Fut>(&self, mk: M) -> Result<bytes::Bytes, AnthropicError>
182    where
183        M: Fn() -> Fut + Send + Sync,
184        Fut: core::future::Future<Output = Result<reqwest::Request, AnthropicError>> + Send,
185    {
186        let http_client = self.http.clone();
187
188        backoff::future::retry(self.backoff.clone(), || async {
189            let request = mk().await.map_err(backoff::Error::Permanent)?;
190            let response = http_client
191                .execute(request)
192                .await
193                .map_err(AnthropicError::Reqwest)
194                .map_err(backoff::Error::Permanent)?;
195
196            let status = response.status();
197            let headers = response.headers().clone();
198            let bytes = response
199                .bytes()
200                .await
201                .map_err(AnthropicError::Reqwest)
202                .map_err(backoff::Error::Permanent)?;
203
204            if status.is_success() {
205                return Ok(bytes);
206            }
207
208            if crate::retry::is_retryable_status(status.as_u16()) {
209                let err = crate::error::deserialize_api_error(status, &bytes);
210                if let Some(retry_after) = crate::retry::parse_retry_after(&headers) {
211                    return Err(backoff::Error::retry_after(err, retry_after));
212                }
213                return Err(backoff::Error::transient(err));
214            }
215
216            Err(backoff::Error::Permanent(
217                crate::error::deserialize_api_error(status, &bytes),
218            ))
219        })
220        .await
221    }
222}