cashu/nuts/nut18/
transport.rs1use 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#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
14pub enum TransportType {
15 #[serde(rename = "nostr")]
17 Nostr,
18 #[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#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
45pub struct Transport {
46 #[serde(rename = "t")]
48 pub _type: TransportType,
49 #[serde(rename = "a")]
51 pub target: String,
52 #[serde(rename = "g")]
54 pub tags: Option<Vec<Vec<String>>>,
55}
56
57impl Transport {
58 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#[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 pub fn transport_type(mut self, transport_type: TransportType) -> Self {
87 self._type = Some(transport_type);
88 self
89 }
90
91 pub fn target<S: Into<String>>(mut self, target: S) -> Self {
93 self.target = Some(target.into());
94 self
95 }
96
97 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 pub fn tags(mut self, tags: Vec<Vec<String>>) -> Self {
105 self.tags = Some(tags);
106 self
107 }
108
109 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}