1use std::convert::Infallible;
28use std::future::Future;
29use std::pin::Pin;
30use std::sync::Arc;
31use std::task::{Context, Poll};
32use std::time::Duration;
33
34use axum_core::extract::Request;
35use axum_core::response::Response;
36use http::{HeaderMap, Uri};
37use r402::facilitator::Facilitator;
38use r402::proto::v2;
39use tower::util::BoxCloneSyncService;
40use tower::{Layer, Service};
41use url::Url;
42
43use super::facilitator::FacilitatorClient;
44use super::paygate::{Paygate, ResourceInfoBuilder};
45use super::pricing::{DynamicPriceTags, PriceTagSource, StaticPriceTags};
46
47pub struct X402Middleware<F> {
52 facilitator: F,
53 base_url: Option<Url>,
54 settle_before_execution: bool,
55}
56
57impl<F: Clone> Clone for X402Middleware<F> {
58 fn clone(&self) -> Self {
59 Self {
60 facilitator: self.facilitator.clone(),
61 base_url: self.base_url.clone(),
62 settle_before_execution: self.settle_before_execution,
63 }
64 }
65}
66
67impl<F: std::fmt::Debug> std::fmt::Debug for X402Middleware<F> {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.debug_struct("X402Middleware")
70 .field("facilitator", &self.facilitator)
71 .field("base_url", &self.base_url)
72 .field("settle_before_execution", &self.settle_before_execution)
73 .finish()
74 }
75}
76
77impl<F> X402Middleware<F> {
78 pub const fn facilitator(&self) -> &F {
80 &self.facilitator
81 }
82}
83
84impl X402Middleware<Arc<FacilitatorClient>> {
85 #[must_use]
91 pub fn new(url: &str) -> Self {
92 let facilitator = FacilitatorClient::try_from(url).expect("Invalid facilitator URL");
93 Self {
94 facilitator: Arc::new(facilitator),
95 base_url: None,
96 settle_before_execution: false,
97 }
98 }
99
100 pub fn try_new(url: &str) -> Result<Self, Box<dyn std::error::Error>> {
106 let facilitator = FacilitatorClient::try_from(url)?;
107 Ok(Self {
108 facilitator: Arc::new(facilitator),
109 base_url: None,
110 settle_before_execution: false,
111 })
112 }
113
114 #[must_use]
116 pub fn facilitator_url(&self) -> &Url {
117 self.facilitator.base_url()
118 }
119
120 #[must_use]
125 pub fn with_supported_cache_ttl(&self, ttl: Duration) -> Self {
126 let inner = Arc::unwrap_or_clone(Arc::clone(&self.facilitator));
127 let facilitator = Arc::new(inner.with_supported_cache_ttl(ttl));
128 Self {
129 facilitator,
130 base_url: self.base_url.clone(),
131 settle_before_execution: self.settle_before_execution,
132 }
133 }
134}
135
136impl TryFrom<&str> for X402Middleware<Arc<FacilitatorClient>> {
137 type Error = Box<dyn std::error::Error>;
138
139 fn try_from(value: &str) -> Result<Self, Self::Error> {
140 Self::try_new(value)
141 }
142}
143
144impl TryFrom<String> for X402Middleware<Arc<FacilitatorClient>> {
145 type Error = Box<dyn std::error::Error>;
146
147 fn try_from(value: String) -> Result<Self, Self::Error> {
148 Self::try_new(&value)
149 }
150}
151
152impl<F> X402Middleware<F>
153where
154 F: Clone,
155{
156 #[must_use]
163 pub fn with_base_url(&self, base_url: Url) -> Self {
164 let mut this = self.clone();
165 this.base_url = Some(base_url);
166 this
167 }
168
169 #[must_use]
172 pub fn settle_before_execution(&self) -> Self {
173 let mut this = self.clone();
174 this.settle_before_execution = true;
175 this
176 }
177
178 #[must_use]
184 pub fn settle_after_execution(&self) -> Self {
185 let mut this = self.clone();
186 this.settle_before_execution = false;
187 this
188 }
189}
190
191impl<TFacilitator> X402Middleware<TFacilitator>
192where
193 TFacilitator: Clone,
194{
195 #[must_use]
200 pub fn with_price_tag(
201 &self,
202 price_tag: v2::PriceTag,
203 ) -> X402LayerBuilder<StaticPriceTags, TFacilitator> {
204 X402LayerBuilder {
205 facilitator: self.facilitator.clone(),
206 price_source: StaticPriceTags::new(vec![price_tag]),
207 base_url: self.base_url.clone().map(Arc::new),
208 resource: Arc::new(ResourceInfoBuilder::default()),
209 settle_before_execution: self.settle_before_execution,
210 }
211 }
212
213 #[must_use]
218 pub fn with_dynamic_price<F, Fut>(
219 &self,
220 callback: F,
221 ) -> X402LayerBuilder<DynamicPriceTags, TFacilitator>
222 where
223 F: Fn(&HeaderMap, &Uri, Option<&Url>) -> Fut + Send + Sync + 'static,
224 Fut: Future<Output = Vec<v2::PriceTag>> + Send + 'static,
225 {
226 X402LayerBuilder {
227 facilitator: self.facilitator.clone(),
228 price_source: DynamicPriceTags::new(callback),
229 base_url: self.base_url.clone().map(Arc::new),
230 resource: Arc::new(ResourceInfoBuilder::default()),
231 settle_before_execution: self.settle_before_execution,
232 }
233 }
234}
235
236#[derive(Clone)]
241#[allow(missing_debug_implementations)] pub struct X402LayerBuilder<TSource, TFacilitator> {
243 facilitator: TFacilitator,
244 settle_before_execution: bool,
245 base_url: Option<Arc<Url>>,
246 price_source: TSource,
247 resource: Arc<ResourceInfoBuilder>,
248}
249
250impl<TFacilitator> X402LayerBuilder<StaticPriceTags, TFacilitator> {
251 #[must_use]
257 pub fn with_price_tag(mut self, price_tag: v2::PriceTag) -> Self {
258 self.price_source = self.price_source.with_price_tag(price_tag);
259 self
260 }
261}
262
263#[allow(missing_debug_implementations)] impl<TSource, TFacilitator> X402LayerBuilder<TSource, TFacilitator> {
265 #[must_use]
269 pub fn with_description(mut self, description: String) -> Self {
270 let mut new_resource = (*self.resource).clone();
271 new_resource.description = description;
272 self.resource = Arc::new(new_resource);
273 self
274 }
275
276 #[must_use]
280 pub fn with_mime_type(mut self, mime: String) -> Self {
281 let mut new_resource = (*self.resource).clone();
282 new_resource.mime_type = mime;
283 self.resource = Arc::new(new_resource);
284 self
285 }
286
287 #[must_use]
292 #[allow(clippy::needless_pass_by_value)] pub fn with_resource(mut self, resource: Url) -> Self {
294 let mut new_resource = (*self.resource).clone();
295 new_resource.url = Some(resource.to_string());
296 self.resource = Arc::new(new_resource);
297 self
298 }
299}
300
301impl<S, TSource, TFacilitator> Layer<S> for X402LayerBuilder<TSource, TFacilitator>
302where
303 S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + Sync + 'static,
304 S::Future: Send + 'static,
305 TFacilitator: Facilitator + Clone,
306 TSource: PriceTagSource,
307{
308 type Service = X402MiddlewareService<TSource, TFacilitator>;
309
310 fn layer(&self, inner: S) -> Self::Service {
311 X402MiddlewareService {
312 facilitator: self.facilitator.clone(),
313 settle_before_execution: self.settle_before_execution,
314 base_url: self.base_url.clone(),
315 price_source: self.price_source.clone(),
316 resource: Arc::clone(&self.resource),
317 inner: BoxCloneSyncService::new(inner),
318 }
319 }
320}
321
322#[derive(Clone)]
327#[allow(missing_debug_implementations)] pub struct X402MiddlewareService<TSource, TFacilitator> {
329 facilitator: TFacilitator,
331 base_url: Option<Arc<Url>>,
333 settle_before_execution: bool,
335 price_source: TSource,
337 resource: Arc<ResourceInfoBuilder>,
339 inner: BoxCloneSyncService<Request, Response, Infallible>,
341}
342
343impl<TSource, TFacilitator> Service<Request> for X402MiddlewareService<TSource, TFacilitator>
344where
345 TSource: PriceTagSource,
346 TFacilitator: Facilitator + Clone + Send + Sync + 'static,
347{
348 type Response = Response;
349 type Error = Infallible;
350 type Future = Pin<Box<dyn Future<Output = Result<Response, Infallible>> + Send>>;
351
352 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
354 self.inner.poll_ready(cx)
355 }
356
357 fn call(&mut self, req: Request) -> Self::Future {
359 let price_source = self.price_source.clone();
360 let facilitator = self.facilitator.clone();
361 let base_url = self.base_url.clone();
362 let resource_builder = Arc::clone(&self.resource);
363 let settle_before_execution = self.settle_before_execution;
364 let mut inner = self.inner.clone();
365
366 Box::pin(async move {
367 let accepts = price_source
369 .resolve(req.headers(), req.uri(), base_url.as_deref())
370 .await;
371
372 if accepts.is_empty() {
374 return inner.call(req).await;
375 }
376
377 let resource = resource_builder.as_resource_info(base_url.as_deref(), &req);
378
379 let gate = {
380 let mut gate = Paygate::builder(facilitator)
381 .accepts(accepts)
382 .resource(resource)
383 .settle_before_execution(settle_before_execution)
384 .build();
385 gate.enrich_accepts().await;
386 gate
387 };
388 gate.handle_request(inner, req).await
389 })
390 }
391}