r402_core/extensions/
mod.rs1use std::collections::HashMap;
46use std::fmt::{self, Debug, Formatter};
47use std::future::Future;
48use std::pin::Pin;
49use std::sync::Arc;
50
51use compact_str::CompactString;
52
53use crate::wire::{
54 ExtensionEntry, Extensions, PaymentPayload, PaymentRequirements, SettleResponse, VerifyResponse,
55};
56
57#[cfg(feature = "ext-bazaar")]
58#[cfg_attr(docsrs, doc(cfg(feature = "ext-bazaar")))]
59pub mod bazaar;
60
61#[cfg(feature = "ext-payment-id")]
62#[cfg_attr(docsrs, doc(cfg(feature = "ext-payment-id")))]
63pub mod payment_id;
64
65pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
67
68#[derive(Debug)]
70#[non_exhaustive]
71pub struct AdvertiseContext<'a> {
72 pub requirement: Option<&'a PaymentRequirements>,
76}
77
78#[non_exhaustive]
80pub struct VerifyContext<'a> {
81 pub payload: &'a PaymentPayload<serde_json::Value, serde_json::Value>,
85 pub requirements: &'a PaymentRequirements,
87 pub response: &'a VerifyResponse,
90}
91
92impl Debug for VerifyContext<'_> {
93 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
94 f.debug_struct("VerifyContext")
95 .field("requirements", &self.requirements)
96 .finish_non_exhaustive()
97 }
98}
99
100#[non_exhaustive]
102pub struct SettleContext<'a> {
103 pub payload: &'a PaymentPayload<serde_json::Value, serde_json::Value>,
105 pub requirements: &'a PaymentRequirements,
107 pub response: &'a SettleResponse,
109}
110
111impl Debug for SettleContext<'_> {
112 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
113 f.debug_struct("SettleContext")
114 .field("requirements", &self.requirements)
115 .finish_non_exhaustive()
116 }
117}
118
119pub trait Extension: Send + Sync {
129 fn id(&self) -> &'static str;
131
132 fn advertise(&self, _ctx: &AdvertiseContext<'_>) -> Option<ExtensionEntry> {
136 None
137 }
138
139 fn on_verify<'a>(
142 &'a self,
143 _ctx: &'a VerifyContext<'a>,
144 ) -> impl Future<Output = Option<ExtensionEntry>> + Send + 'a {
145 async { None }
146 }
147
148 fn on_settle<'a>(
151 &'a self,
152 _ctx: &'a SettleContext<'a>,
153 ) -> impl Future<Output = Option<ExtensionEntry>> + Send + 'a {
154 async { None }
155 }
156}
157
158pub trait DynExtension: Send + Sync {
163 fn id(&self) -> &'static str;
165
166 fn advertise(&self, ctx: &AdvertiseContext<'_>) -> Option<ExtensionEntry>;
168
169 fn on_verify<'a>(&'a self, ctx: &'a VerifyContext<'a>)
171 -> BoxFuture<'a, Option<ExtensionEntry>>;
172
173 fn on_settle<'a>(&'a self, ctx: &'a SettleContext<'a>)
175 -> BoxFuture<'a, Option<ExtensionEntry>>;
176}
177
178impl<T: Extension + ?Sized> DynExtension for T {
179 fn id(&self) -> &'static str {
180 <Self as Extension>::id(self)
181 }
182
183 fn advertise(&self, ctx: &AdvertiseContext<'_>) -> Option<ExtensionEntry> {
184 <Self as Extension>::advertise(self, ctx)
185 }
186
187 fn on_verify<'a>(
188 &'a self,
189 ctx: &'a VerifyContext<'a>,
190 ) -> BoxFuture<'a, Option<ExtensionEntry>> {
191 Box::pin(<Self as Extension>::on_verify(self, ctx))
192 }
193
194 fn on_settle<'a>(
195 &'a self,
196 ctx: &'a SettleContext<'a>,
197 ) -> BoxFuture<'a, Option<ExtensionEntry>> {
198 Box::pin(<Self as Extension>::on_settle(self, ctx))
199 }
200}
201
202#[derive(Clone, Default)]
207pub struct ExtensionRegistry {
208 ordered: Vec<Arc<dyn DynExtension>>,
209 by_id: HashMap<CompactString, Arc<dyn DynExtension>>,
210}
211
212impl Debug for ExtensionRegistry {
213 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
214 let ids: Vec<&str> = self.ordered.iter().map(|e| e.id()).collect();
215 f.debug_tuple("ExtensionRegistry").field(&ids).finish()
216 }
217}
218
219impl ExtensionRegistry {
220 #[must_use]
222 pub fn new() -> Self {
223 Self::default()
224 }
225
226 pub fn register<E: Extension + 'static>(&mut self, extension: E) {
229 let id = extension.id();
230 let arc: Arc<dyn DynExtension> = Arc::new(extension);
231 self.ordered.retain(|existing| existing.id() != id);
233 self.ordered.push(Arc::clone(&arc));
234 let _ = self.by_id.insert(CompactString::from(id), arc);
235 }
236
237 #[must_use]
239 pub fn get(&self, id: &str) -> Option<&dyn DynExtension> {
240 self.by_id.get(id).map(AsRef::as_ref)
241 }
242
243 pub fn iter(&self) -> impl Iterator<Item = &dyn DynExtension> {
245 self.ordered.iter().map(AsRef::as_ref)
246 }
247
248 #[must_use]
250 pub fn is_empty(&self) -> bool {
251 self.ordered.is_empty()
252 }
253
254 #[must_use]
256 pub fn len(&self) -> usize {
257 self.ordered.len()
258 }
259
260 #[must_use]
262 pub fn advertise(&self, ctx: &AdvertiseContext<'_>) -> Extensions {
263 let mut out = Extensions::new();
264 for ext in self.iter() {
265 if let Some(entry) = ext.advertise(ctx) {
266 out.insert(ext.id(), entry);
267 }
268 }
269 out
270 }
271
272 pub async fn collect_verify(&self, ctx: &VerifyContext<'_>) -> Extensions {
275 let mut out = Extensions::new();
276 for ext in self.iter() {
277 if let Some(entry) = ext.on_verify(ctx).await {
278 out.insert(ext.id(), entry);
279 }
280 }
281 out
282 }
283
284 pub async fn collect_settle(&self, ctx: &SettleContext<'_>) -> Extensions {
287 let mut out = Extensions::new();
288 for ext in self.iter() {
289 if let Some(entry) = ext.on_settle(ctx).await {
290 out.insert(ext.id(), entry);
291 }
292 }
293 out
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use serde_json::json;
300
301 use super::*;
302
303 struct StubExt(&'static str, serde_json::Value);
304
305 impl Extension for StubExt {
306 fn id(&self) -> &'static str {
307 self.0
308 }
309 fn advertise(&self, _: &AdvertiseContext<'_>) -> Option<ExtensionEntry> {
310 Some(ExtensionEntry::info(self.1.clone()))
311 }
312 }
313
314 #[test]
315 fn register_and_lookup() {
316 let mut registry = ExtensionRegistry::new();
317 registry.register(StubExt("bazaar", json!({"registered": true})));
318 registry.register(StubExt("other", json!({"x": 1})));
319 assert_eq!(registry.len(), 2);
320 assert!(registry.get("bazaar").is_some());
321 assert!(registry.get("missing").is_none());
322 }
323
324 #[test]
325 fn duplicate_registration_overwrites() {
326 let mut registry = ExtensionRegistry::new();
327 registry.register(StubExt("x", json!(1)));
328 registry.register(StubExt("x", json!(2)));
329 assert_eq!(registry.len(), 1);
330 let ctx = AdvertiseContext { requirement: None };
331 let ext = registry.advertise(&ctx);
332 let val = ext.get("x").unwrap().as_info().unwrap();
333 assert_eq!(val, &json!(2));
334 }
335
336 #[test]
337 fn advertise_emits_every_entry() {
338 let mut registry = ExtensionRegistry::new();
339 registry.register(StubExt("a", json!("A")));
340 registry.register(StubExt("b", json!("B")));
341 let ctx = AdvertiseContext { requirement: None };
342 let ext = registry.advertise(&ctx);
343 assert_eq!(ext.len(), 2);
344 }
345}