1use std::collections::HashMap;
9use std::fmt::{self, Debug, Formatter};
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::Arc;
13
14use compact_str::CompactString;
15
16use crate::payment::{
17 ExtensionEntry, Extensions, PaymentPayload, PaymentRequirements, ResourceInfo, SettleResponse,
18 VerifyResponse,
19};
20
21pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
23
24#[derive(Debug)]
26#[non_exhaustive]
27pub struct AdvertiseContext<'a> {
28 pub requirement: Option<&'a PaymentRequirements>,
30 pub resource: Option<&'a ResourceInfo>,
32 pub accepts: &'a [PaymentRequirements],
34 pub existing: Option<&'a ExtensionEntry>,
36}
37
38impl<'a> AdvertiseContext<'a> {
39 #[must_use]
41 pub const fn new(requirement: Option<&'a PaymentRequirements>) -> Self {
42 Self {
43 requirement,
44 resource: None,
45 accepts: &[],
46 existing: None,
47 }
48 }
49
50 #[must_use]
52 pub const fn for_payment_required(
53 resource: &'a ResourceInfo,
54 accepts: &'a [PaymentRequirements],
55 existing: Option<&'a ExtensionEntry>,
56 ) -> Self {
57 Self {
58 requirement: None,
59 resource: Some(resource),
60 accepts,
61 existing,
62 }
63 }
64}
65
66#[non_exhaustive]
68pub struct VerifyContext<'a> {
69 pub payload: &'a PaymentPayload<serde_json::Value, serde_json::Value>,
71 pub requirements: &'a PaymentRequirements,
73 pub response: &'a VerifyResponse,
75}
76
77impl Debug for VerifyContext<'_> {
78 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
79 f.debug_struct("VerifyContext")
80 .field("requirements", &self.requirements)
81 .finish_non_exhaustive()
82 }
83}
84
85impl<'a> VerifyContext<'a> {
86 #[must_use]
88 pub const fn new(
89 payload: &'a PaymentPayload<serde_json::Value, serde_json::Value>,
90 requirements: &'a PaymentRequirements,
91 response: &'a VerifyResponse,
92 ) -> Self {
93 Self {
94 payload,
95 requirements,
96 response,
97 }
98 }
99}
100
101#[non_exhaustive]
103pub struct SettleContext<'a> {
104 pub payload: &'a PaymentPayload<serde_json::Value, serde_json::Value>,
106 pub requirements: &'a PaymentRequirements,
108 pub response: &'a SettleResponse,
110 pub resource_url: Option<&'a str>,
112 pub advertised: Option<&'a Extensions>,
114}
115
116impl Debug for SettleContext<'_> {
117 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
118 f.debug_struct("SettleContext")
119 .field("requirements", &self.requirements)
120 .finish_non_exhaustive()
121 }
122}
123
124impl<'a> SettleContext<'a> {
125 #[must_use]
127 pub const fn new(
128 payload: &'a PaymentPayload<serde_json::Value, serde_json::Value>,
129 requirements: &'a PaymentRequirements,
130 response: &'a SettleResponse,
131 ) -> Self {
132 Self {
133 payload,
134 requirements,
135 response,
136 resource_url: None,
137 advertised: None,
138 }
139 }
140
141 #[must_use]
143 pub const fn with_resource_url(mut self, resource_url: &'a str) -> Self {
144 self.resource_url = Some(resource_url);
145 self
146 }
147
148 #[must_use]
150 pub const fn with_advertised(mut self, advertised: &'a Extensions) -> Self {
151 self.advertised = Some(advertised);
152 self
153 }
154}
155
156pub trait Extension: Send + Sync {
161 fn id(&self) -> &'static str;
163
164 fn advertise(&self, _ctx: &AdvertiseContext<'_>) -> Option<ExtensionEntry> {
166 None
167 }
168
169 fn dynamic_info_fields(&self) -> &'static [&'static str] {
171 &[]
172 }
173
174 fn enrich_payment_required<'a>(
178 &'a self,
179 _ctx: &'a AdvertiseContext<'a>,
180 ) -> impl Future<Output = Option<ExtensionEntry>> + Send + 'a {
181 std::future::ready(None)
182 }
183
184 fn on_verify<'a>(
187 &'a self,
188 _ctx: &'a VerifyContext<'a>,
189 ) -> impl Future<Output = Option<ExtensionEntry>> + Send + 'a {
190 std::future::ready(None)
191 }
192
193 fn on_settle<'a>(
196 &'a self,
197 _ctx: &'a SettleContext<'a>,
198 ) -> impl Future<Output = Option<ExtensionEntry>> + Send + 'a {
199 std::future::ready(None)
200 }
201}
202
203pub trait DynExtension: Send + Sync {
205 fn id(&self) -> &'static str;
207
208 fn advertise(&self, ctx: &AdvertiseContext<'_>) -> Option<ExtensionEntry>;
210
211 fn dynamic_info_fields(&self) -> &'static [&'static str];
213
214 fn enrich_payment_required<'a>(
216 &'a self,
217 ctx: &'a AdvertiseContext<'a>,
218 ) -> BoxFuture<'a, Option<ExtensionEntry>>;
219
220 fn on_verify<'a>(&'a self, ctx: &'a VerifyContext<'a>)
222 -> BoxFuture<'a, Option<ExtensionEntry>>;
223
224 fn on_settle<'a>(&'a self, ctx: &'a SettleContext<'a>)
226 -> BoxFuture<'a, Option<ExtensionEntry>>;
227}
228
229impl<T: Extension + ?Sized> DynExtension for T {
230 fn id(&self) -> &'static str {
231 <Self as Extension>::id(self)
232 }
233
234 fn advertise(&self, ctx: &AdvertiseContext<'_>) -> Option<ExtensionEntry> {
235 <Self as Extension>::advertise(self, ctx)
236 }
237
238 fn dynamic_info_fields(&self) -> &'static [&'static str] {
239 <Self as Extension>::dynamic_info_fields(self)
240 }
241
242 fn enrich_payment_required<'a>(
243 &'a self,
244 ctx: &'a AdvertiseContext<'a>,
245 ) -> BoxFuture<'a, Option<ExtensionEntry>> {
246 Box::pin(<Self as Extension>::enrich_payment_required(self, ctx))
247 }
248
249 fn on_verify<'a>(
250 &'a self,
251 ctx: &'a VerifyContext<'a>,
252 ) -> BoxFuture<'a, Option<ExtensionEntry>> {
253 Box::pin(<Self as Extension>::on_verify(self, ctx))
254 }
255
256 fn on_settle<'a>(
257 &'a self,
258 ctx: &'a SettleContext<'a>,
259 ) -> BoxFuture<'a, Option<ExtensionEntry>> {
260 Box::pin(<Self as Extension>::on_settle(self, ctx))
261 }
262}
263
264#[derive(Clone, Default)]
266pub struct ExtensionRegistry {
267 ordered: Vec<Arc<dyn DynExtension>>,
268 by_id: HashMap<CompactString, Arc<dyn DynExtension>>,
269}
270
271impl Debug for ExtensionRegistry {
272 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
273 let ids: Vec<&str> = self.ordered.iter().map(|e| e.id()).collect();
274 f.debug_tuple("ExtensionRegistry").field(&ids).finish()
275 }
276}
277
278impl ExtensionRegistry {
279 #[must_use]
281 pub fn new() -> Self {
282 Self::default()
283 }
284
285 pub fn register<E: Extension + 'static>(&mut self, extension: E) {
287 let id = extension.id();
288 let arc: Arc<dyn DynExtension> = Arc::new(extension);
289 self.ordered.retain(|existing| existing.id() != id);
290 self.ordered.push(Arc::clone(&arc));
291 let _ = self.by_id.insert(CompactString::from(id), arc);
292 }
293
294 #[must_use]
296 pub fn get(&self, id: &str) -> Option<&dyn DynExtension> {
297 self.by_id.get(id).map(AsRef::as_ref)
298 }
299
300 pub fn iter(&self) -> impl Iterator<Item = &dyn DynExtension> {
302 self.ordered.iter().map(AsRef::as_ref)
303 }
304
305 #[must_use]
307 pub fn is_empty(&self) -> bool {
308 self.ordered.is_empty()
309 }
310
311 #[must_use]
313 pub fn len(&self) -> usize {
314 self.ordered.len()
315 }
316
317 #[must_use]
319 pub fn advertise(&self, ctx: &AdvertiseContext<'_>) -> Extensions {
320 let mut out = Extensions::new();
321 for ext in self.iter() {
322 if let Some(entry) = ext.advertise(ctx) {
323 out.insert(ext.id(), entry);
324 }
325 }
326 out
327 }
328
329 pub async fn collect_verify(&self, ctx: &VerifyContext<'_>) -> Extensions {
331 let mut out = Extensions::new();
332 for ext in self.iter() {
333 if let Some(entry) = ext.on_verify(ctx).await {
334 out.insert(ext.id(), entry);
335 }
336 }
337 out
338 }
339
340 pub async fn collect_settle(&self, ctx: &SettleContext<'_>) -> Extensions {
342 let mut out = Extensions::new();
343 for ext in self.iter() {
344 if let Some(entry) = ext.on_settle(ctx).await {
345 out.insert(ext.id(), entry);
346 }
347 }
348 out
349 }
350}
351
352#[cfg(test)]
353#[allow(clippy::unwrap_used, reason = "unit tests panic on assertion failure")]
354mod tests {
355 use serde_json::json;
356
357 use super::*;
358
359 struct StubExt(&'static str, serde_json::Value);
360
361 impl Extension for StubExt {
362 fn id(&self) -> &'static str {
363 self.0
364 }
365
366 fn advertise(&self, _: &AdvertiseContext<'_>) -> Option<ExtensionEntry> {
367 Some(ExtensionEntry::info(self.1.clone()))
368 }
369 }
370
371 #[test]
372 fn register_and_lookup() {
373 let mut registry = ExtensionRegistry::new();
374 registry.register(StubExt("bazaar", json!({"registered": true})));
375 registry.register(StubExt("other", json!({"x": 1})));
376 assert_eq!(registry.len(), 2);
377 assert!(registry.get("bazaar").is_some());
378 assert!(registry.get("missing").is_none());
379 }
380
381 #[test]
382 fn duplicate_registration_overwrites() {
383 let mut registry = ExtensionRegistry::new();
384 registry.register(StubExt("x", json!(1)));
385 registry.register(StubExt("x", json!(2)));
386 assert_eq!(registry.len(), 1);
387 let ctx = AdvertiseContext::new(None);
388 let ext = registry.advertise(&ctx);
389 let val = ext.get("x").unwrap().as_info().unwrap();
390 assert_eq!(val, &json!(2));
391 }
392
393 #[test]
394 fn advertise_emits_every_entry() {
395 let mut registry = ExtensionRegistry::new();
396 registry.register(StubExt("a", json!("A")));
397 registry.register(StubExt("b", json!("B")));
398 let ctx = AdvertiseContext::new(None);
399 let ext = registry.advertise(&ctx);
400 assert_eq!(ext.len(), 2);
401 }
402}