Skip to content

Commit 9e70b69

Browse files
committed
Add X402ClientBuilder fluent API and convenience constructors for HTTP client setup
* Add X402ClientBuilder struct with ownership-taking builder pattern * Implement builder methods: register, register_v1, policy, selector, and hook registration (on_before_payment_creation, on_after_payment_creation, on_payment_creation_failure) * Add X402Client::builder() static constructor returning X402ClientBuilder * Add X402HttpClient::from_client() convenience constructor accepting owned X402Client * Add X402Http
1 parent c1e7956 commit 9e70b69

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

r402-http/src/client.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,38 @@ impl X402HttpClient {
5555
Self { client }
5656
}
5757

58+
/// Creates a new middleware from an owned [`X402Client`].
59+
///
60+
/// Convenience wrapper that wraps the client in an [`Arc`] internally.
61+
#[must_use]
62+
pub fn from_client(client: X402Client) -> Self {
63+
Self {
64+
client: Arc::new(client),
65+
}
66+
}
67+
68+
/// Builds a [`reqwest_middleware::ClientWithMiddleware`] with x402 payment
69+
/// handling from an owned [`X402Client`].
70+
///
71+
/// This is the simplest way to get a payment-capable HTTP client:
72+
///
73+
/// ```ignore
74+
/// use r402::client::X402Client;
75+
/// use r402_http::client::X402HttpClient;
76+
///
77+
/// let http_client = X402HttpClient::build_reqwest(
78+
/// X402Client::builder()
79+
/// .register("eip155:*".into(), Box::new(evm_scheme))
80+
/// .build()
81+
/// );
82+
/// ```
83+
#[must_use]
84+
pub fn build_reqwest(client: X402Client) -> reqwest_middleware::ClientWithMiddleware {
85+
reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
86+
.with(Self::from_client(client))
87+
.build()
88+
}
89+
5890
/// Extracts payment-required info from a 402 response.
5991
///
6092
/// Checks V2 header first, then falls back to V1 body.

r402/src/client.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,99 @@ const fn default_selector(_version: u32, _reqs: &[RequirementsView]) -> usize {
105105
0
106106
}
107107

108+
/// Fluent builder for [`X402Client`].
109+
///
110+
/// Provides an ownership-taking API for chained construction:
111+
///
112+
/// ```ignore
113+
/// let client = X402Client::builder()
114+
/// .register("eip155:*".into(), Box::new(evm_scheme))
115+
/// .policy(prefer_network("eip155:8453".into()))
116+
/// .build();
117+
/// ```
118+
pub struct X402ClientBuilder {
119+
inner: X402Client,
120+
}
121+
122+
impl Default for X402ClientBuilder {
123+
fn default() -> Self {
124+
Self::new()
125+
}
126+
}
127+
128+
impl X402ClientBuilder {
129+
/// Creates a new builder with default settings.
130+
#[must_use]
131+
pub fn new() -> Self {
132+
Self {
133+
inner: X402Client::new(),
134+
}
135+
}
136+
137+
/// Registers a V2 scheme client for a network (ownership-taking).
138+
#[must_use]
139+
pub fn register(mut self, network: Network, client: Box<dyn SchemeClient>) -> Self {
140+
self.inner.register(network, client);
141+
self
142+
}
143+
144+
/// Registers a V1 scheme client for a network (ownership-taking).
145+
#[must_use]
146+
pub fn register_v1(mut self, network: Network, client: Box<dyn SchemeClientV1>) -> Self {
147+
self.inner.register_v1(network, client);
148+
self
149+
}
150+
151+
/// Adds a requirement filter policy (ownership-taking).
152+
#[must_use]
153+
pub fn policy(mut self, policy: PaymentPolicy) -> Self {
154+
self.inner.register_policy(policy);
155+
self
156+
}
157+
158+
/// Sets the requirement selector (ownership-taking).
159+
#[must_use]
160+
pub fn selector(mut self, selector: PaymentRequirementsSelector) -> Self {
161+
self.inner.selector = selector;
162+
self
163+
}
164+
165+
/// Registers a before-payment-creation hook (ownership-taking).
166+
#[must_use]
167+
pub fn on_before_payment_creation(mut self, hook: BeforePaymentCreationHook) -> Self {
168+
self.inner.on_before_payment_creation(hook);
169+
self
170+
}
171+
172+
/// Registers an after-payment-creation hook (ownership-taking).
173+
#[must_use]
174+
pub fn on_after_payment_creation(mut self, hook: AfterPaymentCreationHook) -> Self {
175+
self.inner.on_after_payment_creation(hook);
176+
self
177+
}
178+
179+
/// Registers a failure hook (ownership-taking).
180+
#[must_use]
181+
pub fn on_payment_creation_failure(mut self, hook: OnPaymentCreationFailureHook) -> Self {
182+
self.inner.on_payment_creation_failure(hook);
183+
self
184+
}
185+
186+
/// Consumes the builder and returns the configured [`X402Client`].
187+
#[must_use]
188+
pub fn build(self) -> X402Client {
189+
self.inner
190+
}
191+
}
192+
193+
impl std::fmt::Debug for X402ClientBuilder {
194+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195+
f.debug_struct("X402ClientBuilder")
196+
.field("inner", &self.inner)
197+
.finish()
198+
}
199+
}
200+
108201
/// Async-first x402 client with scheme registration, policies, hooks,
109202
/// and payment creation.
110203
///
@@ -145,6 +238,18 @@ impl Default for X402Client {
145238
}
146239

147240
impl X402Client {
241+
/// Returns a fluent [`X402ClientBuilder`] for chained construction.
242+
///
243+
/// ```ignore
244+
/// let client = X402Client::builder()
245+
/// .register("eip155:*".into(), Box::new(evm_scheme))
246+
/// .build();
247+
/// ```
248+
#[must_use]
249+
pub fn builder() -> X402ClientBuilder {
250+
X402ClientBuilder::new()
251+
}
252+
148253
/// Creates a new client with default selector.
149254
#[must_use]
150255
pub fn new() -> Self {

0 commit comments

Comments
 (0)