Skip to main content

r402_server/
resource.rs

1//! Resource-server orchestration: scheme table, hooks, and facilitator handle.
2
3use std::sync::Arc;
4
5use r402_facilitator::{DynFacilitator, Facilitator};
6use r402_protocol::extension::{Extension, ExtensionRegistry};
7use r402_protocol::network::{ChainId, ChainIdPattern};
8use r402_protocol::payment::{PaymentRequirements, SupportedResponse, V2};
9
10use crate::hooks::{
11    CancelReason, DynResourceServerHooks, PaymentHookContext, ResourceServerHooks,
12    VerifiedPaymentCanceledContext, WirePaymentPayload,
13};
14use crate::payment_flow::{
15    PaymentFlowError, PaymentFlowName, PaymentFlowScheme, ResolvedPaymentFlow, SettlePhase,
16    resolve_payment_flow,
17};
18use crate::scheme::{DynSchemeNetworkServer, FacilitatorSupportError, SchemeNetworkServer};
19
20/// Server-side payment orchestrator.
21pub struct ResourceServer {
22    pub(crate) facilitator: Arc<dyn DynFacilitator>,
23    pub(crate) hooks: Vec<Arc<dyn DynResourceServerHooks>>,
24    pub(crate) schemes: Vec<(ChainIdPattern, Arc<dyn DynSchemeNetworkServer>)>,
25    pub(crate) extensions: ExtensionRegistry,
26}
27
28impl Clone for ResourceServer {
29    fn clone(&self) -> Self {
30        Self {
31            facilitator: Arc::clone(&self.facilitator),
32            hooks: self.hooks.clone(),
33            schemes: self.schemes.clone(),
34            extensions: self.extensions.clone(),
35        }
36    }
37}
38
39impl std::fmt::Debug for ResourceServer {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.debug_struct("ResourceServer")
42            .field("hooks", &self.hooks.len())
43            .field("schemes", &self.schemes.len())
44            .field("extensions", &self.extensions.len())
45            .finish_non_exhaustive()
46    }
47}
48
49impl ResourceServer {
50    /// Creates a resource server over any [`Facilitator`].
51    #[must_use]
52    pub fn new<F>(facilitator: Arc<F>) -> Self
53    where
54        F: Facilitator + 'static,
55    {
56        let erased: Arc<dyn DynFacilitator> = facilitator;
57        Self {
58            facilitator: erased,
59            hooks: Vec::new(),
60            schemes: Vec::new(),
61            extensions: ExtensionRegistry::new(),
62        }
63    }
64
65    /// Creates from an already-erased facilitator handle.
66    #[must_use]
67    pub fn from_dyn(facilitator: Arc<dyn DynFacilitator>) -> Self {
68        Self {
69            facilitator,
70            hooks: Vec::new(),
71            schemes: Vec::new(),
72            extensions: ExtensionRegistry::new(),
73        }
74    }
75
76    /// Registers a lifecycle hook. Returns `self` for builder chaining.
77    #[must_use]
78    pub fn with_hook(mut self, hook: impl ResourceServerHooks + 'static) -> Self {
79        self.hooks.push(Arc::new(hook));
80        self
81    }
82
83    /// Registers a lifecycle hook after construction.
84    pub fn add_hook(&mut self, hook: impl ResourceServerHooks + 'static) {
85        self.hooks.push(Arc::new(hook));
86    }
87
88    /// Number of registered resource-server hooks.
89    #[must_use]
90    pub fn hook_count(&self) -> usize {
91        self.hooks.len()
92    }
93
94    /// Returns a clone of the inner facilitator handle.
95    #[must_use]
96    pub fn facilitator(&self) -> Arc<dyn DynFacilitator> {
97        Arc::clone(&self.facilitator)
98    }
99
100    /// Registers a scheme/network adapter. Returns `self` for builder chaining.
101    #[must_use]
102    pub fn with_scheme(
103        mut self,
104        network: ChainIdPattern,
105        scheme: impl SchemeNetworkServer + 'static,
106    ) -> Self {
107        self.register_scheme(network, scheme);
108        self
109    }
110
111    /// Registers a scheme/network adapter after construction.
112    pub fn register_scheme(
113        &mut self,
114        network: ChainIdPattern,
115        scheme: impl SchemeNetworkServer + 'static,
116    ) {
117        self.schemes.push((network, Arc::new(scheme)));
118    }
119
120    /// Registered adapter for `scheme` on `network`, if any.
121    ///
122    /// Exact CAIP-2 registrations win over wildcard/set patterns.
123    #[must_use]
124    pub fn registered_scheme(
125        &self,
126        scheme: &str,
127        network: &ChainId,
128    ) -> Option<&dyn DynSchemeNetworkServer> {
129        let mut patterned: Option<&dyn DynSchemeNetworkServer> = None;
130        for (pattern, server) in &self.schemes {
131            if server.scheme() != scheme || !pattern.matches(network) {
132                continue;
133            }
134            if matches!(pattern, ChainIdPattern::Exact { .. }) {
135                return Some(server.as_ref());
136            }
137            if patterned.is_none() {
138                patterned = Some(server.as_ref());
139            }
140        }
141        patterned
142    }
143
144    /// Resolves ATM + payment flow from the registered scheme table.
145    ///
146    /// # Errors
147    ///
148    /// [`PaymentFlowError::UnregisteredScheme`] when no adapter matches this
149    /// accept, or ATM/flow errors from [`resolve_payment_flow`].
150    pub(crate) fn resolved_payment_flow(
151        &self,
152        requirements: &PaymentRequirements,
153    ) -> Result<ResolvedPaymentFlow, PaymentFlowError> {
154        let Some(scheme) =
155            self.registered_scheme(requirements.scheme.as_str(), &requirements.network)
156        else {
157            return Err(PaymentFlowError::UnregisteredScheme {
158                scheme: requirements.scheme.to_string(),
159                network: requirements.network.to_string(),
160            });
161        };
162        resolve_payment_flow(
163            &PaymentFlowScheme {
164                scheme: scheme.scheme(),
165                default_asset_transfer_method: scheme.default_asset_transfer_method(),
166                payment_flows: scheme.payment_flows(),
167            },
168            requirements,
169        )
170    }
171
172    /// Resolves the payment-flow name for this accept from the scheme table.
173    ///
174    /// # Errors
175    ///
176    /// [`PaymentFlowError::UnregisteredScheme`] when no adapter matches this
177    /// accept, or ATM/flow errors from [`resolve_payment_flow`].
178    pub fn get_payment_flow(
179        &self,
180        requirements: &PaymentRequirements,
181    ) -> Result<PaymentFlowName, PaymentFlowError> {
182        Ok(self.resolved_payment_flow(requirements)?.payment_flow)
183    }
184
185    /// Whether the registered scheme yields cancel-settle requirements.
186    ///
187    /// Escrow accepts must return `true` before a transport will serve them.
188    pub async fn has_settle_on_cancel(&self, requirements: &PaymentRequirements) -> bool {
189        let Some(scheme) =
190            self.registered_scheme(requirements.scheme.as_str(), &requirements.network)
191        else {
192            return false;
193        };
194        let ctx = VerifiedPaymentCanceledContext {
195            payment: PaymentHookContext {
196                payload: WirePaymentPayload::new(requirements.clone(), serde_json::Value::Null),
197                requirements: requirements.clone(),
198            },
199            reason: CancelReason::HandlerFailed,
200            error: None,
201            response_status: None,
202            settled_phases: vec![SettlePhase::BeforeHandler],
203        };
204        scheme.settle_on_cancel(&ctx).await.is_some()
205    }
206
207    /// Registers a protocol extension advertised on 402 responses.
208    #[must_use]
209    pub fn with_extension(mut self, extension: impl Extension + 'static) -> Self {
210        self.extensions.register(extension);
211        self
212    }
213}
214
215/// Checks each accept against facilitator `/supported` kinds.
216///
217/// Unused registrations are ignored. An unregistered accept skips
218/// [`SchemeNetworkServer::validate_facilitator_support`].
219///
220/// # Errors
221///
222/// [`FacilitatorSupportError::KindMissing`] when no v2 kind matches the
223/// accept. Other variants come from the registered scheme hook.
224pub fn validate_accepts_against_supported(
225    server: &ResourceServer,
226    accepts: &[PaymentRequirements],
227    supported: &SupportedResponse,
228) -> Result<(), FacilitatorSupportError> {
229    for requirements in accepts {
230        let scheme = requirements.scheme.as_str();
231        let network = requirements.network.to_string();
232        let kind = supported.kinds.iter().find(|kind| {
233            V2 == kind.x402_version
234                && kind.scheme.as_str() == scheme
235                && kind.network.as_str() == network
236        });
237        let Some(kind) = kind else {
238            return Err(FacilitatorSupportError::KindMissing {
239                scheme: requirements.scheme.clone(),
240                network: requirements.network.clone(),
241            });
242        };
243        let Some(registered) = server.registered_scheme(scheme, &requirements.network) else {
244            continue;
245        };
246        registered.validate_facilitator_support(
247            &requirements.network,
248            kind,
249            &supported.extensions,
250        )?;
251    }
252    Ok(())
253}