Skip to main content

r402_http/server/
pricing.rs

1//! Static and dynamic price-tag sources.
2
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use http::{HeaderMap, Uri};
8use r402_protocol::payment::PriceTag;
9use url::Url;
10
11/// Resolves V2 price tags for one request.
12pub trait PriceTagSource: Clone + Send + Sync + 'static {
13    /// Always returns a (possibly empty) tag list. Empty means layer bypass.
14    fn resolve(
15        &self,
16        headers: &HeaderMap,
17        uri: &Uri,
18        base_url: &Url,
19    ) -> impl Future<Output = Vec<PriceTag>> + Send;
20}
21
22/// Fixed tag list for every request.
23#[derive(Clone, Debug)]
24pub struct StaticPriceTags {
25    tags: Arc<[PriceTag]>,
26}
27
28impl StaticPriceTags {
29    /// Stores `tags`.
30    #[must_use]
31    pub fn new(tags: Vec<PriceTag>) -> Self {
32        Self { tags: tags.into() }
33    }
34
35    /// Stored tags.
36    #[must_use]
37    pub fn tags(&self) -> &[PriceTag] {
38        &self.tags
39    }
40
41    /// Appends one tag.
42    #[must_use]
43    pub fn with_price_tag(mut self, tag: PriceTag) -> Self {
44        let mut tags = self.tags.to_vec();
45        tags.push(tag);
46        self.tags = tags.into();
47        self
48    }
49}
50
51impl PriceTagSource for StaticPriceTags {
52    fn resolve(
53        &self,
54        _headers: &HeaderMap,
55        _uri: &Uri,
56        _base_url: &Url,
57    ) -> impl Future<Output = Vec<PriceTag>> + Send {
58        std::future::ready(self.tags.to_vec())
59    }
60}
61
62type BoxedDynamicPriceCallback = dyn for<'a> Fn(
63        &'a HeaderMap,
64        &'a Uri,
65        &'a Url,
66    ) -> Pin<Box<dyn Future<Output = Vec<PriceTag>> + Send + 'a>>
67    + Send
68    + Sync;
69
70/// Per-request price tags from an async callback.
71pub struct DynamicPriceTags {
72    callback: Arc<BoxedDynamicPriceCallback>,
73}
74
75impl Clone for DynamicPriceTags {
76    fn clone(&self) -> Self {
77        Self {
78            callback: Arc::clone(&self.callback),
79        }
80    }
81}
82
83impl std::fmt::Debug for DynamicPriceTags {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.debug_struct("DynamicPriceTags")
86            .field("callback", &"<callback>")
87            .finish()
88    }
89}
90
91impl DynamicPriceTags {
92    /// Wraps `callback`.
93    pub fn new<F, Fut>(callback: F) -> Self
94    where
95        F: Fn(&HeaderMap, &Uri, &Url) -> Fut + Send + Sync + 'static,
96        Fut: Future<Output = Vec<PriceTag>> + Send + 'static,
97    {
98        Self {
99            callback: Arc::new(move |headers, uri, base_url| {
100                Box::pin(callback(headers, uri, base_url))
101            }),
102        }
103    }
104}
105
106impl PriceTagSource for DynamicPriceTags {
107    async fn resolve(&self, headers: &HeaderMap, uri: &Uri, base_url: &Url) -> Vec<PriceTag> {
108        (self.callback)(headers, uri, base_url).await
109    }
110}