use crate::config::ClientConfig;
use crate::http::auth::AuthHandler;
use crate::types::errors::{AnthropicError, Result};
use crate::types::shared::RequestId;
use reqwest::{Client, Request, RequestBuilder, Response};
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct HttpClient {
client: Client,
config: ClientConfig,
auth: AuthHandler,
}
impl HttpClient {
pub fn new(config: ClientConfig) -> Result<Self> {
config.validate()?;
let client = Client::builder()
.timeout(config.timeout)
.build()
.map_err(|e| AnthropicError::Connection {
message: e.to_string(),
})?;
let auth = AuthHandler::with_method(config.api_key.clone(), config.auth_method.clone());
Ok(Self {
client,
config,
auth,
})
}
pub async fn send(&self, mut request: Request) -> Result<Response> {
let headers = request.headers_mut();
self.auth.add_auth_headers(headers)?;
let response =
self.client
.execute(request)
.await
.map_err(|e| AnthropicError::Connection {
message: e.to_string(),
})?;
self.handle_response_status(response).await
}
pub fn get(&self, url: &str) -> RequestBuilder {
self.client.get(url)
}
pub fn post(&self, url: &str) -> RequestBuilder {
self.client.post(url)
}
pub fn put(&self, url: &str) -> RequestBuilder {
self.client.put(url)
}
pub fn delete(&self, url: &str) -> RequestBuilder {
self.client.delete(url)
}
pub fn build_url(&self, path: &str) -> String {
format!("{}{}", self.config.base_url.trim_end_matches('/'), path)
}
async fn handle_response_status(&self, response: Response) -> Result<Response> {
let status = response.status();
if status.is_success() {
return Ok(response);
}
let status_code = status.as_u16();
let error_message = match response.text().await {
Ok(body) => {
match serde_json::from_str::<Value>(&body) {
Ok(json) => json
.get("error")
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.unwrap_or(&body)
.to_string(),
Err(_) => body,
}
}
Err(_) => format!(
"HTTP {}: {}",
status_code,
status.canonical_reason().unwrap_or("Unknown")
),
};
Err(AnthropicError::from_status(status_code, error_message))
}
pub fn extract_request_id(&self, response: &Response) -> Option<RequestId> {
response
.headers()
.get("request-id")
.and_then(|value| value.to_str().ok())
.map(|id| RequestId::new(id.to_string()))
}
pub fn base_url(&self) -> &str {
&self.config.base_url
}
pub fn config(&self) -> &ClientConfig {
&self.config
}
pub fn client(&self) -> &Client {
&self.client
}
}