lnurl/
lightning_address.rs1use 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}