1use std::convert::Infallible;
29use std::future::Future;
30use std::pin::Pin;
31use std::sync::Arc;
32use std::task::{Context, Poll};
33use std::time::Duration;
34
35use axum_core::extract::Request;
36use axum_core::response::Response;
37use http::{HeaderMap, Uri};
38use r402::facilitator::Facilitator;
39use r402::proto::v2;
40use tower::util::BoxCloneSyncService;
41use tower::{Layer, Service};
42use url::Url;
43
44use super::facilitator::FacilitatorClient;
45use super::paygate::{Paygate, ResourceTemplate};
46use super::pricing::{DynamicPriceTags, PriceTagSource, StaticPriceTags};
47
48#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
61pub enum SettlementMode {
62 #[default]
64 Sequential,
65 Concurrent,
67}
68
69pub struct X402Middleware<F> {
74 facilitator: F,
75 base_url: Option<Url>,
76}
77
78impl<F: Clone> Clone for X402Middleware<F> {
79 fn clone(&self) -> Self {
80 Self {
81 facilitator: self.facilitator.clone(),
82 base_url: self.base_url.clone(),
83 }
84 }
85}
86
87impl<F: std::fmt::Debug> std::fmt::Debug for X402Middleware<F> {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("X402Middleware")
90 .field("facilitator", &self.facilitator)
91 .field("base_url", &self.base_url)
92 .finish()
93 }
94}
95
96impl<F> X402Middleware<F> {
97 #[must_use]
102 pub const fn from_facilitator(facilitator: F) -> Self {
103 Self {
104 facilitator,
105 base_url: None,
106 }
107 }
108
109 pub const fn facilitator(&self) -> &F {
111 &self.facilitator
112 }
113}
114
115impl X402Middleware<Arc<FacilitatorClient>> {
116 #[must_use]
122 pub fn new(url: &str) -> Self {
123 let facilitator = FacilitatorClient::try_from(url).expect("Invalid facilitator URL");
124 Self {
125 facilitator: Arc::new(facilitator),
126 base_url: None,
127 }
128 }
129
130 pub fn try_new(url: &str) -> Result<Self, Box<dyn std::error::Error>> {
136 let facilitator = FacilitatorClient::try_from(url)?;
137 Ok(Self {
138 facilitator: Arc::new(facilitator),
139 base_url: None,
140 })
141 }
142
143 #[must_use]
145 pub fn facilitator_url(&self) -> &Url {
146 self.facilitator.base_url()
147 }
148
149 #[must_use]
154 pub fn with_supported_cache_ttl(&self, ttl: Duration) -> Self {
155 let inner = Arc::unwrap_or_clone(Arc::clone(&self.facilitator));
156 let facilitator = Arc::new(inner.with_supported_cache_ttl(ttl));
157 Self {
158 facilitator,
159 base_url: self.base_url.clone(),
160 }
161 }
162
163 #[must_use]
171 pub fn with_facilitator_timeout(&self, timeout: Duration) -> Self {
172 let inner = Arc::unwrap_or_clone(Arc::clone(&self.facilitator));
173 let facilitator = Arc::new(inner.with_timeout(timeout));
174 Self {
175 facilitator,
176 base_url: self.base_url.clone(),
177 }
178 }
179}
180
181impl TryFrom<&str> for X402Middleware<Arc<FacilitatorClient>> {
182 type Error = Box<dyn std::error::Error>;
183
184 fn try_from(value: &str) -> Result<Self, Self::Error> {
185 Self::try_new(value)
186 }
187}
188
189impl TryFrom<String> for X402Middleware<Arc<FacilitatorClient>> {
190 type Error = Box<dyn std::error::Error>;
191
192 fn try_from(value: String) -> Result<Self, Self::Error> {
193 Self::try_new(&value)
194 }
195}
196
197impl<F> X402Middleware<F>
198where
199 F: Clone,
200{
201 #[must_use]
208 pub fn with_base_url(&self, base_url: Url) -> Self {
209 let mut this = self.clone();
210 this.base_url = Some(base_url);
211 this
212 }
213}
214
215impl<TFacilitator> X402Middleware<TFacilitator>
216where
217 TFacilitator: Clone,
218{
219 #[must_use]
224 pub fn with_price_tag(
225 &self,
226 price_tag: v2::PriceTag,
227 ) -> X402LayerBuilder<StaticPriceTags, TFacilitator> {
228 X402LayerBuilder {
229 facilitator: self.facilitator.clone(),
230 price_source: StaticPriceTags::new(vec![price_tag]),
231 base_url: self.base_url.clone().map(Arc::new),
232 resource: Arc::new(ResourceTemplate::default()),
233 settlement_mode: SettlementMode::default(),
234 }
235 }
236
237 #[must_use]
244 pub fn with_price_tags(
245 &self,
246 price_tags: Vec<v2::PriceTag>,
247 ) -> X402LayerBuilder<StaticPriceTags, TFacilitator> {
248 X402LayerBuilder {
249 facilitator: self.facilitator.clone(),
250 price_source: StaticPriceTags::new(price_tags),
251 base_url: self.base_url.clone().map(Arc::new),
252 resource: Arc::new(ResourceTemplate::default()),
253 settlement_mode: SettlementMode::default(),
254 }
255 }
256
257 #[must_use]
262 pub fn with_dynamic_price<F, Fut>(
263 &self,
264 callback: F,
265 ) -> X402LayerBuilder<DynamicPriceTags, TFacilitator>
266 where
267 F: Fn(&HeaderMap, &Uri, Option<&Url>) -> Fut + Send + Sync + 'static,
268 Fut: Future<Output = Vec<v2::PriceTag>> + Send + 'static,
269 {
270 X402LayerBuilder {
271 facilitator: self.facilitator.clone(),
272 price_source: DynamicPriceTags::new(callback),
273 base_url: self.base_url.clone().map(Arc::new),
274 resource: Arc::new(ResourceTemplate::default()),
275 settlement_mode: SettlementMode::default(),
276 }
277 }
278}
279
280#[derive(Clone)]
285#[allow(missing_debug_implementations)] pub struct X402LayerBuilder<TSource, TFacilitator> {
287 facilitator: TFacilitator,
288 base_url: Option<Arc<Url>>,
289 price_source: TSource,
290 resource: Arc<ResourceTemplate>,
291 settlement_mode: SettlementMode,
292}
293
294impl<TFacilitator> X402LayerBuilder<StaticPriceTags, TFacilitator> {
295 #[must_use]
301 pub fn with_price_tag(mut self, price_tag: v2::PriceTag) -> Self {
302 self.price_source = self.price_source.with_price_tag(price_tag);
303 self
304 }
305}
306
307#[allow(missing_debug_implementations)] impl<TSource, TFacilitator> X402LayerBuilder<TSource, TFacilitator> {
309 #[must_use]
313 pub fn with_description(mut self, description: String) -> Self {
314 let mut new_resource = (*self.resource).clone();
315 new_resource.description = description;
316 self.resource = Arc::new(new_resource);
317 self
318 }
319
320 #[must_use]
324 pub fn with_mime_type(mut self, mime: String) -> Self {
325 let mut new_resource = (*self.resource).clone();
326 new_resource.mime_type = mime;
327 self.resource = Arc::new(new_resource);
328 self
329 }
330
331 #[must_use]
336 #[allow(clippy::needless_pass_by_value)] pub fn with_resource(mut self, resource: Url) -> Self {
338 let mut new_resource = (*self.resource).clone();
339 new_resource.url = Some(resource.to_string());
340 self.resource = Arc::new(new_resource);
341 self
342 }
343
344 #[must_use]
353 pub const fn with_settlement_mode(mut self, mode: SettlementMode) -> Self {
354 self.settlement_mode = mode;
355 self
356 }
357}
358
359impl<S, TSource, TFacilitator> Layer<S> for X402LayerBuilder<TSource, TFacilitator>
360where
361 S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + Sync + 'static,
362 S::Future: Send + 'static,
363 TFacilitator: Facilitator + Clone,
364 TSource: PriceTagSource,
365{
366 type Service = X402MiddlewareService<TSource, TFacilitator>;
367
368 fn layer(&self, inner: S) -> Self::Service {
369 X402MiddlewareService {
370 facilitator: self.facilitator.clone(),
371 base_url: self.base_url.clone(),
372 price_source: self.price_source.clone(),
373 resource: Arc::clone(&self.resource),
374 settlement_mode: self.settlement_mode,
375 inner: BoxCloneSyncService::new(inner),
376 }
377 }
378}
379
380#[derive(Clone)]
385#[allow(missing_debug_implementations)] pub struct X402MiddlewareService<TSource, TFacilitator> {
387 facilitator: TFacilitator,
389 base_url: Option<Arc<Url>>,
391 price_source: TSource,
393 resource: Arc<ResourceTemplate>,
395 settlement_mode: SettlementMode,
397 inner: BoxCloneSyncService<Request, Response, Infallible>,
399}
400
401impl<TSource, TFacilitator> Service<Request> for X402MiddlewareService<TSource, TFacilitator>
402where
403 TSource: PriceTagSource,
404 TFacilitator: Facilitator + Clone + Send + Sync + 'static,
405{
406 type Response = Response;
407 type Error = Infallible;
408 type Future = Pin<Box<dyn Future<Output = Result<Response, Infallible>> + Send>>;
409
410 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
412 self.inner.poll_ready(cx)
413 }
414
415 fn call(&mut self, req: Request) -> Self::Future {
417 let price_source = self.price_source.clone();
418 let facilitator = self.facilitator.clone();
419 let base_url = self.base_url.clone();
420 let resource_builder = Arc::clone(&self.resource);
421 let settlement_mode = self.settlement_mode;
422 let mut inner = self.inner.clone();
423
424 Box::pin(async move {
425 let accepts = price_source
427 .resolve(req.headers(), req.uri(), base_url.as_deref())
428 .await;
429
430 if accepts.is_empty() {
432 return inner.call(req).await;
433 }
434
435 let resource = resource_builder.resolve(base_url.as_deref(), &req);
436
437 let mut gate = Paygate::builder(facilitator)
438 .accepts(accepts)
439 .resource(resource)
440 .build();
441 gate.enrich_accepts().await;
442
443 let result = match settlement_mode {
444 SettlementMode::Sequential => gate.handle_request(inner, req).await,
445 SettlementMode::Concurrent => gate.handle_request_concurrent(inner, req).await,
446 };
447 Ok(result.unwrap_or_else(|err| gate.error_response(err)))
448 })
449 }
450}