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
107
108
109
110
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2024 Rust Nostr Developers
// Distributed under the MIT software license

//! NIP21
//!
//! <https://github.com/nostr-protocol/nips/blob/master/21.md>

use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;

use bitcoin::secp256k1::XOnlyPublicKey;

use super::nip01::Coordinate;
use super::nip19::{Error as NIP19Error, FromBech32, Nip19Event, Nip19Profile, ToBech32};
use crate::event::id::EventId;

/// URI scheme
pub const SCHEME: &str = "nostr";

/// NIP21 error
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
    /// NIP19 error
    NIP19(NIP19Error),
    /// Invalid nostr URI
    InvalidURI,
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NIP19(e) => write!(f, "NIP19: {e}"),
            Self::InvalidURI => write!(f, "Invalid nostr URI"),
        }
    }
}

impl From<NIP19Error> for Error {
    fn from(e: NIP19Error) -> Self {
        Self::NIP19(e)
    }
}

/// Nostr URI trait
pub trait NostrURI: Sized + ToBech32 + FromBech32
where
    Error: From<<Self as ToBech32>::Err>,
    Error: From<<Self as FromBech32>::Err>,
{
    /// Get nostr URI
    fn to_nostr_uri(&self) -> Result<String, Error> {
        Ok(format!("{SCHEME}:{}", self.to_bech32()?))
    }

    /// From `nostr` URI
    fn from_nostr_uri<S>(uri: S) -> Result<Self, Error>
    where
        S: Into<String>,
    {
        let uri: String = uri.into();
        let splitted: Vec<&str> = uri.split(':').collect();
        let data = splitted.get(1).ok_or(Error::InvalidURI)?;
        Ok(Self::from_bech32(*data)?)
    }
}

impl NostrURI for XOnlyPublicKey {}
impl NostrURI for EventId {}
impl NostrURI for Nip19Profile {}
impl NostrURI for Nip19Event {}
impl NostrURI for Coordinate {}

#[cfg(test)]
mod tests {
    use core::str::FromStr;

    use super::*;

    #[test]
    fn test_to_nostr_uri() {
        let pubkey = XOnlyPublicKey::from_str(
            "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4",
        )
        .unwrap();
        assert_eq!(
            pubkey.to_nostr_uri().unwrap(),
            String::from("nostr:npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy")
        );
    }

    #[test]
    fn test_from_nostr_uri() {
        let pubkey = XOnlyPublicKey::from_str(
            "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4",
        )
        .unwrap();
        assert_eq!(
            XOnlyPublicKey::from_nostr_uri(
                "nostr:npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy"
            )
            .unwrap(),
            pubkey
        );
    }
}