cashu/nuts/nut18/
transport.rs

1//! Transport types for NUT-18: Payment Requests
2
3use std::fmt;
4use std::str::FromStr;
5
6use bitcoin::base64::engine::{general_purpose, GeneralPurpose};
7use bitcoin::base64::{alphabet, Engine};
8use serde::{Deserialize, Serialize};
9
10use crate::nuts::nut18::error::Error;
11
12/// Transport Type
13#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
14pub enum TransportType {
15    /// Nostr
16    #[serde(rename = "nostr")]
17    Nostr,
18    /// Http post
19    #[serde(rename = "post")]
20    HttpPost,
21}
22
23impl fmt::Display for TransportType {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        use serde::ser::Error;
26        let t = serde_json::to_string(self).map_err(|e| fmt::Error::custom(e.to_string()))?;
27        write!(f, "{t}")
28    }
29}
30
31impl FromStr for TransportType {
32    type Err = Error;
33
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        match s.to_lowercase().as_str() {
36            "nostr" => Ok(Self::Nostr),
37            "post" => Ok(Self::HttpPost),
38            _ => Err(Error::InvalidPrefix),
39        }
40    }
41}
42
43/// Transport
44#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
45pub struct Transport {
46    /// Type
47    #[serde(rename = "t")]
48    pub _type: TransportType,
49    /// Target
50    #[serde(rename = "a")]
51    pub target: String,
52    /// Tags
53    #[serde(rename = "g")]
54    pub tags: Option<Vec<Vec<String>>>,
55}
56
57impl Transport {
58    /// Create a new TransportBuilder
59    pub fn builder() -> TransportBuilder {
60        TransportBuilder::default()
61    }
62}
63
64impl FromStr for Transport {
65    type Err = Error;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        let decode_config = general_purpose::GeneralPurposeConfig::new()
69            .with_decode_padding_mode(bitcoin::base64::engine::DecodePaddingMode::Indifferent);
70        let decoded = GeneralPurpose::new(&alphabet::URL_SAFE, decode_config).decode(s)?;
71
72        Ok(ciborium::from_reader(&decoded[..])?)
73    }
74}
75
76/// Builder for Transport
77#[derive(Debug, Default, Clone)]
78pub struct TransportBuilder {
79    _type: Option<TransportType>,
80    target: Option<String>,
81    tags: Option<Vec<Vec<String>>>,
82}
83
84impl TransportBuilder {
85    /// Set transport type
86    pub fn transport_type(mut self, transport_type: TransportType) -> Self {
87        self._type = Some(transport_type);
88        self
89    }
90
91    /// Set target
92    pub fn target<S: Into<String>>(mut self, target: S) -> Self {
93        self.target = Some(target.into());
94        self
95    }
96
97    /// Add a tag
98    pub fn add_tag(mut self, tag: Vec<String>) -> Self {
99        self.tags.get_or_insert_with(Vec::new).push(tag);
100        self
101    }
102
103    /// Set tags
104    pub fn tags(mut self, tags: Vec<Vec<String>>) -> Self {
105        self.tags = Some(tags);
106        self
107    }
108
109    /// Build the Transport
110    pub fn build(self) -> Result<Transport, &'static str> {
111        let _type = self._type.ok_or("Transport type is required")?;
112        let target = self.target.ok_or("Target is required")?;
113
114        Ok(Transport {
115            _type,
116            target,
117            tags: self.tags,
118        })
119    }
120}
121
122impl AsRef<String> for Transport {
123    fn as_ref(&self) -> &String {
124        &self.target
125    }
126}