Skip to main content

cdk_common/
subscription.rs

1//! Subscription types and traits
2use std::ops::Deref;
3use std::str::FromStr;
4use std::sync::Arc;
5
6use cashu::nut17::{self, Kind, NotificationId};
7use cashu::quote_id::QuoteId;
8use cashu::PublicKey;
9use serde::{Deserialize, Serialize};
10
11use crate::pub_sub::{Error, SubscriptionRequest};
12
13/// CDK/Mint Subscription parameters.
14///
15/// This is a concrete type alias for `nut17::Params<SubId>`.
16pub type Params = nut17::Params<Arc<SubId>>;
17
18impl SubscriptionRequest for Params {
19    type Topic = NotificationId<QuoteId>;
20
21    type SubscriptionId = SubId;
22
23    fn subscription_name(&self) -> Arc<Self::SubscriptionId> {
24        self.id.clone()
25    }
26
27    fn try_get_topics(&self) -> Result<Vec<Self::Topic>, Error> {
28        self.filters
29            .iter()
30            .map(|filter| match self.kind {
31                Kind::Bolt11MeltQuote => QuoteId::from_str(filter)
32                    .map(NotificationId::MeltQuoteBolt11)
33                    .map_err(|_| Error::ParsingError(filter.to_owned())),
34                Kind::Bolt11MintQuote => QuoteId::from_str(filter)
35                    .map(NotificationId::MintQuoteBolt11)
36                    .map_err(|_| Error::ParsingError(filter.to_owned())),
37                Kind::ProofState => PublicKey::from_str(filter)
38                    .map(NotificationId::ProofState)
39                    .map_err(|_| Error::ParsingError(filter.to_owned())),
40
41                Kind::Bolt12MintQuote => QuoteId::from_str(filter)
42                    .map(NotificationId::MintQuoteBolt12)
43                    .map_err(|_| Error::ParsingError(filter.to_owned())),
44                Kind::Bolt12MeltQuote => QuoteId::from_str(filter)
45                    .map(NotificationId::MeltQuoteBolt12)
46                    .map_err(|_| Error::ParsingError(filter.to_owned())),
47                Kind::OnchainMintQuote => QuoteId::from_str(filter)
48                    .map(NotificationId::MintQuoteOnchain)
49                    .map_err(|_| Error::ParsingError(filter.to_owned())),
50                Kind::OnchainMeltQuote => QuoteId::from_str(filter)
51                    .map(NotificationId::MeltQuoteOnchain)
52                    .map_err(|_| Error::ParsingError(filter.to_owned())),
53                Kind::Custom(ref s) => {
54                    if let Some(method) = s.strip_suffix("_mint_quote") {
55                        QuoteId::from_str(filter)
56                            .map(|id| NotificationId::MintQuoteCustom(method.to_string(), id))
57                            .map_err(|_| Error::ParsingError(filter.to_owned()))
58                    } else if let Some(method) = s.strip_suffix("_melt_quote") {
59                        QuoteId::from_str(filter)
60                            .map(|id| NotificationId::MeltQuoteCustom(method.to_string(), id))
61                            .map_err(|_| Error::ParsingError(filter.to_owned()))
62                    } else {
63                        Err(Error::ParsingError(filter.to_owned()))
64                    }
65                }
66            })
67            .collect::<Result<Vec<_>, _>>()
68    }
69}
70
71/// Subscriptions parameters for the wallet
72///
73/// This is because the Wallet can subscribe to non CDK quotes, where IDs are not constraint to
74/// QuoteId
75pub type WalletParams = nut17::Params<Arc<String>>;
76
77impl SubscriptionRequest for WalletParams {
78    type Topic = NotificationId<String>;
79
80    type SubscriptionId = String;
81
82    fn subscription_name(&self) -> Arc<Self::SubscriptionId> {
83        self.id.clone()
84    }
85
86    fn try_get_topics(&self) -> Result<Vec<Self::Topic>, Error> {
87        self.filters
88            .iter()
89            .map(|filter| {
90                Ok(match self.kind {
91                    Kind::Bolt11MeltQuote => NotificationId::MeltQuoteBolt11(filter.to_owned()),
92                    Kind::Bolt11MintQuote => NotificationId::MintQuoteBolt11(filter.to_owned()),
93                    Kind::ProofState => PublicKey::from_str(filter)
94                        .map(NotificationId::ProofState)
95                        .map_err(|_| Error::ParsingError(filter.to_owned()))?,
96
97                    Kind::Bolt12MintQuote => NotificationId::MintQuoteBolt12(filter.to_owned()),
98                    Kind::Bolt12MeltQuote => NotificationId::MeltQuoteBolt12(filter.to_owned()),
99                    Kind::OnchainMintQuote => NotificationId::MintQuoteOnchain(filter.to_owned()),
100                    Kind::OnchainMeltQuote => NotificationId::MeltQuoteOnchain(filter.to_owned()),
101                    Kind::Custom(ref s) => {
102                        if let Some(method) = s.strip_suffix("_mint_quote") {
103                            NotificationId::MintQuoteCustom(method.to_string(), filter.to_owned())
104                        } else if let Some(method) = s.strip_suffix("_melt_quote") {
105                            NotificationId::MeltQuoteCustom(method.to_string(), filter.to_owned())
106                        } else {
107                            // If we can't parse the custom method, we can't create a NotificationId
108                            // This might happen if the custom kind doesn't follow the convention
109                            return Err(Error::ParsingError(format!("Invalid custom kind: {}", s)));
110                        }
111                    }
112                })
113            })
114            .collect::<Result<Vec<_>, _>>()
115    }
116}
117
118/// Subscription Id wrapper
119///
120/// This is the place to add some sane default (like a max length) to the
121/// subscription ID
122#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
123pub struct SubId(String);
124
125impl From<&str> for SubId {
126    fn from(s: &str) -> Self {
127        Self(s.to_string())
128    }
129}
130
131impl From<String> for SubId {
132    fn from(s: String) -> Self {
133        Self(s)
134    }
135}
136
137impl FromStr for SubId {
138    type Err = ();
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        Ok(Self(s.to_string()))
142    }
143}
144
145impl Deref for SubId {
146    type Target = String;
147
148    fn deref(&self) -> &Self::Target {
149        &self.0
150    }
151}