pincer_core/client.rs
1//! HTTP client traits.
2//!
3//! - [`HttpClient`] - Low-level HTTP execution
4//! - [`PincerClient`] - High-level client with base URL (for `#[pincer]` macro)
5//!
6//! Most users should use the `#[pincer]` macro which generates clients automatically.
7//! Implement [`PincerClient`] directly for custom auth or testing.
8
9use std::future::Future;
10
11use bytes::Bytes;
12use url::Url;
13
14use crate::{Request, Response, Result};
15
16/// Core HTTP client trait.
17///
18/// This trait defines the interface for executing HTTP requests.
19/// Implementations should be async-first and support connection pooling.
20pub trait HttpClient: Send + Sync {
21 /// Execute an HTTP request and return the response.
22 ///
23 /// # Errors
24 ///
25 /// Returns an error if the request fails for any reason:
26 /// - Network errors
27 /// - TLS errors
28 /// - Timeouts
29 /// - Invalid response
30 fn execute(
31 &self,
32 request: Request<Bytes>,
33 ) -> impl Future<Output = Result<Response<Bytes>>> + Send;
34}
35
36/// Extension trait for [`HttpClient`] with convenience methods.
37pub trait HttpClientExt: HttpClient {
38 /// Execute a GET request.
39 ///
40 /// # Errors
41 ///
42 /// Returns an error if the request fails.
43 fn get(&self, url: &str) -> impl Future<Output = Result<Response<Bytes>>> + Send {
44 async move {
45 let url = url::Url::parse(url)?;
46 let request = Request::builder(crate::Method::Get, url).build();
47 self.execute(request).await
48 }
49 }
50
51 /// Execute a POST request with a JSON body.
52 ///
53 /// # Errors
54 ///
55 /// Returns an error if serialization or the request fails.
56 fn post_json<T: serde::Serialize + Send + Sync>(
57 &self,
58 url: &str,
59 body: &T,
60 ) -> impl Future<Output = Result<Response<Bytes>>> + Send {
61 async move {
62 let url = url::Url::parse(url)?;
63 let request = Request::builder(crate::Method::Post, url)
64 .json(body)?
65 .build();
66 self.execute(request).await
67 }
68 }
69
70 /// Execute a PUT request with a JSON body.
71 ///
72 /// # Errors
73 ///
74 /// Returns an error if serialization or the request fails.
75 fn put_json<T: serde::Serialize + Send + Sync>(
76 &self,
77 url: &str,
78 body: &T,
79 ) -> impl Future<Output = Result<Response<Bytes>>> + Send {
80 async move {
81 let url = url::Url::parse(url)?;
82 let request = Request::builder(crate::Method::Put, url)
83 .json(body)?
84 .build();
85 self.execute(request).await
86 }
87 }
88
89 /// Execute a DELETE request.
90 ///
91 /// # Errors
92 ///
93 /// Returns an error if the request fails.
94 fn delete(&self, url: &str) -> impl Future<Output = Result<Response<Bytes>>> + Send {
95 async move {
96 let url = url::Url::parse(url)?;
97 let request = Request::builder(crate::Method::Delete, url).build();
98 self.execute(request).await
99 }
100 }
101}
102
103// Blanket implementation for all HttpClient implementors
104impl<T: HttpClient> HttpClientExt for T {}
105
106// ============================================================================
107// Pincer Client Trait
108// ============================================================================
109
110/// Trait for types that can be used as pincer API clients.
111///
112/// This trait combines HTTP execution capability with a base URL, enabling
113/// the `#[pincer]` macro to generate implementations for any compatible type.
114///
115/// # Implementing `PincerClient`
116///
117/// You can implement this trait for your own types to:
118/// - Add custom request interceptors (e.g., authentication headers)
119/// - Create mock clients for testing
120/// - Wrap existing HTTP clients with additional functionality
121///
122/// # Example
123///
124/// ```ignore
125/// use pincer::{PincerClient, Request, Response, Result};
126/// use bytes::Bytes;
127/// use url::Url;
128///
129/// #[derive(Clone)]
130/// struct AuthenticatedClient {
131/// inner: HyperClient,
132/// base_url: Url,
133/// token: String,
134/// }
135///
136/// impl PincerClient for AuthenticatedClient {
137/// fn execute(
138/// &self,
139/// request: Request<Bytes>,
140/// ) -> impl Future<Output = Result<Response<Bytes>>> + Send {
141/// let token = self.token.clone();
142/// let inner = self.inner.clone();
143/// async move {
144/// // Inject auth header
145/// let (method, url, mut headers, body) = request.into_parts();
146/// headers.insert("Authorization".to_string(), format!("Bearer {}", token));
147/// let request = Request::from_parts(method, url, headers, body);
148/// inner.execute(request).await
149/// }
150/// }
151///
152/// fn base_url(&self) -> &Url {
153/// &self.base_url
154/// }
155/// }
156/// ```
157pub trait PincerClient: Clone + Send + Sync {
158 /// Execute an HTTP request and return the response.
159 ///
160 /// # Errors
161 ///
162 /// Returns an error if the request fails for any reason:
163 /// - Network errors
164 /// - TLS errors
165 /// - Timeouts
166 /// - Invalid response
167 fn execute(
168 &self,
169 request: Request<Bytes>,
170 ) -> impl Future<Output = Result<Response<Bytes>>> + Send;
171
172 /// Get the base URL for this client.
173 ///
174 /// All API paths will be resolved relative to this URL.
175 fn base_url(&self) -> &Url;
176}
177
178// ============================================================================
179// Streaming Client Trait (feature-gated)
180// ============================================================================
181
182/// Streaming HTTP client trait.
183///
184/// This trait extends [`HttpClient`] to support streaming responses.
185/// Enable with the `streaming` feature flag.
186#[cfg(feature = "streaming")]
187pub trait HttpClientStreaming: HttpClient {
188 /// Execute an HTTP request and return a streaming response.
189 ///
190 /// Unlike [`HttpClient::execute`], this method returns a response with
191 /// a streaming body that yields chunks as they arrive from the server.
192 ///
193 /// # Errors
194 ///
195 /// Returns an error if the request fails for any reason:
196 /// - Network errors
197 /// - TLS errors
198 /// - Timeouts
199 /// - Invalid response
200 fn execute_streaming(
201 &self,
202 request: Request<Bytes>,
203 ) -> impl Future<Output = Result<crate::response::streaming::StreamingResponse>> + Send;
204}