1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Copyright (c) 2024 Yuki Kishimoto
// Distributed under the MIT software license

use alloc::string::String;
use core::str::FromStr;

use bech32::{FromBase32, ToBase32, Variant};
use serde::{Deserialize, Deserializer, Serialize};

use crate::error::Error;

const PREFIX: &str = "lnurl";

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LnUrl {
    url: String,
}

impl LnUrl {
    pub fn new<S>(url: S) -> Self
    where
        S: Into<String>,
    {
        Self { url: url.into() }
    }

    #[inline]
    pub fn decode<S>(lnurl: S) -> Result<Self, Error>
    where
        S: AsRef<str>,
    {
        Self::from_str(lnurl.as_ref())
    }

    #[inline]
    pub fn encode(&self) -> Result<String, Error> {
        let base32 = self.url.as_bytes().to_base32();
        Ok(bech32::encode(PREFIX, base32, Variant::Bech32)?)
    }

    #[inline]
    pub fn endpoint(&self) -> String {
        self.url.clone()
    }
}

impl FromStr for LnUrl {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Error> {
        if s.to_lowercase().starts_with(PREFIX) {
            let (_, data, _) = bech32::decode(s).map_err(|_| Error::InvalidLnUrl)?;
            let bytes = FromBase32::from_base32(&data).map_err(|_| Error::InvalidLnUrl)?;
            let url = String::from_utf8(bytes).map_err(|_| Error::InvalidLnUrl)?;
            Ok(Self { url })
        } else {
            Err(Error::InvalidLnUrl)
        }
    }
}

impl Serialize for LnUrl {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.encode().map_err(serde::ser::Error::custom)?)
    }
}

impl<'de> Deserialize<'de> for LnUrl {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let lnurl = String::deserialize(deserializer)?;
        LnUrl::from_str(&lnurl).map_err(serde::de::Error::custom)
    }
}

#[cfg(test)]
mod tests {
    use alloc::string::ToString;

    use super::*;

    #[test]
    fn encode_test() {
        let url = "https://service.com/api?q=3fc3645b439ce8e7f2553a69e5267081d96dcd340693afabe04be7b0ccd178df";
        let expected =
            "LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS";

        let lnurl = LnUrl::new(url.to_string());
        assert_eq!(lnurl.encode().unwrap().to_uppercase(), expected);
    }

    #[test]
    fn decode_tests() {
        let str =
            "LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS";
        let expected = "https://service.com/api?q=3fc3645b439ce8e7f2553a69e5267081d96dcd340693afabe04be7b0ccd178df";

        let lnurl = LnUrl::decode(str.to_string()).unwrap();
        assert_eq!(lnurl.url, expected);
    }
}