lnurl/
lightning_address.rs

1use crate::lnurl::LnUrl;
2use crate::Error;
3use email_address::EmailAddress;
4use serde::{Deserialize, Deserializer, Serialize};
5use std::fmt::{Display, Formatter};
6use std::str::FromStr;
7
8#[derive(Debug, PartialEq, Clone, Eq, Hash)]
9pub struct LightningAddress {
10    value: EmailAddress,
11}
12
13impl LightningAddress {
14    pub fn new(value: &str) -> Result<Self, Error> {
15        EmailAddress::from_str(value)
16            .map(|value| LightningAddress { value })
17            .map_err(|_| Error::InvalidLightningAddress)
18    }
19
20    pub fn from_domain_and_local_part(domain: &str, local_part: &str) -> Result<Self, Error> {
21        let string = format!("{}@{}", local_part, domain);
22        LightningAddress::new(&string)
23    }
24
25    #[inline]
26    pub fn lnurlp_url(&self) -> String {
27        format!(
28            "https://{}/.well-known/lnurlp/{}",
29            self.value.domain(),
30            self.value.local_part()
31        )
32    }
33
34    #[inline]
35    pub fn lnurl(&self) -> LnUrl {
36        LnUrl::from_url(self.lnurlp_url())
37    }
38}
39
40impl FromStr for LightningAddress {
41    type Err = Error;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        LightningAddress::new(s)
45    }
46}
47
48impl Serialize for LightningAddress {
49    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50    where
51        S: serde::Serializer,
52    {
53        serializer.serialize_str(self.value.as_str())
54    }
55}
56
57impl<'de> Deserialize<'de> for LightningAddress {
58    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59    where
60        D: Deserializer<'de>,
61    {
62        let s = String::deserialize(deserializer)?;
63        LightningAddress::new(&s).map_err(serde::de::Error::custom)
64    }
65}
66
67impl PartialOrd for LightningAddress {
68    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
69        Some(self.cmp(other))
70    }
71}
72
73impl Ord for LightningAddress {
74    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
75        self.value.as_str().cmp(other.value.as_str())
76    }
77}
78
79impl Display for LightningAddress {
80    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
81        write!(f, "{}", self.value)
82    }
83}
84
85#[cfg(test)]
86mod test {
87    use crate::lightning_address::LightningAddress;
88    use crate::lnurl::LnUrl;
89    use std::str::FromStr;
90
91    #[test]
92    fn test_parsing() {
93        let address = LightningAddress::from_str("ben@opreturnbot.com");
94        assert!(address.is_ok());
95        assert_eq!(
96            address.unwrap().lnurlp_url(),
97            "https://opreturnbot.com/.well-known/lnurlp/ben"
98        );
99    }
100
101    #[test]
102    fn test_invalid_parsing() {
103        assert!(LightningAddress::from_str("invalid").is_err());
104        assert!(LightningAddress::from_str("####").is_err());
105        assert!(LightningAddress::from_str("LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS").is_err());
106    }
107
108    #[test]
109    fn test_lnurl() {
110        let address = LightningAddress::from_str("ben@opreturnbot.com").unwrap();
111        let lnurl = LnUrl::from_str("lnurl1dp68gurn8ghj7mmswfjhgatjde3x7apwvdhk6tewwajkcmpdddhx7amw9akxuatjd3cz7cn9dc94s6d4").unwrap();
112
113        assert_eq!(address.lnurl(), lnurl);
114    }
115}