1use std::convert::Infallible;
21use std::future::Future;
22use std::pin::Pin;
23use std::sync::Arc;
24use std::task::{Context, Poll};
25use std::time::Duration;
26
27use axum_core::extract::Request;
28use axum_core::response::Response;
29use http::{HeaderMap, Uri};
30use r402::facilitator::Facilitator;
31use r402::proto::v2;
32use tower::util::BoxCloneSyncService;
33use tower::{Layer, Service};
34use url::Url;
35
36use super::facilitator::FacilitatorClient;
37use super::paygate::{Paygate, ResourceInfoBuilder};
38use super::pricing::{DynamicPriceTags, PriceTagSource, StaticPriceTags};
39
40pub struct X402Middleware<F> {
45 facilitator: F,
46 base_url: Option<Url>,
47}
48
49impl<F: Clone> Clone for X402Middleware<F> {
50 fn clone(&self) -> Self {
51 Self {
52 facilitator: self.facilitator.clone(),
53 base_url: self.base_url.clone(),
54 }
55 }
56}
57
58impl<F: std::fmt::Debug> std::fmt::Debug for X402Middleware<F> {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.debug_struct("X402Middleware")
61 .field("facilitator", &self.facilitator)
62 .field("base_url", &self.base_url)
63 .finish()
64 }
65}
66
67impl<F> X402Middleware<F> {
68 pub const fn facilitator(&self) -> &F {
70 &self.facilitator
71 }
72}
73
74impl X402Middleware<Arc<FacilitatorClient>> {
75 #[must_use]
81 pub fn new(url: &str) -> Self {
82 let facilitator = FacilitatorClient::try_from(url).expect("Invalid facilitator URL");
83 Self {
84 facilitator: Arc::new(facilitator),
85 base_url: None,
86 }
87 }
88
89 pub fn try_new(url: &str) -> Result<Self, Box<dyn std::error::Error>> {
95 let facilitator = FacilitatorClient::try_from(url)?;
96 Ok(Self {
97 facilitator: Arc::new(facilitator),
98 base_url: None,
99 })
100 }
101
102 #[must_use]
104 pub fn facilitator_url(&self) -> &Url {
105 self.facilitator.base_url()
106 }
107
108 #[must_use]
113 pub fn with_supported_cache_ttl(&self, ttl: Duration) -> Self {
114 let inner = Arc::unwrap_or_clone(Arc::clone(&self.facilitator));
115 let facilitator = Arc::new(inner.with_supported_cache_ttl(ttl));
116 Self {
117 facilitator,
118 base_url: self.base_url.clone(),
119 }
120 }
121
122 #[must_use]
130 pub fn with_facilitator_timeout(&self, timeout: Duration) -> Self {
131 let inner = Arc::unwrap_or_clone(Arc::clone(&self.facilitator));
132 let facilitator = Arc::new(inner.with_timeout(timeout));
133 Self {
134 facilitator,
135 base_url: self.base_url.clone(),
136 }
137 }
138}
139
140impl TryFrom<&str> for X402Middleware<Arc<FacilitatorClient>> {
141 type Error = Box<dyn std::error::Error>;
142
143 fn try_from(value: &str) -> Result<Self, Self::Error> {
144 Self::try_new(value)
145 }
146}
147
148impl TryFrom<String> for X402Middleware<Arc<FacilitatorClient>> {
149 type Error = Box<dyn std::error::Error>;
150
151 fn try_from(value: String) -> Result<Self, Self::Error> {
152 Self::try_new(&value)
153 }
154}
155
156impl<F> X402Middleware<F>
157where
158 F: Clone,
159{
160 #[must_use]
167 pub fn with_base_url(&self, base_url: Url) -> Self {
168 let mut this = self.clone();
169 this.base_url = Some(base_url);
170 this
171 }
172}
173
174impl<TFacilitator> X402Middleware<TFacilitator>
175where
176 TFacilitator: Clone,
177{
178 #[must_use]
183 pub fn with_price_tag(
184 &self,
185 price_tag: v2::PriceTag,
186 ) -> X402LayerBuilder<StaticPriceTags, TFacilitator> {
187 X402LayerBuilder {
188 facilitator: self.facilitator.clone(),
189 price_source: StaticPriceTags::new(vec![price_tag]),
190 base_url: self.base_url.clone().map(Arc::new),
191 resource: Arc::new(ResourceInfoBuilder::default()),
192 }
193 }
194
195 #[must_use]
200 pub fn with_dynamic_price<F, Fut>(
201 &self,
202 callback: F,
203 ) -> X402LayerBuilder<DynamicPriceTags, TFacilitator>
204 where
205 F: Fn(&HeaderMap, &Uri, Option<&Url>) -> Fut + Send + Sync + 'static,
206 Fut: Future<Output = Vec<v2::PriceTag>> + Send + 'static,
207 {
208 X402LayerBuilder {
209 facilitator: self.facilitator.clone(),
210 price_source: DynamicPriceTags::new(callback),
211 base_url: self.base_url.clone().map(Arc::new),
212 resource: Arc::new(ResourceInfoBuilder::default()),
213 }
214 }
215}
216
217#[derive(Clone)]
222#[allow(missing_debug_implementations)] pub struct X402LayerBuilder<TSource, TFacilitator> {
224 facilitator: TFacilitator,
225 base_url: Option<Arc<Url>>,
226 price_source: TSource,
227 resource: Arc<ResourceInfoBuilder>,
228}
229
230impl<TFacilitator> X402LayerBuilder<StaticPriceTags, TFacilitator> {
231 #[must_use]
237 pub fn with_price_tag(mut self, price_tag: v2::PriceTag) -> Self {
238 self.price_source = self.price_source.with_price_tag(price_tag);
239 self
240 }
241}
242
243#[allow(missing_debug_implementations)] impl<TSource, TFacilitator> X402LayerBuilder<TSource, TFacilitator> {
245 #[must_use]
249 pub fn with_description(mut self, description: String) -> Self {
250 let mut new_resource = (*self.resource).clone();
251 new_resource.description = description;
252 self.resource = Arc::new(new_resource);
253 self
254 }
255
256 #[must_use]
260 pub fn with_mime_type(mut self, mime: String) -> Self {
261 let mut new_resource = (*self.resource).clone();
262 new_resource.mime_type = mime;
263 self.resource = Arc::new(new_resource);
264 self
265 }
266
267 #[must_use]
272 #[allow(clippy::needless_pass_by_value)] pub fn with_resource(mut self, resource: Url) -> Self {
274 let mut new_resource = (*self.resource).clone();
275 new_resource.url = Some(resource.to_string());
276 self.resource = Arc::new(new_resource);
277 self
278 }
279}
280
281impl<S, TSource, TFacilitator> Layer<S> for X402LayerBuilder<TSource, TFacilitator>
282where
283 S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + Sync + 'static,
284 S::Future: Send + 'static,
285 TFacilitator: Facilitator + Clone,
286 TSource: PriceTagSource,
287{
288 type Service = X402MiddlewareService<TSource, TFacilitator>;
289
290 fn layer(&self, inner: S) -> Self::Service {
291 X402MiddlewareService {
292 facilitator: self.facilitator.clone(),
293 base_url: self.base_url.clone(),
294 price_source: self.price_source.clone(),
295 resource: Arc::clone(&self.resource),
296 inner: BoxCloneSyncService::new(inner),
297 }
298 }
299}
300
301#[derive(Clone)]
306#[allow(missing_debug_implementations)] pub struct X402MiddlewareService<TSource, TFacilitator> {
308 facilitator: TFacilitator,
310 base_url: Option<Arc<Url>>,
312 price_source: TSource,
314 resource: Arc<ResourceInfoBuilder>,
316 inner: BoxCloneSyncService<Request, Response, Infallible>,
318}
319
320impl<TSource, TFacilitator> Service<Request> for X402MiddlewareService<TSource, TFacilitator>
321where
322 TSource: PriceTagSource,
323 TFacilitator: Facilitator + Clone + Send + Sync + 'static,
324{
325 type Response = Response;
326 type Error = Infallible;
327 type Future = Pin<Box<dyn Future<Output = Result<Response, Infallible>> + Send>>;
328
329 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
331 self.inner.poll_ready(cx)
332 }
333
334 fn call(&mut self, req: Request) -> Self::Future {
336 let price_source = self.price_source.clone();
337 let facilitator = self.facilitator.clone();
338 let base_url = self.base_url.clone();
339 let resource_builder = Arc::clone(&self.resource);
340 let mut inner = self.inner.clone();
341
342 Box::pin(async move {
343 let accepts = price_source
345 .resolve(req.headers(), req.uri(), base_url.as_deref())
346 .await;
347
348 if accepts.is_empty() {
350 return inner.call(req).await;
351 }
352
353 let resource = resource_builder.as_resource_info(base_url.as_deref(), &req);
354
355 let gate = {
356 let mut gate = Paygate::builder(facilitator)
357 .accepts(accepts)
358 .resource(resource)
359 .build();
360 gate.enrich_accepts().await;
361 gate
362 };
363 gate.handle_request(inner, req).await
364 })
365 }
366}