Skip to main content

cashu/nuts/
nut15.rs

1//! NUT-15: Multipart payments
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/15.md>
4
5use serde::{Deserialize, Deserializer, Serialize};
6
7use super::{CurrencyUnit, PaymentMethod};
8use crate::Amount;
9
10/// Multi-part payment
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename = "lowercase")]
13#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
14pub struct Mpp {
15    /// Amount
16    pub amount: Amount,
17}
18
19/// Mpp Method Settings
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
22pub struct MppMethodSettings {
23    /// Payment Method e.g. bolt11
24    pub method: PaymentMethod,
25    /// Currency Unit e.g. sat
26    pub unit: CurrencyUnit,
27}
28
29/// Mpp Settings
30#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)]
31#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut15::Settings))]
32pub struct Settings {
33    /// Method settings
34    pub methods: Vec<MppMethodSettings>,
35}
36
37impl Settings {
38    /// Check if methods is empty
39    pub fn is_empty(&self) -> bool {
40        self.methods.is_empty()
41    }
42}
43
44// Custom deserialization to handle both array and object formats
45impl<'de> Deserialize<'de> for Settings {
46    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
47    where
48        D: Deserializer<'de>,
49    {
50        #[derive(Deserialize)]
51        #[serde(untagged)]
52        enum SettingsFormat {
53            Array(Vec<MppMethodSettings>),
54            Object { methods: Vec<MppMethodSettings> },
55        }
56
57        let format = SettingsFormat::deserialize(deserializer)?;
58        match format {
59            SettingsFormat::Array(methods) => Ok(Settings { methods }),
60            SettingsFormat::Object { methods } => Ok(Settings { methods }),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::nut00::KnownMethod;
69    use crate::PaymentMethod;
70
71    #[test]
72    fn test_nut15_settings_deserialization() {
73        // Test array format
74        let array_json = r#"[{"method":"bolt11","unit":"sat"}]"#;
75        let settings: Settings = serde_json::from_str(array_json).unwrap();
76        assert_eq!(settings.methods.len(), 1);
77        assert_eq!(
78            settings.methods[0].method,
79            PaymentMethod::Known(KnownMethod::Bolt11)
80        );
81        assert_eq!(settings.methods[0].unit, CurrencyUnit::Sat);
82
83        // Test object format
84        let object_json = r#"{"methods":[{"method":"bolt11","unit":"sat"}]}"#;
85        let settings: Settings = serde_json::from_str(object_json).unwrap();
86        assert_eq!(settings.methods.len(), 1);
87        assert_eq!(
88            settings.methods[0].method,
89            PaymentMethod::Known(KnownMethod::Bolt11)
90        );
91        assert_eq!(settings.methods[0].unit, CurrencyUnit::Sat);
92    }
93
94    #[test]
95    fn test_nut15_settings_serialization() {
96        let settings = Settings {
97            methods: vec![MppMethodSettings {
98                method: PaymentMethod::Known(KnownMethod::Bolt11),
99                unit: CurrencyUnit::Sat,
100            }],
101        };
102
103        let json = serde_json::to_string(&settings).unwrap();
104        assert_eq!(json, r#"{"methods":[{"method":"bolt11","unit":"sat"}]}"#);
105    }
106
107    #[test]
108    fn test_nut15_settings_empty() {
109        let settings = Settings { methods: vec![] };
110        assert!(settings.is_empty());
111
112        let settings_with_data = Settings {
113            methods: vec![MppMethodSettings {
114                method: PaymentMethod::Known(KnownMethod::Bolt11),
115                unit: CurrencyUnit::Sat,
116            }],
117        };
118        assert!(!settings_with_data.is_empty());
119    }
120}