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    async fn execute<O, M, Fut>(&self, mk: M) -> Result<O, AnthropicError>
131    where
132        O: DeserializeOwned,
133        M: Fn() -> Fut + Send + Sync,
134        Fut: core::future::Future<Output = Result<reqwest::Request, AnthropicError>> + Send,
135    {
136        // Validate auth before any request
137        self.config.validate_auth()?;
138
139        let bytes = self.execute_raw(mk).await?;
140        let resp: O =
141            serde_json::from_slice(&bytes).map_err(|e| crate::error::map_deser(&e, &bytes))?;
142        Ok(resp)
143    }
144
145    async fn execute_raw<M, Fut>(&self, mk: M) -> Result<bytes::Bytes, AnthropicError>
146    where
147        M: Fn() -> Fut + Send + Sync,
148        Fut: core::future::Future<Output = Result<reqwest::Request, AnthropicError>> + Send,
149    {
150        let http_client = self.http.clone();
151
152        backoff::future::retry(self.backoff.clone(), || async {
153            let request = mk().await.map_err(backoff::Error::Permanent)?;
154            let response = http_client
155                .execute(request)
156                .await
157                .map_err(AnthropicError::Reqwest)
158                .map_err(backoff::Error::Permanent)?;
159
160            let status = response.status();
161            let headers = response.headers().clone();
162            let bytes = response
163                .bytes()
164                .await
165                .map_err(AnthropicError::Reqwest)
166                .map_err(backoff::Error::Permanent)?;
167
168            if status.is_success() {
169                return Ok(bytes);
170            }
171
172            if crate::retry::is_retryable_status(status.as_u16()) {
173                let err = crate::error::deserialize_api_error(status, &bytes);
174                if let Some(retry_after) = crate::retry::parse_retry_after(&headers) {
175                    return Err(backoff::Error::retry_after(err, retry_after));
176                }
177                return Err(backoff::Error::transient(err));
178            }
179
180            Err(backoff::Error::Permanent(
181                crate::error::deserialize_api_error(status, &bytes),
182            ))
183        })
184        .await
185    }
186}