Skip to main content

r402_http/server/
pricing.rs

1//! Price tag sources for the x402 payment gate.
2//!
3//! Abstracts over static and dynamic pricing strategies via the
4//! [`PriceTagSource`] trait. All sources produce [`v2::PriceTag`] values
5//! (V2-only server layer).
6
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10
11use http::{HeaderMap, Uri};
12use r402::proto::v2;
13use url::Url;
14
15/// Trait for types that can provide V2 price tags for a request.
16///
17/// This trait abstracts over static and dynamic pricing strategies.
18/// Implementations must be infallible - they always return price tags.
19pub trait PriceTagSource: Clone + Send + Sync + 'static {
20    /// Resolves price tags for the given request context.
21    ///
22    /// This method is infallible - it must always return a non-empty vector of price tags.
23    fn resolve(
24        &self,
25        headers: &HeaderMap,
26        uri: &Uri,
27        base_url: Option<&Url>,
28    ) -> impl Future<Output = Vec<v2::PriceTag>> + Send;
29}
30
31/// Static price tag source - returns the same price tags for every request.
32///
33/// This is the default implementation used when calling `with_price_tag()`.
34/// It simply stores a vector of price tags and returns clones on each request.
35#[derive(Clone, Debug)]
36pub struct StaticPriceTags {
37    tags: Arc<Vec<v2::PriceTag>>,
38}
39
40impl StaticPriceTags {
41    /// Creates a new static price tag source from a vector of price tags.
42    #[must_use]
43    pub fn new(tags: Vec<v2::PriceTag>) -> Self {
44        Self {
45            tags: Arc::new(tags),
46        }
47    }
48
49    /// Returns a reference to the stored price tags.
50    #[must_use]
51    pub fn tags(&self) -> &[v2::PriceTag] {
52        &self.tags
53    }
54
55    /// Adds a price tag to the source.
56    #[must_use]
57    pub fn with_price_tag(mut self, tag: v2::PriceTag) -> Self {
58        let mut tags = (*self.tags).clone();
59        tags.push(tag);
60        self.tags = Arc::new(tags);
61        self
62    }
63}
64
65impl PriceTagSource for StaticPriceTags {
66    async fn resolve(
67        &self,
68        _headers: &HeaderMap,
69        _uri: &Uri,
70        _base_url: Option<&Url>,
71    ) -> Vec<v2::PriceTag> {
72        (*self.tags).clone()
73    }
74}
75
76/// Internal type alias for the boxed dynamic pricing callback.
77type BoxedDynamicPriceCallback = dyn for<'a> Fn(
78        &'a HeaderMap,
79        &'a Uri,
80        Option<&'a Url>,
81    ) -> Pin<Box<dyn Future<Output = Vec<v2::PriceTag>> + Send + 'a>>
82    + Send
83    + Sync;
84
85/// Dynamic price tag source - computes price tags per-request via callback.
86///
87/// This implementation allows computing different prices based on request
88/// headers, URI, or other runtime factors.
89pub struct DynamicPriceTags {
90    callback: Arc<BoxedDynamicPriceCallback>,
91}
92
93impl Clone for DynamicPriceTags {
94    fn clone(&self) -> Self {
95        Self {
96            callback: Arc::clone(&self.callback),
97        }
98    }
99}
100
101impl std::fmt::Debug for DynamicPriceTags {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.debug_struct("DynamicPriceTags")
104            .field("callback", &"<callback>")
105            .finish()
106    }
107}
108
109impl DynamicPriceTags {
110    /// Creates a new dynamic price source from an async closure.
111    ///
112    /// The closure receives request context and returns a vector of price tags.
113    pub fn new<F, Fut>(callback: F) -> Self
114    where
115        F: Fn(&HeaderMap, &Uri, Option<&Url>) -> Fut + Send + Sync + 'static,
116        Fut: Future<Output = Vec<v2::PriceTag>> + Send + 'static,
117    {
118        Self {
119            callback: Arc::new(move |headers, uri, base_url| {
120                Box::pin(callback(headers, uri, base_url))
121            }),
122        }
123    }
124}
125
126impl PriceTagSource for DynamicPriceTags {
127    async fn resolve(
128        &self,
129        headers: &HeaderMap,
130        uri: &Uri,
131        base_url: Option<&Url>,
132    ) -> Vec<v2::PriceTag> {
133        (self.callback)(headers, uri, base_url).await
134    }
135}