From 9f548154845f1501650269c93f93ecf6e18fcfa3 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Tue, 19 May 2026 16:43:14 +0200 Subject: [PATCH 01/11] nostr: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 6 + Cargo.toml | 1 + crates/nostr-keyring/examples/async.rs | 2 +- crates/nostr-keyring/examples/blocking.rs | 2 +- crates/nostr-keyring/src/lib.rs | 20 +- crates/nostr-relay-builder/examples/hyper.rs | 2 +- crates/nostr-relay-builder/examples/mock.rs | 2 +- crates/nostr-relay-builder/examples/policy.rs | 2 +- .../examples/simple_relay.rs | 2 +- crates/nostr-relay-builder/src/error.rs | 44 +++ crates/nostr-relay-builder/src/local/inner.rs | 26 +- crates/nostr-relay-builder/src/local/mod.rs | 2 +- .../nostr-relay-builder/src/local/session.rs | 2 +- crates/nostr-relay-builder/src/mock.rs | 3 - crates/nostr-relay-builder/src/prelude.rs | 1 + crates/nostr/Cargo.toml | 3 + crates/nostr/README.md | 2 +- crates/nostr/examples/keys.rs | 2 +- crates/nostr/examples/nip05.rs | 2 +- crates/nostr/examples/nip06.rs | 6 +- crates/nostr/examples/nip09.rs | 2 +- crates/nostr/examples/nip11.rs | 2 +- crates/nostr/examples/nip13.rs | 2 +- crates/nostr/examples/nip15.rs | 2 +- crates/nostr/examples/nip19.rs | 2 +- crates/nostr/examples/nip57.rs | 2 +- crates/nostr/examples/nip98.rs | 2 +- crates/nostr/examples/parser.rs | 2 +- crates/nostr/src/error.rs | 106 +++++++ crates/nostr/src/event/builder.rs | 104 +++---- crates/nostr/src/event/error.rs | 61 ---- crates/nostr/src/event/id.rs | 14 +- crates/nostr/src/event/kind.rs | 7 +- crates/nostr/src/event/mod.rs | 33 +- crates/nostr/src/event/tag/codec.rs | 35 +-- crates/nostr/src/event/tag/cow.rs | 3 +- crates/nostr/src/event/tag/mod.rs | 8 +- crates/nostr/src/event/unsigned.rs | 26 +- crates/nostr/src/filter.rs | 33 +- crates/nostr/src/key/mod.rs | 68 ++-- crates/nostr/src/key/public_key.rs | 23 +- crates/nostr/src/key/secret_key.rs | 15 +- crates/nostr/src/lib.rs | 6 +- crates/nostr/src/message/client.rs | 68 ++-- crates/nostr/src/message/mod.rs | 27 +- crates/nostr/src/message/relay.rs | 26 +- crates/nostr/src/nips/nip01/mod.rs | 91 +----- crates/nostr/src/nips/nip01/tags.rs | 32 +- crates/nostr/src/nips/nip02.rs | 67 +--- crates/nostr/src/nips/nip04/impl.rs | 70 ++--- crates/nostr/src/nips/nip05.rs | 60 +--- crates/nostr/src/nips/nip06/bip32.rs | 5 +- crates/nostr/src/nips/nip06/mod.rs | 50 +-- crates/nostr/src/nips/nip10.rs | 86 ++---- crates/nostr/src/nips/nip11.rs | 2 +- crates/nostr/src/nips/nip13/mod.rs | 52 +--- crates/nostr/src/nips/nip15.rs | 20 +- crates/nostr/src/nips/nip17.rs | 98 ++---- crates/nostr/src/nips/nip18.rs | 95 +----- crates/nostr/src/nips/nip19.rs | 190 ++++-------- crates/nostr/src/nips/nip21.rs | 78 +---- crates/nostr/src/nips/nip22.rs | 93 +----- crates/nostr/src/nips/nip23.rs | 75 +---- crates/nostr/src/nips/nip25.rs | 47 +-- crates/nostr/src/nips/nip30.rs | 73 ++--- crates/nostr/src/nips/nip31.rs | 38 +-- crates/nostr/src/nips/nip32.rs | 33 +- crates/nostr/src/nips/nip34.rs | 175 +++-------- crates/nostr/src/nips/nip39.rs | 41 +-- crates/nostr/src/nips/nip40.rs | 54 +--- crates/nostr/src/nips/nip42.rs | 45 +-- crates/nostr/src/nips/nip44/impl.rs | 72 +---- crates/nostr/src/nips/nip44/traits.rs | 4 +- crates/nostr/src/nips/nip44/v2.rs | 98 +++--- crates/nostr/src/nips/nip46.rs | 212 +++---------- crates/nostr/src/nips/nip47.rs | 184 ++++------- crates/nostr/src/nips/nip49.rs | 162 ++++------ crates/nostr/src/nips/nip51.rs | 72 +---- crates/nostr/src/nips/nip53.rs | 172 +++-------- crates/nostr/src/nips/nip56.rs | 102 ++---- crates/nostr/src/nips/nip57.rs | 111 +------ crates/nostr/src/nips/nip58.rs | 79 +---- crates/nostr/src/nips/nip59.rs | 102 ++---- crates/nostr/src/nips/nip60.rs | 151 +++------ crates/nostr/src/nips/nip62.rs | 45 +-- crates/nostr/src/nips/nip65.rs | 52 +--- crates/nostr/src/nips/nip66.rs | 69 ++--- crates/nostr/src/nips/nip70.rs | 32 +- crates/nostr/src/nips/nip73.rs | 71 ++--- crates/nostr/src/nips/nip7d.rs | 33 +- crates/nostr/src/nips/nip88.rs | 83 ++--- crates/nostr/src/nips/nip89.rs | 33 +- crates/nostr/src/nips/nip90.rs | 89 ++---- crates/nostr/src/nips/nip94.rs | 131 ++------ crates/nostr/src/nips/nip98.rs | 290 ++++++------------ crates/nostr/src/nips/nipb0.rs | 47 +-- crates/nostr/src/nips/nipb7.rs | 47 +-- crates/nostr/src/nips/nipc0.rs | 34 +- crates/nostr/src/nips/util.rs | 94 ++++-- crates/nostr/src/parser.rs | 35 +-- crates/nostr/src/prelude.rs | 6 +- crates/nostr/src/signer.rs | 40 --- crates/nostr/src/types/image.rs | 37 +-- crates/nostr/src/types/url.rs | 77 ++--- crates/nostr/src/util/json.rs | 38 ++- crates/nostr/src/util/mod.rs | 7 +- crates/nwc/examples/notifications.rs | 2 +- crates/nwc/examples/nwc.rs | 2 +- crates/nwc/src/error.rs | 13 +- .../nostr-database/src/flatbuffers/mod.rs | 18 +- database/nostr-lmdb/src/store/error.rs | 19 +- database/nostr-sqlite/src/error.rs | 29 +- gossip/nostr-gossip-sqlite/src/store.rs | 8 +- rfs/nostr-blossom/examples/delete.rs | 2 +- rfs/nostr-blossom/examples/download.rs | 2 +- rfs/nostr-blossom/examples/list.rs | 2 +- rfs/nostr-blossom/examples/upload.rs | 2 +- rfs/nostr-blossom/src/error.rs | 13 +- sdk/examples/aggregated-query.rs | 2 +- sdk/examples/blacklist.rs | 2 +- sdk/examples/bot.rs | 2 +- sdk/examples/client.rs | 2 +- sdk/examples/code_snippet.rs | 2 +- sdk/examples/comment.rs | 2 +- sdk/examples/fetch-events.rs | 2 +- sdk/examples/gossip.rs | 2 +- sdk/examples/limits.rs | 2 +- sdk/examples/lmdb.rs | 2 +- sdk/examples/monitor.rs | 2 +- sdk/examples/nip42.rs | 2 +- sdk/examples/nostr-connect.rs | 2 +- sdk/examples/nostrdb.rs | 2 +- sdk/examples/status.rs | 2 +- sdk/examples/stream-events.rs | 2 +- sdk/examples/subscriptions.rs | 2 +- sdk/examples/tor.rs | 2 +- sdk/examples/whitelist.rs | 2 +- sdk/src/client/api/util.rs | 5 +- sdk/src/client/error.rs | 28 +- sdk/src/relay/api/sync.rs | 12 +- sdk/src/relay/error.rs | 29 +- .../examples/browser-signer-proxy.rs | 2 +- .../examples/custom_html.rs | 2 +- .../nostr-browser-signer-proxy/src/error.rs | 19 +- signer/nostr-browser-signer/src/lib.rs | 44 +-- .../nostr-connect/examples/handle-auth-url.rs | 9 +- .../examples/nostr-connect-signer.rs | 2 +- signer/nostr-connect/src/client.rs | 2 +- signer/nostr-connect/src/error.rs | 51 +-- 149 files changed, 1707 insertions(+), 4040 deletions(-) create mode 100644 crates/nostr/src/error.rs delete mode 100644 crates/nostr/src/event/error.rs delete mode 100644 crates/nostr/src/signer.rs diff --git a/Cargo.lock b/Cargo.lock index db700dbe6..4eba29ac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1842,6 +1842,7 @@ dependencies = [ "chacha20poly1305", "faster-hex", "nostr-ots", + "opaquerr", "rand 0.10.1", "reqwest", "scrypt", @@ -2174,6 +2175,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "opaquerr" +version = "0.1.0" +source = "git+https://github.com/shadowylab/opaquerr?rev=1d4a95bb39d7f2fa1638d84af8540e898a3d902f#1d4a95bb39d7f2fa1638d84af8540e898a3d902f" + [[package]] name = "openssl" version = "0.10.80" diff --git a/Cargo.toml b/Cargo.toml index 7598c56c9..f17f5d1e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ nostr-lmdb = { version = "0.45.0-alpha.1", path = "./database/nostr-lmdb", defau nostr-ndb = { version = "0.45.0-alpha.1", path = "./database/nostr-ndb", default-features = false } nostr-relay-builder = { version = "0.45.0-alpha.1", path = "./crates/nostr-relay-builder", default-features = false } nostr-sdk = { version = "0.45.0-alpha.1", path = "./sdk", default-features = false } +opaquerr = { git = "https://github.com/shadowylab/opaquerr", rev = "1d4a95bb39d7f2fa1638d84af8540e898a3d902f" } reqwest = { version = "0.12", default-features = false } rusqlite = { version = "0.38", default-features = false } serde = { version = "1.0", default-features = false } diff --git a/crates/nostr-keyring/examples/async.rs b/crates/nostr-keyring/examples/async.rs index 2631c7581..d648c80d7 100644 --- a/crates/nostr-keyring/examples/async.rs +++ b/crates/nostr-keyring/examples/async.rs @@ -5,7 +5,7 @@ use nostr_keyring::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; let keyring = NostrKeyring::new("rust-nostr-test"); diff --git a/crates/nostr-keyring/examples/blocking.rs b/crates/nostr-keyring/examples/blocking.rs index 62f1565a5..60c8c2355 100644 --- a/crates/nostr-keyring/examples/blocking.rs +++ b/crates/nostr-keyring/examples/blocking.rs @@ -4,7 +4,7 @@ use nostr_keyring::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; let keyring = NostrKeyring::new("rust-nostr-test"); diff --git a/crates/nostr-keyring/src/lib.rs b/crates/nostr-keyring/src/lib.rs index 02e361676..e3b7bf075 100644 --- a/crates/nostr-keyring/src/lib.rs +++ b/crates/nostr-keyring/src/lib.rs @@ -15,20 +15,20 @@ use std::fmt; #[cfg(feature = "async")] use async_utility::{task, tokio}; pub use keyring::{Entry, Error as KeyringError}; -use nostr::{Keys, SecretKey, key}; +use nostr::key::{Keys, SecretKey}; pub mod prelude; /// Keyring error #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// Join error #[cfg(feature = "async")] Join(tokio::task::JoinError), /// Keyring error Keyring(KeyringError), - /// Nostr keys error - Keys(key::Error), } impl std::error::Error for Error {} @@ -36,14 +36,20 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => e.fmt(f), #[cfg(feature = "async")] Self::Join(e) => write!(f, "{e}"), Self::Keyring(e) => write!(f, "{e}"), - Self::Keys(e) => write!(f, "{e}"), } } } +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + #[cfg(feature = "async")] impl From for Error { fn from(e: tokio::task::JoinError) -> Self { @@ -57,12 +63,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - /// Nostr keyring #[derive(Debug, Clone)] pub struct NostrKeyring { diff --git a/crates/nostr-relay-builder/examples/hyper.rs b/crates/nostr-relay-builder/examples/hyper.rs index 02b10dace..1aba1b50f 100644 --- a/crates/nostr-relay-builder/examples/hyper.rs +++ b/crates/nostr-relay-builder/examples/hyper.rs @@ -111,7 +111,7 @@ impl Service> for HttpServer { } #[tokio::main] -async fn main() -> nostr_relay_builder::prelude::Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let relay = LocalRelay::new(); diff --git a/crates/nostr-relay-builder/examples/mock.rs b/crates/nostr-relay-builder/examples/mock.rs index 17188234c..55609b7d4 100644 --- a/crates/nostr-relay-builder/examples/mock.rs +++ b/crates/nostr-relay-builder/examples/mock.rs @@ -7,7 +7,7 @@ use std::time::Duration; use nostr_relay_builder::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let relay = MockRelay::run().await?; diff --git a/crates/nostr-relay-builder/examples/policy.rs b/crates/nostr-relay-builder/examples/policy.rs index c4642fe1c..b22333f0b 100644 --- a/crates/nostr-relay-builder/examples/policy.rs +++ b/crates/nostr-relay-builder/examples/policy.rs @@ -54,7 +54,7 @@ impl QueryPolicy for RejectAuthorLimit { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let accept_profile_data = AcceptKinds { diff --git a/crates/nostr-relay-builder/examples/simple_relay.rs b/crates/nostr-relay-builder/examples/simple_relay.rs index dea0e124c..77d49f88a 100644 --- a/crates/nostr-relay-builder/examples/simple_relay.rs +++ b/crates/nostr-relay-builder/examples/simple_relay.rs @@ -4,7 +4,7 @@ use nostr_lmdb::NostrLmdb; use nostr_relay_builder::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let db = NostrLmdb::open("./db/nostr-lmdb").await?; diff --git a/crates/nostr-relay-builder/src/error.rs b/crates/nostr-relay-builder/src/error.rs index e9eee0c03..b6dea5671 100644 --- a/crates/nostr-relay-builder/src/error.rs +++ b/crates/nostr-relay-builder/src/error.rs @@ -6,8 +6,10 @@ use std::{fmt, io}; +use nostr::Event; use nostr_database::DatabaseError; use nostr_sdk::client; +use tokio::sync::broadcast; /// Relay builder error #[derive(Debug)] @@ -18,6 +20,10 @@ pub enum Error { Database(DatabaseError), /// Client error Client(client::Error), + /// Nostr protocol error + Protocol(nostr::error::Error), + /// Other error + Other(String), /// Relay already running AlreadyRunning, /// Premature exit @@ -32,6 +38,8 @@ impl fmt::Display for Error { Self::IO(e) => write!(f, "{e}"), Self::Database(e) => write!(f, "{e}"), Self::Client(e) => e.fmt(f), + Self::Protocol(e) => e.fmt(f), + Self::Other(e) => f.write_str(e), Self::AlreadyRunning => write!(f, "the relay is already running"), Self::PrematureExit => write!(f, "premature exit"), } @@ -55,3 +63,39 @@ impl From for Error { Self::Client(e) } } + +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + +impl From for Error { + fn from(e: async_wsocket::Error) -> Self { + Self::Other(e.to_string()) + } +} + +impl From for Error { + fn from(e: tokio::sync::TryAcquireError) -> Self { + Self::Other(e.to_string()) + } +} + +impl From> for Error { + fn from(e: broadcast::error::SendError) -> Self { + Self::Other(e.to_string()) + } +} + +impl From for Error { + fn from(e: negentropy::Error) -> Self { + Self::Other(e.to_string()) + } +} + +impl From for Error { + fn from(e: faster_hex::Error) -> Self { + Self::Other(e.to_string()) + } +} diff --git a/crates/nostr-relay-builder/src/local/inner.rs b/crates/nostr-relay-builder/src/local/inner.rs index a9a2046fa..886fc483f 100644 --- a/crates/nostr-relay-builder/src/local/inner.rs +++ b/crates/nostr-relay-builder/src/local/inner.rs @@ -237,7 +237,7 @@ impl InnerLocalRelay { &self, stream: S, addr: SocketAddr, - ) -> Result<()> + ) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { @@ -254,7 +254,7 @@ impl InnerLocalRelay { } /// Pass bare [TcpStream] for handling - async fn handle_connection(self, raw_stream: S, addr: SocketAddr) -> Result<()> + async fn handle_connection(self, raw_stream: S, addr: SocketAddr) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { @@ -275,7 +275,7 @@ impl InnerLocalRelay { &self, ws_stream: WebSocketStream, addr: SocketAddr, - ) -> Result<()> + ) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { @@ -361,7 +361,7 @@ impl InnerLocalRelay { ws_tx: &mut WsTx, msg: ClientMessage<'_>, addr: &SocketAddr, - ) -> Result<()> + ) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { @@ -807,7 +807,7 @@ impl InnerLocalRelay { addr: &SocketAddr, subscription_id: Cow<'_, SubscriptionId>, mut filters: Vec, - ) -> Result<()> + ) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { @@ -1008,11 +1008,13 @@ impl InnerLocalRelay { } #[inline] -async fn send_msg(tx: &mut WsTx, msg: RelayMessage<'_>) -> Result<()> +async fn send_msg(tx: &mut WsTx, msg: RelayMessage<'_>) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { - tx.send(Message::Text(msg.as_json().into())).await?; + tx.send(Message::Text(msg.as_json().into())) + .await + .map_err(|e| Error::Other(e.to_string()))?; Ok(()) } @@ -1020,7 +1022,7 @@ async fn send_auth_and_close( tx: &mut WsTx, subscription_id: Cow<'_, SubscriptionId>, challenge: String, -) -> Result<()> +) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { @@ -1074,7 +1076,7 @@ fn find_filters_with_kind<'a>(filters: &'a [Filter], kind: &Kind) -> Option( tx: &mut WsTx, subscription_id: Cow<'_, SubscriptionId>, -) -> Result<()> +) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { @@ -1092,12 +1094,14 @@ where } #[inline] -async fn send_json_msgs(tx: &mut WsTx, json_msgs: I) -> Result<()> +async fn send_json_msgs(tx: &mut WsTx, json_msgs: I) -> Result<(), Error> where I: IntoIterator, S: AsyncRead + AsyncWrite + Unpin, { let mut stream = stream::iter(json_msgs).map(|msg| Ok(Message::Text(msg.into()))); - tx.send_all(&mut stream).await?; + tx.send_all(&mut stream) + .await + .map_err(|e| Error::Other(e.to_string()))?; Ok(()) } diff --git a/crates/nostr-relay-builder/src/local/mod.rs b/crates/nostr-relay-builder/src/local/mod.rs index a1c4ce013..1cdba45ae 100644 --- a/crates/nostr-relay-builder/src/local/mod.rs +++ b/crates/nostr-relay-builder/src/local/mod.rs @@ -112,7 +112,7 @@ impl LocalRelay { } /// Pass an already upgraded stream - pub async fn take_connection(&self, stream: S, addr: SocketAddr) -> Result<()> + pub async fn take_connection(&self, stream: S, addr: SocketAddr) -> Result<(), Error> where S: AsyncRead + AsyncWrite + Unpin, { diff --git a/crates/nostr-relay-builder/src/local/session.rs b/crates/nostr-relay-builder/src/local/session.rs index e8efd1856..e995439da 100644 --- a/crates/nostr-relay-builder/src/local/session.rs +++ b/crates/nostr-relay-builder/src/local/session.rs @@ -6,7 +6,7 @@ use std::collections::{HashMap, HashSet}; use std::time::{Duration, Instant}; use negentropy::{Negentropy, NegentropyStorageVector}; -use nostr::{Event, Filter, PublicKey, Result, SubscriptionId, Timestamp}; +use nostr::{Event, Filter, PublicKey, SubscriptionId, Timestamp}; pub(super) enum RateLimiterResponse { Allowed, diff --git a/crates/nostr-relay-builder/src/mock.rs b/crates/nostr-relay-builder/src/mock.rs index e27acbb6b..3aa90309d 100644 --- a/crates/nostr-relay-builder/src/mock.rs +++ b/crates/nostr-relay-builder/src/mock.rs @@ -6,9 +6,6 @@ use std::ops::Deref; -use nostr::prelude::*; -use nostr_database::prelude::*; - use crate::builder::{LocalRelayBuilder, LocalRelayTestOptions}; use crate::error::Error; use crate::local::LocalRelay; diff --git a/crates/nostr-relay-builder/src/prelude.rs b/crates/nostr-relay-builder/src/prelude.rs index eb9a8811a..b145b93e9 100644 --- a/crates/nostr-relay-builder/src/prelude.rs +++ b/crates/nostr-relay-builder/src/prelude.rs @@ -5,6 +5,7 @@ //! Prelude #![allow(unknown_lints)] +#![allow(unused_imports)] #![allow(ambiguous_glob_reexports)] #![doc(hidden)] diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml index 8cf3354fc..2b7e99922 100644 --- a/crates/nostr/Cargo.toml +++ b/crates/nostr/Cargo.toml @@ -18,6 +18,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std"] std = [ + "alloc", "base64?/std", "bech32/std", "bip39?/std", @@ -41,6 +42,7 @@ alloc = [ "cbc?/alloc", "chacha20poly1305?/alloc", "faster-hex/alloc", + "opaquerr/alloc", "rand?/alloc", "secp256k1/alloc", "serde/alloc", @@ -75,6 +77,7 @@ chacha20 = { version = "0.9", optional = true } chacha20poly1305 = { version = "0.10", default-features = false, optional = true } faster-hex.workspace = true nostr-ots = { version = "0.2", optional = true } +opaquerr.workspace = true rand = { version = "0.10", default-features = false, optional = true } scrypt = { version = "0.12", default-features = false, optional = true } secp256k1 = { version = "0.29", default-features = false, features = ["serde"] } diff --git a/crates/nostr/README.md b/crates/nostr/README.md index 4cfd55265..e280c0844 100644 --- a/crates/nostr/README.md +++ b/crates/nostr/README.md @@ -22,7 +22,7 @@ use std::num::NonZeroU8; use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { // Generate new random keys let keys = Keys::generate(); diff --git a/crates/nostr/examples/keys.rs b/crates/nostr/examples/keys.rs index 9d77feb9a..920d58ef5 100644 --- a/crates/nostr/examples/keys.rs +++ b/crates/nostr/examples/keys.rs @@ -4,7 +4,7 @@ use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { // Random keys let keys = Keys::generate(); let public_key = keys.public_key(); diff --git a/crates/nostr/examples/nip05.rs b/crates/nostr/examples/nip05.rs index 821d2411c..a8d59ab40 100644 --- a/crates/nostr/examples/nip05.rs +++ b/crates/nostr/examples/nip05.rs @@ -4,7 +4,7 @@ use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let public_key = PublicKey::parse("b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a")?; diff --git a/crates/nostr/examples/nip06.rs b/crates/nostr/examples/nip06.rs index 331333f94..69b506da8 100644 --- a/crates/nostr/examples/nip06.rs +++ b/crates/nostr/examples/nip06.rs @@ -2,13 +2,11 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -use nostr::nips::nip06::FromMnemonic; -use nostr::nips::nip19::ToBech32; -use nostr::{Keys, Result}; +use nostr::prelude::*; const MNEMONIC_PHRASE: &str = "equal dragon fabric refuse stable cherry smoke allow alley easy never medal attend together lumber movie what sad siege weather matrix buffalo state shoot"; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let keys = Keys::from_mnemonic(MNEMONIC_PHRASE, Some("mypassphrase"))?; println!("{}", keys.secret_key().to_bech32()?); diff --git a/crates/nostr/examples/nip09.rs b/crates/nostr/examples/nip09.rs index 1d1ac6224..474cb513d 100644 --- a/crates/nostr/examples/nip09.rs +++ b/crates/nostr/examples/nip09.rs @@ -4,7 +4,7 @@ use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; let event_id = diff --git a/crates/nostr/examples/nip11.rs b/crates/nostr/examples/nip11.rs index 814132259..c1cf39771 100644 --- a/crates/nostr/examples/nip11.rs +++ b/crates/nostr/examples/nip11.rs @@ -5,7 +5,7 @@ use nostr::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let url = Url::parse("https://relay.damus.io")?; let client = reqwest::Client::new(); diff --git a/crates/nostr/examples/nip13.rs b/crates/nostr/examples/nip13.rs index 406853678..2955855eb 100644 --- a/crates/nostr/examples/nip13.rs +++ b/crates/nostr/examples/nip13.rs @@ -6,7 +6,7 @@ use std::num::NonZeroU8; use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; let difficulty = NonZeroU8::new(20).unwrap(); // leading zero bits diff --git a/crates/nostr/examples/nip15.rs b/crates/nostr/examples/nip15.rs index 7780b3231..79b2a3ee3 100644 --- a/crates/nostr/examples/nip15.rs +++ b/crates/nostr/examples/nip15.rs @@ -4,7 +4,7 @@ use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; let shipping = ShippingMethod::new("123", 5.50).name("DHL"); diff --git a/crates/nostr/examples/nip19.rs b/crates/nostr/examples/nip19.rs index cf38a95ac..54f6e965a 100644 --- a/crates/nostr/examples/nip19.rs +++ b/crates/nostr/examples/nip19.rs @@ -4,7 +4,7 @@ use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let pubkey = PublicKey::from_hex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")?; let profile = Nip19Profile::new( diff --git a/crates/nostr/examples/nip57.rs b/crates/nostr/examples/nip57.rs index 79e361ec7..c99dfb5c2 100644 --- a/crates/nostr/examples/nip57.rs +++ b/crates/nostr/examples/nip57.rs @@ -4,7 +4,7 @@ use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; let public_key = diff --git a/crates/nostr/examples/nip98.rs b/crates/nostr/examples/nip98.rs index 6b37814a4..5a8f65b0c 100644 --- a/crates/nostr/examples/nip98.rs +++ b/crates/nostr/examples/nip98.rs @@ -5,7 +5,7 @@ use nostr::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; let server_url: Url = Url::parse("https://example.com")?; diff --git a/crates/nostr/examples/parser.rs b/crates/nostr/examples/parser.rs index c6765530f..93ffc75ab 100644 --- a/crates/nostr/examples/parser.rs +++ b/crates/nostr/examples/parser.rs @@ -4,7 +4,7 @@ use nostr::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Error> { let parser = NostrParser::new(); let text: &str = "I have never been very active in discussions but working on rust-nostr (at the time called nostr-rs-sdk) since September 2022 🦀 \n\nIf I remember correctly there were also nostr:nprofile1qqsqfyvdlsmvj0nakmxq6c8n0c2j9uwrddjd8a95ynzn9479jhlth3gpvemhxue69uhkv6tvw3jhytnwdaehgu3wwa5kuef0dec82c33w94xwcmdd3cxketedsux6ertwecrgues0pk8xdrew33h27pkd4unvvpkw3nkv7pe0p68gat58ycrw6ps0fenwdnvva48w0mzwfhkzerrv9ehg0t5wf6k2qgnwaehxw309ac82unsd3jhqct89ejhxtcpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsh8njvk and nostr:nprofile1qqswuyd9ml6qcxd92h6pleptfrcqucvvjy39vg4wx7mv9wm8kakyujgpypmhxue69uhkx6r0wf6hxtndd94k2erfd3nk2u3wvdhk6w35xs6z7qgwwaehxw309ahx7uewd3hkctcpypmhxue69uhkummnw3ezuetfde6kuer6wasku7nfvuh8xurpvdjj7a0nq40"; diff --git a/crates/nostr/src/error.rs b/crates/nostr/src/error.rs new file mode 100644 index 000000000..59ea8f7d8 --- /dev/null +++ b/crates/nostr/src/error.rs @@ -0,0 +1,106 @@ +//! Nostr error. + +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use core::{error, fmt}; + +opaquerr::define_kind! { + /// Nostr error kind. + pub ErrorKind { + /// Input is not well-formed and cannot be parsed. + Malformed => "input is malformed", + /// Input is well-formed, but violates a protocol/library invariant. + Invalid => "input violates a protocol/library invariant", + /// Required data is missing. + Missing => "required data is missing", + /// The value/operation is known but not supported. + Unsupported => "the value/operation is known but not supported", + /// Cryptographic operation failed. + Crypto => "cryptographic operation failed.", + /// Anything not covered by the stable categories above. + Other => "other error", + } +} + +opaquerr::define_error! { + /// Nostr error. + pub Error(ErrorKind) + + from { + serde_json::Error => ErrorKind::Malformed, + } +} + +#[derive(Debug)] +struct DisplayError(String); + +impl fmt::Display for DisplayError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl error::Error for DisplayError {} + +impl Error { + #[inline] + pub(crate) fn invalid(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Invalid, error.into()) + } + + #[inline] + pub(crate) fn invalid_display(error: E) -> Self + where + E: fmt::Display, + { + Self::invalid(DisplayError(error.to_string())) + } + + #[inline] + pub(crate) fn malformed(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Malformed, error.into()) + } + + #[inline] + pub(crate) fn malformed_display(error: E) -> Self + where + E: fmt::Display, + { + Self::malformed(DisplayError(error.to_string())) + } + + #[inline] + #[cfg(any(feature = "nip46", feature = "nip59"))] + pub(crate) fn crypto(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Crypto, error.into()) + } + + #[inline] + #[cfg(feature = "nip49")] + pub(crate) fn crypto_display(error: E) -> Self + where + E: fmt::Display, + { + Self::crypto(DisplayError(error.to_string())) + } + + /// Creates a new Nostr error from a known kind of error as well as an arbitrary error payload. + /// + /// It is a shortcut for [`Error::new`] with [`ErrorKind::Other`]. + #[inline] + pub fn other(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Other, error.into()) + } +} diff --git a/crates/nostr/src/event/builder.rs b/crates/nostr/src/event/builder.rs index dfd6a6f60..d58696a85 100644 --- a/crates/nostr/src/event/builder.rs +++ b/crates/nostr/src/event/builder.rs @@ -7,34 +7,15 @@ use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::fmt; -use core::ops::Range; use serde_json::{Value, json}; +use crate::error::{Error, ErrorKind}; use crate::nips::nip58::Nip58Tag; use crate::nips::nip62::VanishTarget; use crate::prelude::*; use crate::util::BoxedFuture; -/// Wrong kind error -#[derive(Debug, PartialEq, Eq)] -pub enum WrongKindError { - /// Single kind - Single(Kind), - /// Range - Range(Range), -} - -impl fmt::Display for WrongKindError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Single(k) => k.fmt(f), - Self::Range(range) => write!(f, "'{} <= k <= {}'", range.start, range.end), - } - } -} - /// Template that can be converted into a generic [`EventBuilder`]. pub trait EventBuilderTemplate: Sized { /// Convert into the generic event builder. @@ -57,7 +38,8 @@ where B: EventBuilderTemplate, S: GetPublicKey + SignEvent + ?Sized, { - type Error = SignerError; + /// Error type + type Error = Error; fn finalize(self, signer: &S) -> Result { let builder: EventBuilder = self.build(); @@ -107,9 +89,9 @@ where B: EventBuilderTemplateAsync + Send, S: AsyncGetPublicKey + AsyncSignEvent + ?Sized, { - type Error = SignerError; + type Error = Error; - fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result> + fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result> where Self: 'a, S: 'a, @@ -746,10 +728,7 @@ impl EventBuilder { /// Badge award /// /// - pub fn award_badge( - badge_definition: &Event, - awarded_public_keys: I, - ) -> Result + pub fn award_badge(badge_definition: &Event, awarded_public_keys: I) -> Result where I: IntoIterator, { @@ -760,7 +739,9 @@ impl EventBuilder { Ok(Nip01Tag::Identifier(id)) => Some(id), _ => None, }) - .ok_or(nip58::Error::IdentifierTagNotFound)?; + .ok_or_else(|| { + Error::with_static_message(ErrorKind::Missing, "identifier tag not found") + })?; // At least 1 tag let mut tags = Vec::with_capacity(1); @@ -789,14 +770,20 @@ impl EventBuilder { badge_definitions: Vec, badge_awards: Vec, pubkey_awarded: &PublicKey, - ) -> Result { + ) -> Result { if badge_definitions.len() != badge_awards.len() { - return Err(nip58::Error::InvalidLength); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid length", + )); } let badge_awards: Vec = nip58::filter_for_kind(badge_awards, &Kind::BadgeAward); if badge_awards.is_empty() { - return Err(nip58::Error::InvalidKind); + return Err(Error::with_static_message( + ErrorKind::Missing, + "badge awards are missing", + )); } for award in badge_awards.iter() { @@ -804,14 +791,20 @@ impl EventBuilder { Ok(Nip01Tag::PublicKey { public_key, .. }) => public_key == *pubkey_awarded, _ => false, }) { - return Err(nip58::Error::BadgeAwardsLackAwardedPublicKey); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "badge award lacks awarded public key", + )); } } let badge_definitions: Vec = nip58::filter_for_kind(badge_definitions, &Kind::BadgeDefinition); if badge_definitions.is_empty() { - return Err(nip58::Error::InvalidKind); + return Err(Error::with_static_message( + ErrorKind::Missing, + "badge definitions are missing", + )); } let mut tags: Vec = Vec::new(); @@ -840,7 +833,10 @@ impl EventBuilder { for (badge_definition, badge_award) in users_badges { match (badge_definition, badge_award) { ((_, identifier), (_, badge_id, ..)) if badge_id != identifier => { - return Err(nip58::Error::MismatchedBadgeDefinitionOrAward); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "mismatched badge definition or award", + )); } ((_, identifier), (badge_award_event, badge_id, a_tag, relay_url)) if badge_id == identifier => @@ -863,9 +859,12 @@ impl EventBuilder { /// Data Vending Machine (DVM) - Job Request /// /// - pub fn job_request(kind: Kind) -> Result { + pub fn job_request(kind: Kind) -> Result { if !kind.is_job_request() { - return Err(WrongKindError::Range(NIP90_JOB_REQUEST_RANGE)); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "kind is not in the NIP90 job request range", + )); } Ok(Self::new(kind, "")) @@ -879,7 +878,7 @@ impl EventBuilder { payload: S, millisats: u64, bolt11: Option, - ) -> Result + ) -> Result where S: Into, { @@ -887,7 +886,10 @@ impl EventBuilder { // Check if Job Result kind if !kind.is_job_result() { - return Err(WrongKindError::Range(NIP90_JOB_RESULT_RANGE)); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "kind is not in the NIP90 job result range", + )); } let mut tags: Vec = job_request @@ -1248,7 +1250,7 @@ impl EventBuilder { /// /// #[inline] - pub fn git_issue(issue: GitIssue) -> Result { + pub fn git_issue(issue: GitIssue) -> Result { issue.to_event_builder() } @@ -1256,7 +1258,7 @@ impl EventBuilder { /// /// #[inline] - pub fn git_patch(patch: GitPatch) -> Result { + pub fn git_patch(patch: GitPatch) -> Result { patch.to_event_builder() } @@ -1264,7 +1266,7 @@ impl EventBuilder { /// /// #[inline] - pub fn git_pull_request(pull_request: GitPullRequest) -> Result { + pub fn git_pull_request(pull_request: GitPullRequest) -> Result { pull_request.to_event_builder() } @@ -1272,7 +1274,7 @@ impl EventBuilder { /// /// #[inline] - pub fn git_pull_request_update(update: GitPullRequestUpdate) -> Result { + pub fn git_pull_request_update(update: GitPullRequestUpdate) -> Result { update.to_event_builder() } @@ -1302,7 +1304,7 @@ impl EventBuilder { content: S, reply_to: &Event, relay_url: Option, - ) -> Result + ) -> Result where S: Into, { @@ -1415,12 +1417,12 @@ impl FinalizeEvent for EventBuilder where S: GetPublicKey + SignEvent + ?Sized, { - type Error = SignerError; + type Error = Error; fn finalize(self, signer: &S) -> Result { - let public_key: PublicKey = signer.get_public_key().map_err(SignerError::backend)?; + let public_key: PublicKey = signer.get_public_key().map_err(Error::other)?; let unsigned: UnsignedEvent = self.finalize_unsigned(public_key); - signer.sign_event(unsigned).map_err(SignerError::backend) + signer.sign_event(unsigned).map_err(Error::other) } } @@ -1428,7 +1430,7 @@ impl FinalizeEventAsync for EventBuilder where S: AsyncGetPublicKey + AsyncSignEvent + ?Sized, { - type Error = SignerError; + type Error = Error; fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result> where @@ -1436,15 +1438,13 @@ where S: 'a, { Box::pin(async move { - let public_key: PublicKey = signer - .get_public_key_async() - .await - .map_err(SignerError::backend)?; + let public_key: PublicKey = + signer.get_public_key_async().await.map_err(Error::other)?; let unsigned: UnsignedEvent = self.finalize_unsigned(public_key); signer .sign_event_async(unsigned) .await - .map_err(SignerError::backend) + .map_err(Error::other) }) } } diff --git a/crates/nostr/src/event/error.rs b/crates/nostr/src/event/error.rs deleted file mode 100644 index 2584e48ce..000000000 --- a/crates/nostr/src/event/error.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2022-2023 Yuki Kishimoto -// Copyright (c) 2023-2025 Rust Nostr Developers -// Distributed under the MIT software license - -use alloc::string::{String, ToString}; -use core::fmt; - -use crate::signer::SignerError; - -/// Event error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Error serializing or deserializing JSON data - Json(String), - /// Signer error - Signer(SignerError), - /// Hex decode error - Hex(faster_hex::Error), - /// Unknown JSON event key - UnknownKey(String), - /// Invalid event ID - InvalidId, - /// Invalid signature - InvalidSignature, - /// Empty tag - EmptyTag, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Json(e) => e.fmt(f), - Self::Signer(e) => e.fmt(f), - Self::Hex(e) => e.fmt(f), - Self::UnknownKey(key) => write!(f, "Unknown key: {key}"), - Self::InvalidId => f.write_str("Invalid event ID"), - Self::InvalidSignature => f.write_str("Invalid signature"), - Self::EmptyTag => f.write_str("Empty tag"), - } - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e.to_string()) - } -} - -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } -} - -impl From for Error { - fn from(e: faster_hex::Error) -> Self { - Self::Hex(e) - } -} diff --git a/crates/nostr/src/event/id.rs b/crates/nostr/src/event/id.rs index 753970607..aaed981cc 100644 --- a/crates/nostr/src/event/id.rs +++ b/crates/nostr/src/event/id.rs @@ -13,8 +13,8 @@ use hashes::sha256::Hash as Sha256Hash; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::{Value, json}; -use super::error::Error; use super::{Kind, Tag, Tags}; +use crate::error::{Error, ErrorKind}; use crate::nips::nip13; use crate::nips::nip19::FromBech32; use crate::nips::nip21::FromNostrUri; @@ -81,13 +81,16 @@ impl EventId { return Ok(id); } - Err(Error::InvalidId) + Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid event ID", + )) } /// Parse from hex string pub fn from_hex(hex: &str) -> Result { let mut bytes: [u8; Self::LEN] = [0u8; Self::LEN]; - faster_hex::hex_decode(hex.as_bytes(), &mut bytes)?; + faster_hex::hex_decode(hex.as_bytes(), &mut bytes).map_err(Error::malformed_display)?; Ok(Self::from_byte_array(bytes)) } @@ -95,7 +98,10 @@ impl EventId { pub fn from_slice(slice: &[u8]) -> Result { // Check len if slice.len() != Self::LEN { - return Err(Error::InvalidId); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid event ID", + )); } // Copy bytes diff --git a/crates/nostr/src/event/kind.rs b/crates/nostr/src/event/kind.rs index 3181b9424..99df257e1 100644 --- a/crates/nostr/src/event/kind.rs +++ b/crates/nostr/src/event/kind.rs @@ -7,13 +7,14 @@ use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; -use core::num::ParseIntError; use core::ops::{Add, Range}; use core::str::FromStr; use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; +use crate::error::Error; + /// NIP90 - Job request range pub const NIP90_JOB_REQUEST_RANGE: Range = 5_000..6_000; /// NIP90 - Job result range @@ -301,10 +302,10 @@ impl fmt::Display for Kind { } impl FromStr for Kind { - type Err = ParseIntError; + type Err = Error; fn from_str(kind: &str) -> Result { - let kind: u16 = kind.parse()?; + let kind: u16 = kind.parse().map_err(Error::malformed)?; Ok(Self::from_u16(kind)) } } diff --git a/crates/nostr/src/event/mod.rs b/crates/nostr/src/event/mod.rs index 37560331d..f96b84ec5 100644 --- a/crates/nostr/src/event/mod.rs +++ b/crates/nostr/src/event/mod.rs @@ -20,7 +20,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; mod borrow; mod builder; -mod error; mod id; mod kind; mod tag; @@ -28,15 +27,15 @@ mod unsigned; pub use self::borrow::*; pub use self::builder::*; -pub use self::error::Error; pub use self::id::*; pub use self::kind::*; pub use self::tag::*; pub use self::unsigned::*; #[cfg(feature = "std")] use crate::SECP256K1; +use crate::error::{Error, ErrorKind}; use crate::nips::nip01::Coordinate; -use crate::nips::nip19::{self, Nip19Event, ToBech32}; +use crate::nips::nip19::{Nip19Event, ToBech32}; use crate::nips::nip21::ToNostrUri; use crate::util::{BoxedFuture, impl_json_methods}; use crate::{Metadata, PublicKey, Timestamp}; @@ -169,12 +168,18 @@ impl Event { { // Verify ID if !self.verify_id() { - return Err(Error::InvalidId); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "Invalid event ID", + )); } // Verify signature if !self.verify_signature_with_ctx(secp) { - return Err(Error::InvalidSignature); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "Invalid signature", + )); } Ok(()) @@ -269,10 +274,10 @@ impl Event { } } -impl_json_methods!(Event, Error); +impl_json_methods!(Event); impl ToBech32 for Event { - type Err = nip19::Error; + type Err = Error; fn to_bech32(&self) -> Result { match self.coordinate() { @@ -285,7 +290,7 @@ impl ToBech32 for Event { impl ToNostrUri for Event {} impl TryFrom<&Event> for Metadata { - type Error = serde_json::Error; + type Error = Error; fn try_from(event: &Event) -> Result { Metadata::from_json(&event.content) @@ -359,7 +364,7 @@ impl<'de> Deserialize<'de> for Event { /// Sign event pub trait SignEvent: Any + Debug + Send + Sync { /// Error type - type Error: core::error::Error; + type Error: core::error::Error + Send + Sync; /// Sign an unsigned event fn sign_event(&self, unsigned: UnsignedEvent) -> Result; @@ -368,7 +373,7 @@ pub trait SignEvent: Any + Debug + Send + Sync { /// Sign event pub trait AsyncSignEvent: Any + Debug + Send + Sync { /// Error type - type Error: core::error::Error; + type Error: core::error::Error + Send + Sync; /// Sign an unsigned event fn sign_event_async( @@ -382,8 +387,8 @@ pub trait FinalizeEvent: Sized where S: ?Sized, { - /// Finalization error. - type Error: core::error::Error; + /// Error type + type Error: core::error::Error + Send + Sync; /// Build and sign the event. fn finalize(self, signer: &S) -> Result; @@ -394,8 +399,8 @@ pub trait FinalizeEventAsync: Sized where S: ?Sized, { - /// Finalization error. - type Error: core::error::Error; + /// Error type + type Error: core::error::Error + Send + Sync; /// Build and sign the event. fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result> diff --git a/crates/nostr/src/event/tag/codec.rs b/crates/nostr/src/event/tag/codec.rs index 68f476b1e..0bff31dc5 100644 --- a/crates/nostr/src/event/tag/codec.rs +++ b/crates/nostr/src/event/tag/codec.rs @@ -2,41 +2,8 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -use core::fmt; - use super::Tag; -/// Tag codec error -#[derive(Debug, PartialEq, Eq)] -pub enum TagCodecError { - /// Missing value - Missing(&'static str), - /// Invalid value - Invalid(&'static str), - /// Unknown tag - Unknown, -} - -impl core::error::Error for TagCodecError {} - -impl fmt::Display for TagCodecError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Missing(value) => write!(f, "missing {value}"), - Self::Invalid(value) => write!(f, "{value}"), - Self::Unknown => write!(f, "unknown tag"), - } - } -} - -impl TagCodecError { - /// Missing tag kind - #[inline] - pub fn missing_tag_kind() -> Self { - Self::Missing("tag kind") - } -} - /// Conversion contract between a typed tag representation and the generic raw [`Tag`]. /// /// Types implementing this trait are expected to: @@ -47,7 +14,7 @@ impl TagCodecError { /// tag structs that can be used independently of the enclosing enum. pub trait TagCodec: Sized { /// Error returned when parsing a raw tag into the typed representation fails. - type Error: core::error::Error; + type Error: core::error::Error + Send + Sync; /// Parse a typed tag from a raw tag representation. fn parse(tag: I) -> Result diff --git a/crates/nostr/src/event/tag/cow.rs b/crates/nostr/src/event/tag/cow.rs index 122b7e5fa..95085cbe0 100644 --- a/crates/nostr/src/event/tag/cow.rs +++ b/crates/nostr/src/event/tag/cow.rs @@ -10,6 +10,7 @@ use alloc::vec::Vec; use core::str::FromStr; use super::{Error, Tag}; +use crate::error::ErrorKind; use crate::filter::SingleLetterTag; /// Cow Tag @@ -25,7 +26,7 @@ impl<'a> CowTag<'a> { pub fn parse(tag: Vec>) -> Result { // Check if it's empty if tag.is_empty() { - return Err(Error::EmptyTag); + return Err(Error::with_static_message(ErrorKind::Invalid, "empty tag")); } Ok(Self { buf: tag }) diff --git a/crates/nostr/src/event/tag/mod.rs b/crates/nostr/src/event/tag/mod.rs index a3612a70c..547071b77 100644 --- a/crates/nostr/src/event/tag/mod.rs +++ b/crates/nostr/src/event/tag/mod.rs @@ -24,8 +24,8 @@ mod list; pub use self::codec::*; pub use self::cow::*; pub use self::list::*; -use super::error::Error; use super::id::EventId; +use crate::error::{Error, ErrorKind}; use crate::nips::nip01::{Coordinate, Nip01Tag}; use crate::nips::nip13::Nip13Tag; use crate::nips::nip31::Nip31Tag; @@ -108,7 +108,7 @@ impl Tag { // Check if it's empty if tag.is_empty() { - return Err(Error::EmptyTag); + return Err(Error::with_static_message(ErrorKind::Invalid, "empty tag")); } // Construct without an empty cell @@ -394,8 +394,8 @@ mod tests { #[test] fn test_parse_empty_tag() { assert_eq!( - Tag::parse::, String>(vec![]).unwrap_err(), - Error::EmptyTag + Tag::parse::, String>(vec![]).unwrap_err().kind(), + ErrorKind::Invalid ); } diff --git a/crates/nostr/src/event/unsigned.rs b/crates/nostr/src/event/unsigned.rs index 97ba3700d..3a008bbb4 100644 --- a/crates/nostr/src/event/unsigned.rs +++ b/crates/nostr/src/event/unsigned.rs @@ -11,14 +11,19 @@ use core::num::NonZeroU8; use secp256k1::schnorr::Signature; use secp256k1::{Secp256k1, Verification}; -use super::error::Error; use super::{AsyncSignEvent, FinalizeEvent, FinalizeEventAsync, SignEvent}; #[cfg(feature = "std")] use crate::SECP256K1; +use crate::error::{Error, ErrorKind}; use crate::nips::nip13::{AsyncPowAdapter, PowAdapter}; use crate::util::{BoxedFuture, impl_json_methods}; use crate::{Event, EventId, Kind, PublicKey, Tag, Tags, Timestamp}; +#[inline] +fn invalid_event_id() -> Error { + Error::with_static_message(ErrorKind::Invalid, "invalid event ID") +} + /// Unsigned event #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct UnsignedEvent { @@ -98,7 +103,7 @@ impl UnsignedEvent { if let Some(id) = self.id { let computed_id: EventId = self.compute_id(); if id != computed_id { - return Err(Error::InvalidId); + return Err(invalid_event_id()); } } @@ -162,12 +167,15 @@ impl UnsignedEvent { // Verify the event ID if verify_id && !event.verify_id() { - return Err(Error::InvalidId); + return Err(invalid_event_id()); } // Verify event signature if !event.verify_signature_with_ctx(secp) { - return Err(Error::InvalidSignature); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid signature", + )); } Ok(event) @@ -178,11 +186,11 @@ impl FinalizeEvent for UnsignedEvent where S: SignEvent + ?Sized, { - type Error = S::Error; + type Error = Error; #[inline] fn finalize(self, signer: &S) -> Result { - signer.sign_event(self) + signer.sign_event(self).map_err(Error::other) } } @@ -190,7 +198,7 @@ impl FinalizeEventAsync for UnsignedEvent where S: AsyncSignEvent + ?Sized, { - type Error = S::Error; + type Error = Error; #[inline] fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result> @@ -198,11 +206,11 @@ where Self: 'a, S: 'a, { - Box::pin(async move { signer.sign_event_async(self).await }) + Box::pin(async move { signer.sign_event_async(self).await.map_err(Error::other) }) } } -impl_json_methods!(UnsignedEvent, Error); +impl_json_methods!(UnsignedEvent); impl From for UnsignedEvent { fn from(event: Event) -> Self { diff --git a/crates/nostr/src/filter.rs b/crates/nostr/src/filter.rs index a731f2ed0..8a886fc41 100644 --- a/crates/nostr/src/filter.rs +++ b/crates/nostr/src/filter.rs @@ -9,8 +9,7 @@ use alloc::collections::{BTreeMap, BTreeSet}; use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use core::fmt::Write; +use core::fmt::{self, Write}; use core::hash::Hash; use core::str::FromStr; @@ -18,6 +17,7 @@ use serde::de::{Deserializer, MapAccess, Visitor}; use serde::ser::{SerializeMap, Serializer}; use serde::{Deserialize, Serialize}; +use crate::error::{Error, ErrorKind}; use crate::event::TagsIndexes; use crate::nips::nip01::Coordinate; use crate::util::impl_json_methods; @@ -27,21 +27,8 @@ type GenericTags = BTreeMap>; const P_TAG: SingleLetterTag = SingleLetterTag::lowercase(Alphabet::P); -/// Alphabet Error -#[derive(Debug)] -pub enum SingleLetterTagError { - /// Invalid char - InvalidChar, -} - -impl core::error::Error for SingleLetterTagError {} - -impl fmt::Display for SingleLetterTagError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidChar => f.write_str("invalid char"), - } - } +fn invalid_char() -> Error { + Error::with_static_message(ErrorKind::Invalid, "invalid char") } /// Alphabet @@ -105,7 +92,7 @@ impl SingleLetterTag { } /// Parse single-letter tag from [char] - pub fn from_char(c: char) -> Result { + pub fn from_char(c: char) -> Result { let character = match c { 'a' | 'A' => Alphabet::A, 'b' | 'B' => Alphabet::B, @@ -133,7 +120,7 @@ impl SingleLetterTag { 'x' | 'X' => Alphabet::X, 'y' | 'Y' => Alphabet::Y, 'z' | 'Z' => Alphabet::Z, - _ => return Err(SingleLetterTagError::InvalidChar), + _ => return Err(invalid_char()), }; Ok(Self { @@ -288,14 +275,14 @@ impl fmt::Display for SingleLetterTag { } impl FromStr for SingleLetterTag { - type Err = SingleLetterTagError; + type Err = Error; fn from_str(s: &str) -> Result { if s.len() == 1 { - let c: char = s.chars().next().ok_or(SingleLetterTagError::InvalidChar)?; + let c: char = s.chars().next().ok_or(invalid_char())?; Self::from_char(c) } else { - Err(SingleLetterTagError::InvalidChar) + Err(invalid_char()) } } } @@ -944,7 +931,7 @@ impl Filter { } } -impl_json_methods!(Filter, serde_json::Error); +impl_json_methods!(Filter); impl From for Vec { fn from(filter: Filter) -> Self { diff --git a/crates/nostr/src/key/mod.rs b/crates/nostr/src/key/mod.rs index 97f9c339b..ea76f2e2b 100644 --- a/crates/nostr/src/key/mod.rs +++ b/crates/nostr/src/key/mod.rs @@ -27,8 +27,9 @@ mod secret_key; pub use self::public_key::*; pub use self::secret_key::*; +use crate::error::Error; #[cfg(all(feature = "std", feature = "os-rng"))] -use crate::event::{self, AsyncSignEvent, Event, EventId, SignEvent, UnsignedEvent}; +use crate::event::{AsyncSignEvent, Event, EventId, SignEvent, UnsignedEvent}; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip04"))] use crate::nips::nip04::{AsyncNip04, Nip04}; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip44"))] @@ -39,44 +40,6 @@ use crate::util::BoxedFuture; #[cfg(feature = "std")] use crate::util::SECP256K1; -/// [`Keys`] error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Secp256k1 error - Secp256k1(secp256k1::Error), - /// Hex decode error - Hex(faster_hex::Error), - /// Invalid secret key - InvalidSecretKey, - /// Invalid public key - InvalidPublicKey, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Secp256k1(e) => e.fmt(f), - Self::Hex(e) => e.fmt(f), - Self::InvalidSecretKey => f.write_str("Invalid secret key"), - Self::InvalidPublicKey => f.write_str("Invalid public key"), - } - } -} - -impl From for Error { - fn from(e: secp256k1::Error) -> Self { - Self::Secp256k1(e) - } -} - -impl From for Error { - fn from(e: faster_hex::Error) -> Self { - Self::Hex(e) - } -} - /// Nostr keys #[derive(Clone)] pub struct Keys { @@ -280,7 +243,7 @@ impl GetPublicKey for Keys { #[cfg(all(feature = "std", feature = "os-rng"))] impl SignEvent for Keys { - type Error = event::Error; + type Error = Error; fn sign_event(&self, unsigned: UnsignedEvent) -> Result { let id: EventId = unsigned.id.unwrap_or_else(|| unsigned.compute_id()); @@ -292,7 +255,7 @@ impl SignEvent for Keys { #[cfg(all(feature = "std", feature = "os-rng", feature = "nip04"))] impl Nip04 for Keys { - type Error = crate::nips::nip04::Error; + type Error = Error; fn nip04_encrypt(&self, public_key: &PublicKey, content: &str) -> Result { let secret_key: &SecretKey = self.secret_key(); @@ -311,7 +274,7 @@ impl Nip04 for Keys { #[cfg(all(feature = "std", feature = "os-rng", feature = "nip44"))] impl Nip44 for Keys { - type Error = crate::nips::nip44::Error; + type Error = Error; fn nip44_encrypt(&self, public_key: &PublicKey, content: &str) -> Result { use crate::nips::nip44::{self, Version}; @@ -335,7 +298,7 @@ impl AsyncGetPublicKey for Keys { #[cfg(all(feature = "std", feature = "os-rng"))] impl AsyncSignEvent for Keys { - type Error = event::Error; + type Error = Error; fn sign_event_async( &self, @@ -347,7 +310,7 @@ impl AsyncSignEvent for Keys { #[cfg(all(feature = "std", feature = "os-rng", feature = "nip04"))] impl AsyncNip04 for Keys { - type Error = crate::nips::nip04::Error; + type Error = Error; fn nip04_encrypt_async<'a>( &'a self, @@ -368,7 +331,7 @@ impl AsyncNip04 for Keys { #[cfg(all(feature = "std", feature = "os-rng", feature = "nip44"))] impl AsyncNip44 for Keys { - type Error = crate::nips::nip44::Error; + type Error = Error; fn nip44_encrypt_async<'a>( &'a self, @@ -391,6 +354,7 @@ impl AsyncNip44 for Keys { #[cfg(feature = "std")] mod tests { use super::*; + use crate::error::ErrorKind; const SECRET_KEY_BECH32: &str = "nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99"; @@ -405,15 +369,19 @@ mod tests { #[test] fn parse_invalid_keys() { - assert_eq!(Keys::parse("nsec...").unwrap_err(), Error::InvalidSecretKey); + assert_eq!( + Keys::parse("nsec...").unwrap_err().kind(), + ErrorKind::Invalid + ); assert_eq!( Keys::parse("npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy") - .unwrap_err(), - Error::InvalidSecretKey + .unwrap_err() + .kind(), + ErrorKind::Invalid ); assert_eq!( - Keys::parse("6b911fd37cdf5c8").unwrap_err(), - Error::InvalidSecretKey + Keys::parse("6b911fd37cdf5c8").unwrap_err().kind(), + ErrorKind::Invalid ); } } diff --git a/crates/nostr/src/key/public_key.rs b/crates/nostr/src/key/public_key.rs index 0ff3f142f..f6ef275f3 100644 --- a/crates/nostr/src/key/public_key.rs +++ b/crates/nostr/src/key/public_key.rs @@ -17,7 +17,8 @@ use core::str::{self, FromStr}; use secp256k1::{Secp256k1, Signing, XOnlyPublicKey}; use serde::{Deserialize, Deserializer, Serialize}; -use super::{Error, SecretKey}; +use super::secret_key::SecretKey; +use crate::error::{Error, ErrorKind}; use crate::nips::nip19::{FromBech32, PREFIX_BECH32_PROFILE, PREFIX_BECH32_PUBLIC_KEY}; use crate::nips::nip21::{FromNostrUri, SCHEME_WITH_COLON}; use crate::util::BoxedFuture; @@ -25,7 +26,7 @@ use crate::util::BoxedFuture; /// Get public key pub trait GetPublicKey: Any + Debug + Send + Sync { /// Error type - type Error: core::error::Error; + type Error: core::error::Error + Send + Sync; /// Get public key fn get_public_key(&self) -> Result; @@ -34,7 +35,7 @@ pub trait GetPublicKey: Any + Debug + Send + Sync { /// Get public key pub trait AsyncGetPublicKey: Any + Debug + Send + Sync { /// Error type - type Error: core::error::Error; + type Error: core::error::Error + Send + Sync; /// Get public key fn get_public_key_async(&self) -> BoxedFuture<'_, Result>; @@ -107,18 +108,18 @@ impl PublicKey { if public_key.starts_with(PREFIX_BECH32_PUBLIC_KEY) || public_key.starts_with(PREFIX_BECH32_PROFILE) { - Self::from_bech32(public_key).map_err(|_| Error::InvalidPublicKey) + Self::from_bech32(public_key) } else if public_key.starts_with(SCHEME_WITH_COLON) { - Self::from_nostr_uri(public_key).map_err(|_| Error::InvalidPublicKey) + Self::from_nostr_uri(public_key) } else { - Self::from_hex(public_key).map_err(|_| Error::InvalidPublicKey) + Self::from_hex(public_key) } } /// Parse from hex string pub fn from_hex(hex: &str) -> Result { let mut bytes: [u8; Self::LEN] = [0u8; Self::LEN]; - faster_hex::hex_decode(hex.as_bytes(), &mut bytes)?; + faster_hex::hex_decode(hex.as_bytes(), &mut bytes).map_err(Error::malformed_display)?; Ok(Self::from_byte_array(bytes)) } @@ -126,7 +127,10 @@ impl PublicKey { pub fn from_slice(slice: &[u8]) -> Result { // Check len if slice.len() != Self::LEN { - return Err(Error::InvalidPublicKey); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "public key must be 32 bytes long", + )); } // Copy bytes @@ -176,8 +180,7 @@ impl PublicKey { /// Get the x-only public key pub fn xonly(&self) -> Result { - // TODO: use a OnceCell - Ok(XOnlyPublicKey::from_slice(self.as_bytes())?) + XOnlyPublicKey::from_slice(self.as_bytes()).map_err(Error::invalid_display) } } diff --git a/crates/nostr/src/key/secret_key.rs b/crates/nostr/src/key/secret_key.rs index 8836d619f..d52e2a63f 100644 --- a/crates/nostr/src/key/secret_key.rs +++ b/crates/nostr/src/key/secret_key.rs @@ -16,10 +16,10 @@ use rand::rand_core::UnwrapErr; use rand::rngs::SysRng; use serde::{Deserialize, Deserializer}; -use super::Error; +use crate::error::{Error, ErrorKind}; use crate::nips::nip19::FromBech32; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip49"))] -use crate::nips::nip49::{self, EncryptedSecretKey, KeySecurity}; +use crate::nips::nip49::{EncryptedSecretKey, KeySecurity}; #[cfg(feature = "rand")] use crate::util; @@ -65,14 +65,17 @@ impl SecretKey { return Ok(secret_key); } - Err(Error::InvalidSecretKey) + Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid secret key", + )) } /// Parse from `bytes` #[inline] pub fn from_slice(slice: &[u8]) -> Result { Ok(Self { - inner: secp256k1::SecretKey::from_slice(slice)?, + inner: secp256k1::SecretKey::from_slice(slice).map_err(Error::malformed_display)?, }) } @@ -80,7 +83,7 @@ impl SecretKey { #[inline] pub fn from_hex(hex: &str) -> Result { Ok(Self { - inner: secp256k1::SecretKey::from_str(hex)?, + inner: secp256k1::SecretKey::from_str(hex).map_err(Error::malformed_display)?, }) } @@ -141,7 +144,7 @@ impl SecretKey { /// To use custom values check [`EncryptedSecretKey`] constructors. #[inline] #[cfg(all(feature = "std", feature = "os-rng", feature = "nip49"))] - pub fn encrypt(&self, password: &str) -> Result { + pub fn encrypt(&self, password: &str) -> Result { EncryptedSecretKey::new(self, password, 16, KeySecurity::Unknown) } } diff --git a/crates/nostr/src/lib.rs b/crates/nostr/src/lib.rs index 56254cbf7..7fbef1570 100644 --- a/crates/nostr/src/lib.rs +++ b/crates/nostr/src/lib.rs @@ -41,6 +41,7 @@ pub use bip39; #[doc(hidden)] pub use serde_json; +pub mod error; pub mod event; pub mod filter; pub mod key; @@ -48,7 +49,6 @@ pub mod message; pub mod nips; pub mod parser; pub mod prelude; -pub mod signer; pub mod types; pub mod util; @@ -71,7 +71,3 @@ pub use self::types::{ImageDimensions, RelayUrl, RelayUrlArg, Timestamp, Url}; #[doc(hidden)] #[cfg(feature = "std")] pub use self::util::SECP256K1; - -/// Result -#[doc(hidden)] -pub type Result> = core::result::Result; diff --git a/crates/nostr/src/message/client.rs b/crates/nostr/src/message/client.rs index b04fe51dd..ade63cd26 100644 --- a/crates/nostr/src/message/client.rs +++ b/crates/nostr/src/message/client.rs @@ -12,8 +12,9 @@ use alloc::vec::Vec; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{Value, json}; -use super::MessageHandleError; -use crate::util::impl_json_methods; +use super::invalid_message_format; +use crate::error::Error; +use crate::util::{impl_json_methods, parse_json, parse_json_from_value}; use crate::{Event, Filter, SubscriptionId}; /// Messages sent by clients, received by relays @@ -197,13 +198,11 @@ impl ClientMessage<'_> { /// Deserialize from [`Value`] /// /// **This method NOT verify the event signature!** - pub fn from_value(msg: Value) -> Result { - let v = msg - .as_array() - .ok_or(MessageHandleError::InvalidMessageFormat)?; + pub fn from_value(msg: Value) -> Result { + let v = msg.as_array().ok_or(invalid_message_format())?; if v.is_empty() { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } let v_len: usize = v.len(); @@ -211,10 +210,10 @@ impl ClientMessage<'_> { // ["EVENT", ] if v[0] == "EVENT" { if v_len >= 2 { - let event: Event = serde_json::from_value(v[1].clone())?; + let event: Event = parse_json_from_value(v[1].clone())?; return Ok(Self::event(event)); } else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } } @@ -223,26 +222,26 @@ impl ClientMessage<'_> { if v_len >= 3 { // Deprecated REQ // ["REQ", , , , ...] - let subscription_id: SubscriptionId = serde_json::from_value(v[1].clone())?; + let subscription_id: SubscriptionId = parse_json_from_value(v[1].clone())?; let filters: Vec> = - serde_json::from_value(Value::Array(v[2..].to_vec()))?; + parse_json_from_value(Value::Array(v[2..].to_vec()))?; return Ok(Self::Req { subscription_id: Cow::Owned(subscription_id), filters, }); } else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } } // ["COUNT", , ] if v[0] == "COUNT" { if v_len >= 3 { - let subscription_id: SubscriptionId = serde_json::from_value(v[1].clone())?; - let filter: Filter = serde_json::from_value(v[2].clone())?; + let subscription_id: SubscriptionId = parse_json_from_value(v[1].clone())?; + let filter: Filter = parse_json_from_value(v[2].clone())?; return Ok(Self::count(subscription_id, filter)); } else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } } @@ -250,10 +249,10 @@ impl ClientMessage<'_> { // ["CLOSE", ] if v[0] == "CLOSE" { if v_len >= 2 { - let subscription_id: SubscriptionId = serde_json::from_value(v[1].clone())?; + let subscription_id: SubscriptionId = parse_json_from_value(v[1].clone())?; return Ok(Self::close(subscription_id)); } else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } } @@ -261,10 +260,10 @@ impl ClientMessage<'_> { // ["AUTH", ] if v[0] == "AUTH" { if v_len >= 2 { - let event: Event = serde_json::from_value(v[1].clone())?; + let event: Event = parse_json_from_value(v[1].clone())?; return Ok(Self::auth(event)); } else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } } @@ -274,20 +273,18 @@ impl ClientMessage<'_> { if v[0] == "NEG-OPEN" { // New negentropy protocol message if v_len == 4 { - let subscription_id: SubscriptionId = serde_json::from_value(v[1].clone())?; + let subscription_id: SubscriptionId = parse_json_from_value(v[1].clone())?; let filter: Filter = Filter::from_json(v[2].to_string())?; - let initial_message: String = serde_json::from_value(v[3].clone())?; + let initial_message: String = parse_json_from_value(v[3].clone())?; return Ok(Self::neg_open(subscription_id, filter, initial_message)); } // Old negentropy protocol message if v_len == 5 { - let subscription_id: SubscriptionId = serde_json::from_value(v[1].clone())?; + let subscription_id: SubscriptionId = parse_json_from_value(v[1].clone())?; let filter: Filter = Filter::from_json(v[2].to_string())?; - let id_size: u8 = - v[3].as_u64() - .ok_or(MessageHandleError::InvalidMessageFormat)? as u8; - let initial_message: String = serde_json::from_value(v[4].clone())?; + let id_size: u8 = v[3].as_u64().ok_or(invalid_message_format())? as u8; + let initial_message: String = parse_json_from_value(v[4].clone())?; return Ok(Self::NegOpen { subscription_id: Cow::Owned(subscription_id), filter: Cow::Owned(filter), @@ -296,21 +293,21 @@ impl ClientMessage<'_> { }); } - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } // Negentropy Message // ["NEG-MSG", , ] if v[0] == "NEG-MSG" { if v_len >= 3 { - let subscription_id: SubscriptionId = serde_json::from_value(v[1].clone())?; - let message: String = serde_json::from_value(v[2].clone())?; + let subscription_id: SubscriptionId = parse_json_from_value(v[1].clone())?; + let message: String = parse_json_from_value(v[2].clone())?; return Ok(Self::NegMsg { subscription_id: Cow::Owned(subscription_id), message: Cow::Owned(message), }); } else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } } @@ -318,16 +315,16 @@ impl ClientMessage<'_> { // ["NEG-CLOSE", ] if v[0] == "NEG-CLOSE" { if v_len >= 2 { - let subscription_id: SubscriptionId = serde_json::from_value(v[1].clone())?; + let subscription_id: SubscriptionId = parse_json_from_value(v[1].clone())?; return Ok(Self::NegClose { subscription_id: Cow::Owned(subscription_id), }); } else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } } - Err(MessageHandleError::InvalidMessageFormat) + Err(invalid_message_format()) } } @@ -353,15 +350,14 @@ impl<'de> Deserialize<'de> for ClientMessage<'_> { impl_json_methods! { ClientMessage<'_>, - MessageHandleError, from_json(json) { let msg: &[u8] = json.as_ref(); if msg.is_empty() { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } - let value: Value = serde_json::from_slice(msg)?; + let value: Value = parse_json(&msg)?; Self::from_value(value) } } diff --git a/crates/nostr/src/message/mod.rs b/crates/nostr/src/message/mod.rs index 886777e4f..1ca26300e 100644 --- a/crates/nostr/src/message/mod.rs +++ b/crates/nostr/src/message/mod.rs @@ -20,33 +20,12 @@ pub mod relay; pub use self::client::ClientMessage; pub use self::relay::{MachineReadablePrefix, RelayMessage}; +use crate::error::{Error, ErrorKind}; #[cfg(feature = "rand")] use crate::util; -/// Messages error -#[derive(Debug)] -pub enum MessageHandleError { - /// Impossible to deserialize message - Json(serde_json::Error), - /// Invalid message format - InvalidMessageFormat, -} - -impl core::error::Error for MessageHandleError {} - -impl fmt::Display for MessageHandleError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Json(e) => e.fmt(f), - Self::InvalidMessageFormat => f.write_str("Invalid format"), - } - } -} - -impl From for MessageHandleError { - fn from(e: serde_json::Error) -> Self { - Self::Json(e) - } +fn invalid_message_format() -> Error { + Error::with_static_message(ErrorKind::Malformed, "invalid message format") } /// Subscription ID diff --git a/crates/nostr/src/message/relay.rs b/crates/nostr/src/message/relay.rs index 365e069d2..c17034776 100644 --- a/crates/nostr/src/message/relay.rs +++ b/crates/nostr/src/message/relay.rs @@ -14,8 +14,9 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{Value, json}; -use super::MessageHandleError; -use crate::util::impl_json_methods; +use super::invalid_message_format; +use crate::error::Error; +use crate::util::{impl_json_methods, parse_json_from_value}; use crate::{Event, EventId, SubscriptionId}; /// A string that must be exactly one word. @@ -307,13 +308,13 @@ impl RelayMessage<'_> { } /// Deserialize from [`Value`] - pub fn from_value(msg: Value) -> Result { + pub fn from_value(msg: Value) -> Result { let Value::Array(v) = msg else { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); }; if v.is_empty() { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } let mut v_iter = v.into_iter(); @@ -384,7 +385,7 @@ impl RelayMessage<'_> { message: next_and_deser(&mut v_iter)?, // Index 2 }) } - _ => Err(MessageHandleError::InvalidMessageFormat), + _ => Err(invalid_message_format()), } } @@ -446,28 +447,25 @@ impl<'de> Deserialize<'de> for RelayMessage<'_> { impl_json_methods! { RelayMessage<'_>, - MessageHandleError, from_json(json) { let msg: &[u8] = json.as_ref(); if msg.is_empty() { - return Err(MessageHandleError::InvalidMessageFormat); + return Err(invalid_message_format()); } - let value: Value = serde_json::from_slice(msg)?; + let value: Value = serde_json::from_slice(msg).map_err(Error::malformed)?; Self::from_value(value) } } #[inline] -fn next_and_deser(iter: &mut IntoIter) -> Result +fn next_and_deser(iter: &mut IntoIter) -> Result where T: DeserializeOwned, { - let val: Value = iter - .next() - .ok_or(MessageHandleError::InvalidMessageFormat)?; - Ok(serde_json::from_value(val)?) + let val: Value = iter.next().ok_or(invalid_message_format())?; + parse_json_from_value(val) } /// Returns true if the slice is not empty and has no ASCII whitespace diff --git a/crates/nostr/src/nips/nip01/mod.rs b/crates/nostr/src/nips/nip01/mod.rs index 5242a3ae3..76d6d89ec 100644 --- a/crates/nostr/src/nips/nip01/mod.rs +++ b/crates/nostr/src/nips/nip01/mod.rs @@ -9,7 +9,6 @@ use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use core::fmt; -use core::num::ParseIntError; use core::str::FromStr; use serde::de::{Deserializer, MapAccess, Visitor}; @@ -22,71 +21,15 @@ mod tags; pub use self::tags::*; use super::nip19::{self, FromBech32, Nip19Coordinate, ToBech32}; use super::nip21::{FromNostrUri, ToNostrUri}; -use crate::event::{EventBuilderTemplate, TagCodecError}; -use crate::types::url::{self, Url}; +use crate::error::{Error, ErrorKind}; +use crate::event::{EventBuilderTemplate, Kind, Tag}; +use crate::types::url::Url; use crate::util::impl_json_methods; -use crate::{EventBuilder, Filter, Kind, PublicKey, Tag, event, key}; - -/// NIP-01 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Keys error - Keys(key::Error), - /// Event error - Event(event::Error), - /// Url error - Url(url::Error), - /// Parse Int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), - /// Invalid coordinate - InvalidCoordinate, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Keys(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidCoordinate => f.write_str("Invalid coordinate"), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} +use crate::{EventBuilder, Filter, PublicKey}; -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } +#[inline] +fn invalid_coordinate() -> Error { + Error::with_static_message(ErrorKind::Invalid, "invalid coordinate") } /// Coordinate for event (`a` tag) @@ -137,7 +80,7 @@ impl Coordinate { return Ok(coordinate); } - Err(Error::InvalidCoordinate) + Err(invalid_coordinate()) } /// Try to parse from `::[]` format @@ -149,7 +92,7 @@ impl Coordinate { public_key: PublicKey::from_hex(public_key_str)?, identifier: identifier.to_string(), }), - _ => Err(Error::InvalidCoordinate), + _ => Err(invalid_coordinate()), } } @@ -174,7 +117,7 @@ impl Coordinate { /// /// # Errors /// - /// Returns [`Error::InvalidCoordinate`] if: + /// Returns an error if: /// - the [`Kind`] is `replaceable` and the identifier is not empty /// - the [`Kind`] is `addressable` and the identifier is empty #[inline] @@ -197,15 +140,15 @@ fn verify_coordinate(kind: &Kind, identifier: &str) -> Result<(), Error> { let is_addressable: bool = kind.is_addressable(); if !is_replaceable && !is_addressable { - return Err(Error::InvalidCoordinate); + return Err(invalid_coordinate()); } if is_replaceable && !identifier.is_empty() { - return Err(Error::InvalidCoordinate); + return Err(invalid_coordinate()); } if is_addressable && identifier.is_empty() { - return Err(Error::InvalidCoordinate); + return Err(invalid_coordinate()); } Ok(()) @@ -254,7 +197,7 @@ impl FromStr for Coordinate { } impl ToBech32 for Coordinate { - type Err = nip19::Error; + type Err = Error; #[inline] fn to_bech32(&self) -> Result { @@ -263,7 +206,7 @@ impl ToBech32 for Coordinate { } impl FromBech32 for Coordinate { - type Err = nip19::Error; + type Err = Error; fn from_bech32(addr: &str) -> Result { let coordinate: Nip19Coordinate = Nip19Coordinate::from_bech32(addr)?; @@ -311,7 +254,7 @@ impl fmt::Display for CoordinateBorrow<'_> { } impl ToBech32 for CoordinateBorrow<'_> { - type Err = nip19::Error; + type Err = Error; #[inline] fn to_bech32(&self) -> Result { @@ -502,7 +445,7 @@ impl Metadata { } } -impl_json_methods!(Metadata, serde_json::Error); +impl_json_methods!(Metadata); fn serialize_custom_fields( custom_fields: &BTreeMap, diff --git a/crates/nostr/src/nips/nip01/tags.rs b/crates/nostr/src/nips/nip01/tags.rs index cc77ded74..ea07cb10a 100644 --- a/crates/nostr/src/nips/nip01/tags.rs +++ b/crates/nostr/src/nips/nip01/tags.rs @@ -3,11 +3,11 @@ use alloc::vec; use alloc::vec::Vec; use super::super::util::{ - take_and_parse_optional_public_key, take_and_parse_optional_relay_url, take_coordinate, - take_event_id, take_public_key, take_string, + missing_tag_kind, take_and_parse_optional_public_key, take_and_parse_optional_relay_url, + take_coordinate, take_event_id, take_public_key, take_string, unknown_tag, }; use super::{Coordinate, Error}; -use crate::event::{EventId, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::event::{EventId, Tag, TagCodec, impl_tag_codec_conversions}; use crate::{PublicKey, RelayUrl}; /// Standardized NIP-01 tags @@ -54,7 +54,7 @@ impl TagCodec for Nip01Tag { let mut iter = tag.into_iter(); // Extract first value - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; // Match kind match kind.as_ref() { @@ -81,7 +81,7 @@ impl TagCodec for Nip01Tag { relay_hint, }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -116,7 +116,7 @@ where T: Iterator, S: AsRef, { - let coordinate: Coordinate = take_coordinate::<_, _, Error>(&mut iter)?; + let coordinate: Coordinate = take_coordinate(&mut iter)?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; Ok((coordinate, relay_hint)) @@ -145,7 +145,7 @@ where T: Iterator, S: AsRef, { - let id: EventId = take_event_id::<_, _, Error>(&mut iter)?; + let id: EventId = take_event_id(&mut iter)?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; let public_key: Option = take_and_parse_optional_public_key(&mut iter)?; @@ -183,7 +183,7 @@ where T: Iterator, S: AsRef, { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; + let public_key: PublicKey = take_public_key(&mut iter)?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; Ok((public_key, relay_hint)) @@ -208,7 +208,7 @@ pub(in super::super) fn serialize_p_tag( #[cfg(test)] mod tests { use super::*; - use crate::{event, key}; + use crate::error::ErrorKind; #[test] fn test_standardized_a_tag() { @@ -242,12 +242,12 @@ mod tests { // Invalid coordinate let tag = vec!["a", "hello"]; let err = Nip01Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::InvalidCoordinate); + assert_eq!(err.kind(), ErrorKind::Invalid); // Missing coordinate let tag = vec!["a"]; let err = Nip01Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("coordinate"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -330,12 +330,12 @@ mod tests { // Invalid ID let tag = vec!["e", "hello"]; let err = Nip01Tag::parse(&tag).unwrap_err(); - assert!(matches!(err, Error::Event(event::Error::Hex(_)))); + assert_eq!(err.kind(), ErrorKind::Malformed); // Missing ID let tag = vec!["e"]; let err = Nip01Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("event ID"))); + assert_eq!(err.kind(), ErrorKind::Missing); // Issue: https://gitworkshop.dev/yukikishimoto.com/nostr/issues/note15xl8ae8dnmt26adfw6ec8gshxxs242vrvsa3v36ctwq2x9gglkustlxlwa let result = Nip01Tag::parse(["e", raw, "", "", ""]).unwrap(); @@ -359,7 +359,7 @@ mod tests { // Missing identifier let tag = vec!["d"]; let err = Nip01Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("identifier"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -394,11 +394,11 @@ mod tests { // Invalid public key let tag = vec!["p", "hello"]; let err = Nip01Tag::parse(&tag).unwrap_err(); - assert!(matches!(err, Error::Keys(key::Error::Hex(_)))); + assert_eq!(err.kind(), ErrorKind::Malformed); // Missing public key let tag = vec!["p"]; let err = Nip01Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("public key"))); + assert_eq!(err.kind(), ErrorKind::Missing); } } diff --git a/crates/nostr/src/nips/nip02.rs b/crates/nostr/src/nips/nip02.rs index 44a015322..e4347d930 100644 --- a/crates/nostr/src/nips/nip02.rs +++ b/crates/nostr/src/nips/nip02.rs @@ -8,56 +8,17 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::fmt; -use super::util::{take_and_parse_optional_relay_url, take_optional_string, take_public_key}; +use super::util::{ + missing_tag_kind, take_and_parse_optional_relay_url, take_optional_string, take_public_key, + unknown_tag, +}; +use crate::error::Error; use crate::event::{ - EventBuilder, EventBuilderTemplate, Kind, Tag, TagCodec, TagCodecError, - impl_tag_codec_conversions, + EventBuilder, EventBuilderTemplate, Kind, Tag, TagCodec, impl_tag_codec_conversions, }; -use crate::key::{self, PublicKey}; -use crate::types::url::{self, RelayUrl}; - -/// NIP-02 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Keys error - Keys(key::Error), - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Keys(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} +use crate::key::PublicKey; +use crate::types::url::RelayUrl; /// Contact #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -122,7 +83,7 @@ impl TagCodec for Nip02Tag { let mut iter = tag.into_iter(); // Extract first value - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; // Match kind match kind.as_ref() { @@ -134,7 +95,7 @@ impl TagCodec for Nip02Tag { alias, }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -171,7 +132,7 @@ where T: Iterator, S: AsRef, { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; + let public_key: PublicKey = take_public_key(&mut iter)?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; let alias: Option = take_optional_string(&mut iter); @@ -217,7 +178,7 @@ impl EventBuilderTemplate for ContactListBuilder { #[cfg(test)] mod tests { - use super::{Error, *}; + use super::*; use crate::prelude::*; #[test] @@ -280,12 +241,12 @@ mod tests { // Invalid public key let tag = vec!["p", "hello"]; let err = Nip02Tag::parse(&tag).unwrap_err(); - assert!(matches!(err, Error::Keys(key::Error::Hex(_)))); + assert_eq!(err.kind(), ErrorKind::Malformed); // Missing public key let tag = vec!["p"]; let err = Nip02Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("public key"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] diff --git a/crates/nostr/src/nips/nip04/impl.rs b/crates/nostr/src/nips/nip04/impl.rs index 893bf0052..97f146c3b 100644 --- a/crates/nostr/src/nips/nip04/impl.rs +++ b/crates/nostr/src/nips/nip04/impl.rs @@ -5,8 +5,6 @@ use alloc::string::String; use alloc::vec::Vec; -use core::fmt; -use core::fmt::Debug; use aes::Aes256; use aes::cipher::block_padding::Pkcs7; @@ -20,48 +18,12 @@ use rand::rand_core::UnwrapErr; #[cfg(all(feature = "std", feature = "os-rng"))] use rand::rngs::SysRng; -use crate::{PublicKey, SecretKey, key, util}; +use crate::error::{Error, ErrorKind}; +use crate::{PublicKey, SecretKey, util}; type Aes256CbcEnc = Encryptor; type Aes256CbcDec = Decryptor; -/// `NIP04` error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Key error - Key(key::Error), - /// Invalid content format - InvalidContentFormat, - /// Error while decoding from base64 - Base64Decode, - /// Error while encoding to UTF-8 - Utf8Encode, - /// Wrong encryption block mode - WrongBlockMode, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Key(e) => write!(f, "{e}"), - Self::InvalidContentFormat => f.write_str("Invalid NIP04 content format"), - Self::Base64Decode => f.write_str("Error while decoding NIP04 from base64"), - Self::Utf8Encode => f.write_str("Error while encoding NIP04 to UTF-8"), - Self::WrongBlockMode => f.write_str( - "Wrong encryption block mode. The content must be encrypted using CBC mode!", - ), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) - } -} - /// Encrypt /// ///
Unsecure! Deprecated in favor of NIP17!
@@ -142,21 +104,24 @@ where let encrypted_content: String = encrypted_content.into(); let parsed_content: Vec<&str> = encrypted_content.split("?iv=").collect(); if parsed_content.len() != 2 { - return Err(Error::InvalidContentFormat); + return Err(Error::with_static_message( + ErrorKind::Malformed, + "invalid content format", + )); } let encrypted_content: Vec = general_purpose::STANDARD .decode(parsed_content[0]) - .map_err(|_| Error::Base64Decode)?; + .map_err(Error::malformed_display)?; let iv: Vec = general_purpose::STANDARD .decode(parsed_content[1]) - .map_err(|_| Error::Base64Decode)?; + .map_err(Error::malformed_display)?; let key: [u8; 32] = util::generate_shared_key(secret_key, public_key)?; let cipher = Aes256CbcDec::new(&key.into(), iv.as_slice().into()); let result = cipher .decrypt_padded_vec_mut::(&encrypted_content) - .map_err(|_| Error::WrongBlockMode)?; + .map_err(|_| Error::with_static_message(ErrorKind::Crypto, "wrong block mode"))?; Ok(result) } @@ -174,7 +139,7 @@ where T: Into, { let result = decrypt_to_bytes(secret_key, public_key, encrypted_content)?; - String::from_utf8(result).map_err(|_| Error::Utf8Encode) + String::from_utf8(result).map_err(Error::malformed) } #[cfg(all(test, feature = "std", feature = "os-rng"))] @@ -226,8 +191,9 @@ mod tests { &receiver_pk, "invalidcontentformat" ) - .unwrap_err(), - Error::InvalidContentFormat + .unwrap_err() + .kind(), + ErrorKind::Malformed ); assert_eq!( decrypt( @@ -235,8 +201,9 @@ mod tests { &receiver_pk, "badbase64?iv=encode" ) - .unwrap_err(), - Error::Base64Decode + .unwrap_err() + .kind(), + ErrorKind::Malformed ); // Content encrypted with aes256 using GCM mode @@ -246,8 +213,9 @@ mod tests { &receiver_pk, "nseh0cQPEFID5C0CxYdcPwp091NhRQ==?iv=8PHy8/T19vf4+fr7/P3+/w==" ) - .unwrap_err(), - Error::WrongBlockMode + .unwrap_err() + .kind(), + ErrorKind::Crypto ); } } diff --git a/crates/nostr/src/nips/nip05.rs b/crates/nostr/src/nips/nip05.rs index 013bdc056..80dd3315d 100644 --- a/crates/nostr/src/nips/nip05.rs +++ b/crates/nostr/src/nips/nip05.rs @@ -13,47 +13,11 @@ use core::str::Split; use serde_json::Value; -use crate::types::url::{ParseError, Url}; +use crate::error::{Error, ErrorKind}; +use crate::types::url::Url; +use crate::util::parse_json; use crate::{PublicKey, RelayUrl}; -/// `NIP05` error -#[derive(Debug)] -pub enum Error { - /// Error deserializing JSON data - Json(serde_json::Error), - /// Url error - Url(ParseError), - /// Invalid format - InvalidFormat, - /// Impossible to verify - ImpossibleToVerify, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Json(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::InvalidFormat => f.write_str("invalid format"), - Self::ImpossibleToVerify => f.write_str("impossible to verify"), - } - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e) - } -} - -impl From for Error { - fn from(e: ParseError) -> Self { - Self::Url(e) - } -} - /// NIP-05 address #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Nip05Address { @@ -81,12 +45,17 @@ impl Nip05Address { // Found only a part, meaning that `@` is missing (Some(address), None) => ("_", address), // Invalid format - _ => return Err(Error::InvalidFormat), + _ => { + return Err(Error::with_static_message( + ErrorKind::Malformed, + "invalid format", + )); + } }; // Compose and parse the URLS let url: String = format!("https://{domain}/.well-known/nostr.json?name={name}"); - let url: Url = Url::parse(&url)?; + let url: Url = Url::parse(&url).map_err(Error::malformed)?; Ok(Self { name: name.to_string(), @@ -138,8 +107,9 @@ impl Nip05Profile { /// /// pub fn from_json(address: &Nip05Address, json: &Value) -> Result { - let public_key: PublicKey = - get_key_from_json(json, address).ok_or(Error::ImpossibleToVerify)?; + let public_key: PublicKey = get_key_from_json(json, address).ok_or_else(|| { + Error::with_static_message(ErrorKind::Invalid, "impossible to verify") + })?; let relays: Vec = get_relays_from_json(json, &public_key); let nip46: Vec = get_nip46_relays_from_json(json, &public_key); @@ -155,7 +125,7 @@ impl Nip05Profile { /// #[inline] pub fn from_raw_json(address: &Nip05Address, raw_json: &str) -> Result { - let json: Value = serde_json::from_str(raw_json)?; + let json: Value = parse_json(&raw_json)?; Self::from_json(address, &json) } } @@ -204,7 +174,7 @@ pub fn verify_from_raw_json( address: &Nip05Address, raw_json: &str, ) -> Result { - let json: Value = serde_json::from_str(raw_json)?; + let json: Value = parse_json(&raw_json)?; Ok(verify_from_json(public_key, address, &json)) } diff --git a/crates/nostr/src/nips/nip06/bip32.rs b/crates/nostr/src/nips/nip06/bip32.rs index a883d538b..6234febb8 100644 --- a/crates/nostr/src/nips/nip06/bip32.rs +++ b/crates/nostr/src/nips/nip06/bip32.rs @@ -11,12 +11,9 @@ use core::fmt; use hashes::{Hash, HashEngine, Hmac, HmacEngine, hash160, sha512}; use secp256k1::{self, PublicKey, Secp256k1, SecretKey, Signing}; -/// A BIP32 error #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Error { - /// A secp256k1 error occurred +pub(super) enum Error { Secp256k1(secp256k1::Error), - /// A child number was provided that was out of range InvalidChildNumber(u32), } diff --git a/crates/nostr/src/nips/nip06/mod.rs b/crates/nostr/src/nips/nip06/mod.rs index 787aa7c1c..9beedbeaa 100644 --- a/crates/nostr/src/nips/nip06/mod.rs +++ b/crates/nostr/src/nips/nip06/mod.rs @@ -6,9 +6,9 @@ //! //! +use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; -use core::fmt; use bip39::Mnemonic; use secp256k1::{Secp256k1, Signing}; @@ -18,43 +18,12 @@ mod bip32; use self::bip32::{ChildNumber, Xpriv}; #[cfg(feature = "std")] use crate::SECP256K1; +use crate::error::{Error, ErrorKind}; use crate::{Keys, SecretKey}; const PURPOSE: u32 = 44; const COIN: u32 = 1237; -/// `NIP06` error -#[derive(Debug, Eq, PartialEq)] -pub enum Error { - /// BIP32 error - BIP32(bip32::Error), - /// BIP39 error - BIP39(bip39::Error), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::BIP32(e) => e.fmt(f), - Self::BIP39(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: bip32::Error) -> Self { - Self::BIP32(e) - } -} - -impl From for Error { - fn from(e: bip39::Error) -> Self { - Self::BIP39(e) - } -} - /// NIP06 utils /// /// @@ -142,14 +111,15 @@ impl FromMnemonic for Keys { S: AsRef, { // Parse mnemonic - let mnemonic: Mnemonic = Mnemonic::parse_normalized(mnemonic.as_ref())?; + let mnemonic: Mnemonic = Mnemonic::parse_normalized(mnemonic.as_ref()) + .map_err(|e| Error::new(ErrorKind::Malformed, e.to_string()))?; // Convert mnemonic to seed let seed: [u8; 64] = mnemonic .to_seed_normalized(passphrase.as_ref().map(|s| s.as_ref()).unwrap_or_default()); // Derive BIP32 root key - let root_key: Xpriv = Xpriv::new_master(&seed)?; + let root_key: Xpriv = Xpriv::new_master(&seed).map_err(Error::malformed)?; // Unwrap idx let account: u32 = account.unwrap_or_default(); @@ -158,11 +128,11 @@ impl FromMnemonic for Keys { // Compose derivation path let path: Vec = vec![ - ChildNumber::from_hardened_idx(PURPOSE)?, - ChildNumber::from_hardened_idx(COIN)?, - ChildNumber::from_hardened_idx(account)?, - ChildNumber::from_normal_idx(_type)?, - ChildNumber::from_normal_idx(index)?, + ChildNumber::from_hardened_idx(PURPOSE).map_err(Error::invalid)?, + ChildNumber::from_hardened_idx(COIN).map_err(Error::invalid)?, + ChildNumber::from_hardened_idx(account).map_err(Error::invalid)?, + ChildNumber::from_normal_idx(_type).map_err(Error::invalid)?, + ChildNumber::from_normal_idx(index).map_err(Error::invalid)?, ]; // Derive secret key diff --git a/crates/nostr/src/nips/nip10.rs b/crates/nostr/src/nips/nip10.rs index bb02d1db3..e5ef40866 100644 --- a/crates/nostr/src/nips/nip10.rs +++ b/crates/nostr/src/nips/nip10.rs @@ -12,67 +12,16 @@ use core::fmt; use core::str::FromStr; use super::util::{ - take_and_parse_optional_public_key, take_and_parse_optional_relay_url, take_event_id, + missing_tag_kind, take_and_parse_optional_public_key, take_and_parse_optional_relay_url, + take_event_id, unknown_tag, }; -use crate::event::{self, EventId, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::key::{self, PublicKey}; -use crate::types::url::{self, RelayUrl}; +use crate::error::{Error, ErrorKind}; +use crate::event::{EventId, Tag, TagCodec, impl_tag_codec_conversions}; +use crate::key::PublicKey; +use crate::types::url::RelayUrl; const EVENT: &str = "e"; -/// NIP10 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Keys error - Keys(key::Error), - /// Event error - Event(event::Error), - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), - /// Invalid marker - InvalidMarker, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Keys(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidMarker => f.write_str("invalid marker"), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Marker /// /// @@ -100,7 +49,10 @@ impl FromStr for Marker { match marker { "root" => Ok(Self::Root), "reply" => Ok(Self::Reply), - _ => Err(Error::InvalidMarker), + _ => Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid marker", + )), } } } @@ -158,7 +110,7 @@ impl TagCodec for Nip10Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { EVENT => { @@ -170,7 +122,7 @@ impl TagCodec for Nip10Tag { public_key, }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -229,7 +181,7 @@ where T: Iterator, S: AsRef, { - let id: EventId = take_event_id::<_, _, Error>(&mut iter)?; + let id: EventId = take_event_id(&mut iter)?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; let marker: Option = match iter.next() { @@ -259,14 +211,14 @@ mod tests { fn test_parse_empty_tag() { let tag: Vec = Vec::new(); let err = Nip10Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::missing_tag_kind())); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] fn test_non_existing_tag() { let tag = vec!["p"]; let err = Nip10Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Unknown)); + assert_eq!(err.kind(), ErrorKind::Malformed); } #[test] @@ -353,7 +305,7 @@ mod tests { public_key.to_hex(), ]; let err = Nip10Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::InvalidMarker); + assert_eq!(err.kind(), ErrorKind::Invalid); } #[test] @@ -361,16 +313,16 @@ mod tests { let hex = "19bb195b83fd26db217b6feebb444de4808d90eb4375c31c75ba5bb5c5c10cfc"; let id = EventId::from_hex(hex).unwrap(); - let result = Nip10Tag::parse(["e", hex, "", "mention"]); + let result = Nip10Tag::parse(["e", hex, "", "mention"]).unwrap(); assert_eq!( result, - Ok(Nip10Tag::Event { + Nip10Tag::Event { id, relay_hint: None, marker: None, public_key: None, - }) + } ); } } diff --git a/crates/nostr/src/nips/nip11.rs b/crates/nostr/src/nips/nip11.rs index b8a4201c5..627801af8 100644 --- a/crates/nostr/src/nips/nip11.rs +++ b/crates/nostr/src/nips/nip11.rs @@ -57,7 +57,7 @@ impl RelayInformationDocument { } } -impl_json_methods!(RelayInformationDocument, serde_json::Error); +impl_json_methods!(RelayInformationDocument); /// These are limitations imposed by the relay on clients. Your client should /// expect that requests which exceed these practical limitations are rejected or fail immediately. diff --git a/crates/nostr/src/nips/nip13/mod.rs b/crates/nostr/src/nips/nip13/mod.rs index b2c35be9e..f0d330ab4 100644 --- a/crates/nostr/src/nips/nip13/mod.rs +++ b/crates/nostr/src/nips/nip13/mod.rs @@ -11,9 +11,8 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use core::any::Any; -use core::fmt; use core::fmt::Debug; -use core::num::{NonZeroU8, ParseIntError}; +use core::num::NonZeroU8; #[cfg(feature = "std")] mod blocking_wrapper; @@ -24,45 +23,14 @@ mod single_thread; #[cfg(feature = "pow-multi-thread")] pub use self::multi_thread::*; pub use self::single_thread::*; -use super::util::take_and_parse_from_str; +use super::util::{missing_tag_kind, take_and_parse_from_str, unknown_tag}; use crate::UnsignedEvent; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; use crate::util::BoxedFuture; const NONCE: &str = "nonce"; -/// NIP-13 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Parse Int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseInt(e) => fmt::Display::fmt(e, f), - Self::Codec(e) => fmt::Display::fmt(e, f), - } - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-13 tags /// /// @@ -87,14 +55,14 @@ impl TagCodec for Nip13Tag { { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { NONCE => { let (nonce, difficulty) = parse_nonce_tag(iter)?; Ok(Self::Nonce { nonce, difficulty }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -116,8 +84,8 @@ where T: Iterator, S: AsRef, { - let nonce: u128 = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "nonce")?; - let difficulty: u8 = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "difficulty")?; + let nonce: u128 = take_and_parse_from_str(&mut iter, "nonce")?; + let difficulty: u8 = take_and_parse_from_str(&mut iter, "difficulty")?; Ok((nonce, difficulty)) } @@ -207,7 +175,7 @@ pub mod tests { use hashes::sha256::Hash as Sha256Hash; - use super::{Error, *}; + use super::*; use crate::prelude::*; #[test] @@ -228,7 +196,7 @@ pub mod tests { fn test_parse_nonce_tag_missing_difficulty() { let tag = vec!["nonce", "776797"]; let err = Nip13Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("difficulty"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] diff --git a/crates/nostr/src/nips/nip15.rs b/crates/nostr/src/nips/nip15.rs index f937877a1..17c74fd8e 100644 --- a/crates/nostr/src/nips/nip15.rs +++ b/crates/nostr/src/nips/nip15.rs @@ -60,7 +60,7 @@ impl From for Vec { } } -impl_json_methods!(StallData, serde_json::Error); +impl_json_methods!(StallData); /// Payload for creating or updating product #[derive(Debug, Clone, Serialize, Deserialize)] @@ -174,7 +174,7 @@ impl From for Vec { } } -impl_json_methods!(ProductData, serde_json::Error); +impl_json_methods!(ProductData); /// A shipping method as defined by the merchant #[derive(Debug, Clone, Serialize, Deserialize)] @@ -229,7 +229,7 @@ impl ShippingMethod { } } -impl_json_methods!(ShippingMethod, serde_json::Error); +impl_json_methods!(ShippingMethod); /// Delivery cost for shipping method as defined by the merchant in the product #[derive(Debug, Clone, Serialize, Deserialize)] @@ -240,7 +240,7 @@ pub struct ShippingCost { pub cost: f64, } -impl_json_methods!(ShippingCost, serde_json::Error); +impl_json_methods!(ShippingCost); /// Payload for customer creating an order #[derive(Debug, Clone, Serialize, Deserialize)] @@ -264,7 +264,7 @@ pub struct CustomerOrder { pub shipping_id: String, } -impl_json_methods!(CustomerOrder, serde_json::Error); +impl_json_methods!(CustomerOrder); /// Payload for a merchant to create a payment request #[derive(Debug, Clone, Serialize, Deserialize)] @@ -278,7 +278,7 @@ pub struct MerchantPaymentRequest { pub payment_options: Vec, } -impl_json_methods!(MerchantPaymentRequest, serde_json::Error); +impl_json_methods!(MerchantPaymentRequest); /// Payload to notify a customer about the received payment and or shipping #[derive(Debug, Clone, Serialize, Deserialize)] @@ -294,7 +294,7 @@ pub struct MerchantVerifyPayment { pub shipped: bool, } -impl_json_methods!(MerchantVerifyPayment, serde_json::Error); +impl_json_methods!(MerchantVerifyPayment); /// A customers contact options #[derive(Debug, Clone, Serialize, Deserialize)] @@ -307,7 +307,7 @@ pub struct CustomerContact { pub email: Option, } -impl_json_methods!(CustomerContact, serde_json::Error); +impl_json_methods!(CustomerContact); /// An item in the order #[derive(Debug, Clone, Serialize, Deserialize)] @@ -318,7 +318,7 @@ pub struct CustomerOrderItem { pub quantity: u64, } -impl_json_methods!(CustomerOrderItem, serde_json::Error); +impl_json_methods!(CustomerOrderItem); /// A payment option of an invoice #[derive(Debug, Clone, Serialize, Deserialize)] @@ -330,7 +330,7 @@ pub struct PaymentOption { pub link: String, } -impl_json_methods!(PaymentOption, serde_json::Error); +impl_json_methods!(PaymentOption); #[cfg(test)] mod tests { diff --git a/crates/nostr/src/nips/nip17.rs b/crates/nostr/src/nips/nip17.rs index 0cfbbe5f1..662210653 100644 --- a/crates/nostr/src/nips/nip17.rs +++ b/crates/nostr/src/nips/nip17.rs @@ -11,86 +11,28 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] use super::nip44::{AsyncNip44, Nip44}; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] -use super::nip59::{self, GiftWrapBuilder}; -use super::util::take_relay_url; +use super::nip59::GiftWrapBuilder; +use super::util::{missing_tag_kind, take_relay_url, unknown_tag}; +use crate::error::Error; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] use crate::event::{ AsyncSignEvent, EventBuilder, FinalizeEvent, FinalizeEventAsync, FinalizeUnsignedEvent, Kind, SignEvent, UnsignedEvent, }; -use crate::event::{Event, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::event::{Event, Tag, TagCodec, impl_tag_codec_conversions}; use crate::key::PublicKey; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] use crate::key::{AsyncGetPublicKey, GetPublicKey}; -#[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] -use crate::signer::SignerError; -use crate::types::url::{self, RelayUrl}; +use crate::types::url::RelayUrl; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] use crate::util::BoxedFuture; const RELAY: &str = "relay"; -/// NIP-17 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), - /// Signer error - #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] - Signer(SignerError), - /// NIP-59 error - #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] - NIP59(nip59::Error), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] - Self::Signer(e) => e.fmt(f), - #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] - Self::NIP59(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - -#[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } -} - -#[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] -impl From for Error { - fn from(e: nip59::Error) -> Self { - Self::NIP59(e) - } -} - /// Private Direct Message event builder. /// /// # Example @@ -166,16 +108,16 @@ where type Error = Error; fn finalize(self, signer: &S) -> Result { - let public_key: PublicKey = signer.get_public_key().map_err(SignerError::backend)?; + let public_key: PublicKey = signer.get_public_key().map_err(Error::other)?; let rumor: UnsignedEvent = make_rumor( public_key, self.receiver, self.message, self.rumor_extra_tags, ); - Ok(GiftWrapBuilder::new(self.receiver, rumor) + GiftWrapBuilder::new(self.receiver, rumor) .extra_tags(self.extra_tags) - .finalize(signer)?) + .finalize(signer) } } @@ -192,20 +134,18 @@ where S: 'a, { Box::pin(async move { - let public_key: PublicKey = signer - .get_public_key_async() - .await - .map_err(SignerError::backend)?; + let public_key: PublicKey = + signer.get_public_key_async().await.map_err(Error::other)?; let rumor: UnsignedEvent = make_rumor( public_key, self.receiver, self.message, self.rumor_extra_tags, ); - Ok(GiftWrapBuilder::new(self.receiver, rumor) + GiftWrapBuilder::new(self.receiver, rumor) .extra_tags(self.extra_tags) .finalize_async(signer) - .await?) + .await }) } } @@ -238,7 +178,6 @@ pub enum Nip17Tag { impl TagCodec for Nip17Tag { type Error = Error; - /// Parse NIP-17 standardized tag fn parse(tag: I) -> Result where I: IntoIterator, @@ -248,15 +187,15 @@ impl TagCodec for Nip17Tag { let mut iter = tag.into_iter(); // Extract first value - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; // Match kind match kind.as_ref() { RELAY => { - let url: RelayUrl = take_relay_url::<_, _, Error>(&mut iter)?; + let url: RelayUrl = take_relay_url(&mut iter)?; Ok(Self::Relay(url)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -285,19 +224,20 @@ pub fn extract_relay_list(event: &Event) -> impl Iterator + '_ #[cfg(test)] mod tests { use super::*; + use crate::error::ErrorKind; #[test] fn test_parse_empty_tag() { let tag: Vec = Vec::new(); let err = Nip17Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::missing_tag_kind())); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] fn test_non_existing_tag() { let tag = vec!["p"]; let err = Nip17Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Unknown)); + assert_eq!(err.kind(), ErrorKind::Malformed); } #[test] @@ -314,6 +254,6 @@ mod tests { fn test_missing_relay_url() { let tag = vec!["relay"]; let err = Nip17Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("relay URL"))); + assert_eq!(err.kind(), ErrorKind::Missing); } } diff --git a/crates/nostr/src/nips/nip18.rs b/crates/nostr/src/nips/nip18.rs index b61435cfc..1845a618a 100644 --- a/crates/nostr/src/nips/nip18.rs +++ b/crates/nostr/src/nips/nip18.rs @@ -8,91 +8,22 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::fmt; -use core::num::ParseIntError; -use super::nip01::{self, Coordinate}; +use super::nip01::Coordinate; use super::util::{ - take_and_parse_from_str, take_and_parse_optional_public_key, take_and_parse_optional_relay_url, - take_event_id, take_public_key, + missing_tag_kind, missing_value, take_and_parse_from_str, take_and_parse_optional_public_key, + take_and_parse_optional_relay_url, take_event_id, take_public_key, unknown_tag, }; -use crate::event::{self, EventId, Kind, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::key::{self, PublicKey}; -use crate::types::url::{self, RelayUrl}; +use crate::error::Error; +use crate::event::{EventId, Kind, Tag, TagCodec, impl_tag_codec_conversions}; +use crate::key::PublicKey; +use crate::types::url::RelayUrl; const EVENT: &str = "e"; const KIND: &str = "k"; const PUBLIC_KEY: &str = "p"; const QUOTE: &str = "q"; -/// NIP-18 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Event error - Event(event::Error), - /// Keys error - Keys(key::Error), - /// NIP-01 error - Nip01(nip01::Error), - /// Relay URL error - RelayUrl(url::Error), - /// Parse int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Event(e) => e.fmt(f), - Self::Keys(e) => e.fmt(f), - Self::Nip01(e) => e.fmt(f), - Self::RelayUrl(e) => e.fmt(f), - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: nip01::Error) -> Self { - Self::Nip01(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::RelayUrl(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-18 tags /// /// @@ -141,7 +72,7 @@ impl TagCodec for Nip18Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { EVENT => { @@ -149,7 +80,7 @@ impl TagCodec for Nip18Tag { Ok(Self::Event { id, relay_hint }) } KIND => { - let kind: Kind = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "kind")?; + let kind: Kind = take_and_parse_from_str(&mut iter, "kind")?; Ok(Self::Kind(kind)) } PUBLIC_KEY => { @@ -160,7 +91,7 @@ impl TagCodec for Nip18Tag { }) } QUOTE => parse_q_tag(iter), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -240,7 +171,7 @@ where T: Iterator, S: AsRef, { - let id: EventId = take_event_id::<_, _, Error>(&mut iter)?; + let id: EventId = take_event_id(&mut iter)?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; Ok((id, relay_hint)) @@ -251,7 +182,7 @@ where T: Iterator, S: AsRef, { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; + let public_key: PublicKey = take_public_key(&mut iter)?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; Ok((public_key, relay_hint)) @@ -262,7 +193,7 @@ where T: Iterator, S: AsRef, { - let value: S = iter.next().ok_or(TagCodecError::Missing("event ID"))?; + let value: S = iter.next().ok_or_else(|| missing_value("event ID"))?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; match EventId::from_hex(value.as_ref()) { diff --git a/crates/nostr/src/nips/nip19.rs b/crates/nostr/src/nips/nip19.rs index 49be59d7a..ed9d4966b 100644 --- a/crates/nostr/src/nips/nip19.rs +++ b/crates/nostr/src/nips/nip19.rs @@ -2,7 +2,7 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -//! NIP19: bech32-encoded entities +//! NIP-19: bech32-encoded entities //! //! @@ -12,7 +12,6 @@ use alloc::borrow::Cow; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::convert::Infallible; -use core::fmt; use core::ops::Deref; use core::str::FromStr; @@ -21,10 +20,11 @@ use bech32::{self, Bech32, Hrp}; use super::nip01::{Coordinate, CoordinateBorrow}; use super::nip05::Nip05Profile; #[cfg(feature = "nip49")] -use super::nip49::{self, EncryptedSecretKey}; +use super::nip49::EncryptedSecretKey; +use crate::error::{Error, ErrorKind}; use crate::event::EventId; -use crate::types::url::{self, RelayUrl}; -use crate::{Event, Kind, PublicKey, SecretKey, event, key}; +use crate::types::url::RelayUrl; +use crate::{Event, Kind, PublicKey, SecretKey}; pub const PREFIX_BECH32_SECRET_KEY: &str = "nsec"; pub const PREFIX_BECH32_SECRET_KEY_ENCRYPTED: &str = "ncryptsec"; @@ -54,93 +54,28 @@ const FIXED_1_1_32_BYTES_TVL: usize = 1 + 1 + 32; /// 1 (type) + 1 (len) + 4 (value - 32-bit unsigned number) const FIXED_KIND_BYTES_TVL: usize = 1 + 1 + 4; -/// `NIP19` error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Relay Url parse error - RelayUrl(url::Error), - /// Bech32 decode error. - Bech32Decode(bech32::DecodeError), - /// Bech32 encode error - Bech32Encode(bech32::EncodeError), - /// Keys error - Keys(key::Error), - /// Event error - Event(event::Error), - /// NIP49 error - #[cfg(feature = "nip49")] - NIP49(nip49::Error), - /// Wrong prefix or variant - WrongPrefix, - /// Field missing - FieldMissing(String), - /// TLV error - TLV, - /// From slice error - TryFromSlice, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::RelayUrl(e) => e.fmt(f), - Self::Bech32Decode(e) => e.fmt(f), - Self::Bech32Encode(e) => e.fmt(f), - Self::Keys(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - #[cfg(feature = "nip49")] - Self::NIP49(e) => e.fmt(f), - Self::WrongPrefix => f.write_str("Wrong prefix"), - Self::FieldMissing(name) => write!(f, "Field missing: {name}"), - Self::TLV => f.write_str("TLV error"), - Self::TryFromSlice => f.write_str("From slice error"), - } - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::RelayUrl(e) - } +#[inline] +fn wrong_prefix() -> Error { + Error::with_static_message(ErrorKind::Invalid, "wrong prefix") } -impl From for Error { - fn from(e: bech32::DecodeError) -> Self { - Self::Bech32Decode(e) - } -} - -impl From for Error { - fn from(e: bech32::EncodeError) -> Self { - Self::Bech32Encode(e) - } +#[inline] +fn invalid_tlv() -> Error { + Error::with_static_message(ErrorKind::Malformed, "invalid TLV") } -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } +fn field_missing(field: &'static str) -> Error { + Error::with_static_message(ErrorKind::Missing, field) } -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } +#[inline] +fn bech32_decode(s: &str) -> Result<(Hrp, Vec), Error> { + bech32::decode(s).map_err(|e| Error::new(ErrorKind::Malformed, e.to_string())) } -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -#[cfg(feature = "nip49")] -impl From for Error { - fn from(e: nip49::Error) -> Self { - Self::NIP49(e) - } +#[inline] +fn bech32_encode(hrp: Hrp, data: &[u8]) -> Result { + bech32::encode::(hrp, data).map_err(|e| Error::new(ErrorKind::Invalid, e.to_string())) } /// To ensure total matching on prefixes when decoding a [`Nip19`] object @@ -173,7 +108,7 @@ impl Nip19Prefix { PREFIX_BECH32_PROFILE => Ok(Nip19Prefix::NProfile), PREFIX_BECH32_EVENT => Ok(Nip19Prefix::NEvent), PREFIX_BECH32_COORDINATE => Ok(Nip19Prefix::NAddr), - _ => Err(Error::WrongPrefix), + _ => Err(wrong_prefix()), } } @@ -205,7 +140,7 @@ impl FromStr for Nip19Prefix { m if m.starts_with(PREFIX_BECH32_PROFILE) => Ok(Nip19Prefix::NProfile), m if m.starts_with(PREFIX_BECH32_EVENT) => Ok(Nip19Prefix::NEvent), m if m.starts_with(PREFIX_BECH32_COORDINATE) => Ok(Nip19Prefix::NAddr), - _ => Err(Error::WrongPrefix), + _ => Err(wrong_prefix()), } } } @@ -246,7 +181,7 @@ impl FromBech32 for Nip19 { type Err = Error; fn from_bech32(hash: &str) -> Result { - let (hrp, data) = bech32::decode(hash)?; + let (hrp, data) = bech32_decode(hash)?; let prefix: Nip19Prefix = Nip19Prefix::from_hrp(hrp.as_str())?; match prefix { @@ -285,13 +220,13 @@ impl FromBech32 for SecretKey { type Err = Error; fn from_bech32(secret_key: &str) -> Result { - let (hrp, data) = bech32::decode(secret_key)?; + let (hrp, data) = bech32_decode(secret_key)?; if hrp != HRP_SECRET_KEY { - return Err(Error::WrongPrefix); + return Err(wrong_prefix()); } - Ok(Self::from_slice(data.as_slice())?) + Self::from_slice(data.as_slice()) } } @@ -311,13 +246,13 @@ impl FromBech32 for EncryptedSecretKey { type Err = Error; fn from_bech32(secret_key: &str) -> Result { - let (hrp, data) = bech32::decode(secret_key)?; + let (hrp, data) = bech32_decode(secret_key)?; if hrp != HRP_SECRET_KEY_ENCRYPTED { - return Err(Error::WrongPrefix); + return Err(wrong_prefix()); } - Ok(Self::from_slice(data.as_slice())?) + Self::from_slice(data.as_slice()) } } @@ -326,10 +261,7 @@ impl ToBech32 for EncryptedSecretKey { type Err = Error; fn to_bech32(&self) -> Result { - Ok(bech32::encode::( - HRP_SECRET_KEY_ENCRYPTED, - &self.as_vec(), - )?) + bech32_encode(HRP_SECRET_KEY_ENCRYPTED, &self.as_vec()) } } @@ -337,11 +269,11 @@ impl FromBech32 for PublicKey { type Err = Error; fn from_bech32(public_key: &str) -> Result { - let (hrp, data) = bech32::decode(public_key)?; + let (hrp, data) = bech32_decode(public_key)?; // Parse npub if hrp == HRP_PUBLIC_KEY { - return Ok(Self::from_slice(data.as_slice())?); + return Self::from_slice(data.as_slice()); } // Parse nprofile @@ -350,7 +282,7 @@ impl FromBech32 for PublicKey { return Ok(profile.public_key); } - Err(Error::WrongPrefix) + Err(wrong_prefix()) } } @@ -366,11 +298,11 @@ impl FromBech32 for EventId { type Err = Error; fn from_bech32(id: &str) -> Result { - let (hrp, data) = bech32::decode(id)?; + let (hrp, data) = bech32_decode(id)?; // Parse note if hrp == HRP_NOTE_ID { - return Ok(Self::from_slice(data.as_slice())?); + return Self::from_slice(data.as_slice()); } // Parse nevent @@ -379,7 +311,7 @@ impl FromBech32 for EventId { return Ok(event.event_id); } - Err(Error::WrongPrefix) + Err(wrong_prefix()) } } @@ -440,11 +372,11 @@ impl Nip19Event { let mut relays: Vec = Vec::new(); while !data.is_empty() { - let t = data.first().ok_or(Error::TLV)?; - let l = data.get(1).ok_or(Error::TLV)?; + let t = data.first().ok_or(invalid_tlv())?; + let l = data.get(1).ok_or(invalid_tlv())?; let l = *l as usize; - let bytes = data.get(2..l + 2).ok_or(Error::TLV)?; + let bytes = data.get(2..l + 2).ok_or(invalid_tlv())?; match *t { SPECIAL if event_id.is_none() => { @@ -464,8 +396,7 @@ impl Nip19Event { // The kind value must be a 32-bit unsigned number according to // https://github.com/nostr-protocol/nips/blob/37f6cbb775126b386414220f783ca0f5f85e7614/19.md#shareable-identifiers-with-extra-metadata let k: u16 = - u32::from_be_bytes(bytes.try_into().map_err(|_| Error::TryFromSlice)?) - as u16; + u32::from_be_bytes(bytes.try_into().map_err(Error::malformed)?) as u16; kind = Some(Kind::from(k)); } _ => (), @@ -475,7 +406,7 @@ impl Nip19Event { } Ok(Self { - event_id: event_id.ok_or_else(|| Error::FieldMissing("event id".to_string()))?, + event_id: event_id.ok_or_else(|| field_missing("event id"))?, author, kind, relays, @@ -493,10 +424,10 @@ impl FromBech32 for Nip19Event { type Err = Error; fn from_bech32(event: &str) -> Result { - let (hrp, data) = bech32::decode(event)?; + let (hrp, data) = bech32_decode(event)?; if hrp != HRP_EVENT { - return Err(Error::WrongPrefix); + return Err(wrong_prefix()); } Self::from_bech32_data(data) @@ -540,7 +471,7 @@ impl ToBech32 for Nip19Event { bytes.extend(relay.as_bytes()); // Value } - Ok(bech32::encode::(HRP_EVENT, &bytes)?) + bech32_encode(HRP_EVENT, &bytes) } } @@ -580,11 +511,11 @@ impl Nip19Profile { let mut relays: Vec = Vec::new(); while !data.is_empty() { - let t = data.first().ok_or(Error::TLV)?; - let l = data.get(1).ok_or(Error::TLV)?; + let t = data.first().ok_or(invalid_tlv())?; + let l = data.get(1).ok_or(invalid_tlv())?; let l = *l as usize; - let bytes = data.get(2..l + 2).ok_or(Error::TLV)?; + let bytes = data.get(2..l + 2).ok_or(invalid_tlv())?; match *t { SPECIAL if public_key.is_none() => { @@ -603,7 +534,7 @@ impl Nip19Profile { } Ok(Self { - public_key: public_key.ok_or_else(|| Error::FieldMissing("pubkey".to_string()))?, + public_key: public_key.ok_or_else(|| field_missing("public key"))?, relays, }) } @@ -628,7 +559,7 @@ impl ToBech32 for Nip19Profile { bytes.extend(url); // Value } - Ok(bech32::encode::(HRP_PROFILE, &bytes)?) + bech32_encode(HRP_PROFILE, &bytes) } } @@ -636,10 +567,10 @@ impl FromBech32 for Nip19Profile { type Err = Error; fn from_bech32(profile: &str) -> Result { - let (hrp, data) = bech32::decode(profile)?; + let (hrp, data) = bech32_decode(profile)?; if hrp != HRP_PROFILE { - return Err(Error::WrongPrefix); + return Err(wrong_prefix()); } Self::from_bech32_data(data) @@ -678,11 +609,11 @@ impl Nip19Coordinate { let mut relays: Vec = Vec::new(); while !data.is_empty() { - let t = data.first().ok_or(Error::TLV)?; - let l = data.get(1).ok_or(Error::TLV)?; + let t = data.first().ok_or(invalid_tlv())?; + let l = data.get(1).ok_or(invalid_tlv())?; let l = *l as usize; - let bytes: &[u8] = data.get(2..l + 2).ok_or(Error::TLV)?; + let bytes: &[u8] = data.get(2..l + 2).ok_or(invalid_tlv())?; match *t { SPECIAL if identifier.is_none() => { @@ -701,8 +632,7 @@ impl Nip19Coordinate { // The kind value must be a 32-bit unsigned number according to // https://github.com/nostr-protocol/nips/blob/37f6cbb775126b386414220f783ca0f5f85e7614/19.md#shareable-identifiers-with-extra-metadata let k: u16 = - u32::from_be_bytes(bytes.try_into().map_err(|_| Error::TryFromSlice)?) - as u16; + u32::from_be_bytes(bytes.try_into().map_err(Error::malformed)?) as u16; kind = Some(Kind::from(k)); } _ => (), @@ -712,9 +642,9 @@ impl Nip19Coordinate { } let coordinate = Coordinate { - kind: kind.ok_or_else(|| Error::FieldMissing("kind".to_string()))?, - public_key: pubkey.ok_or_else(|| Error::FieldMissing("pubkey".to_string()))?, - identifier: identifier.ok_or_else(|| Error::FieldMissing("identifier".to_string()))?, + kind: kind.ok_or_else(|| field_missing("kind"))?, + public_key: pubkey.ok_or_else(|| field_missing("public key"))?, + identifier: identifier.ok_or_else(|| field_missing("identifier"))?, }; Ok(Self { coordinate, relays }) @@ -725,10 +655,10 @@ impl FromBech32 for Nip19Coordinate { type Err = Error; fn from_bech32(addr: &str) -> Result { - let (hrp, data) = bech32::decode(addr)?; + let (hrp, data) = bech32_decode(addr)?; if hrp != HRP_COORDINATE { - return Err(Error::WrongPrefix); + return Err(wrong_prefix()); } Self::from_bech32_data(data) @@ -778,7 +708,7 @@ pub(super) fn coordinate_to_bech32<'a>( bytes.extend(relay.as_str().as_bytes()); // Value } - Ok(bech32::encode::(HRP_COORDINATE, &bytes)?) + bech32_encode(HRP_COORDINATE, &bytes) } #[cfg(test)] diff --git a/crates/nostr/src/nips/nip21.rs b/crates/nostr/src/nips/nip21.rs index 72f7cb1ff..eb388fd7f 100644 --- a/crates/nostr/src/nips/nip21.rs +++ b/crates/nostr/src/nips/nip21.rs @@ -2,15 +2,15 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -//! NIP21: `nostr:` URI scheme +//! NIP-21: `nostr:` URI scheme //! //! use alloc::string::String; -use core::convert::Infallible; -use core::fmt; -use super::nip19::{self, FromBech32, Nip19, Nip19Coordinate, Nip19Event, Nip19Profile, ToBech32}; +use super::nip19::{FromBech32, Nip19, Nip19Coordinate, Nip19Event, Nip19Profile, ToBech32}; +use crate::error::{Error, ErrorKind}; +use crate::nips::util::invalid_uri; use crate::{EventId, PublicKey}; /// URI scheme @@ -18,65 +18,22 @@ pub const SCHEME: &str = "nostr"; /// URI scheme with colon pub(crate) const SCHEME_WITH_COLON: &str = "nostr:"; -/// Unsupported Bech32 Type -#[derive(Debug, PartialEq, Eq)] -pub enum UnsupportedVariant { - /// Secret Key - SecretKey, -} - -impl fmt::Display for UnsupportedVariant { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::SecretKey => f.write_str("secret key"), - } - } -} - -/// NIP21 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// NIP19 error - NIP19(nip19::Error), - /// Unsupported bech32 type - UnsupportedVariant(UnsupportedVariant), - /// Invalid nostr URI - InvalidURI, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NIP19(e) => e.fmt(f), - Self::UnsupportedVariant(t) => write!(f, "Unsupported variant: {t}"), - Self::InvalidURI => f.write_str("Invalid nostr URI"), - } - } -} - -impl From for Error { - fn from(e: nip19::Error) -> Self { - Self::NIP19(e) - } -} - -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } +fn unsupported_variant(variant: &'static str) -> Error { + Error::new( + ErrorKind::Unsupported, + format!("unsupported variant: {variant}"), + ) } fn split_uri(uri: &str) -> Result<&str, Error> { let mut splitted = uri.split(':'); - let prefix: &str = splitted.next().ok_or(Error::InvalidURI)?; + let prefix: &str = splitted.next().ok_or(invalid_uri())?; if prefix != SCHEME { - return Err(Error::InvalidURI); + return Err(invalid_uri()); } - splitted.next().ok_or(Error::InvalidURI) + splitted.next().ok_or(invalid_uri()) } /// To nostr URI trait @@ -149,11 +106,9 @@ impl TryFrom for Nip21 { fn try_from(value: Nip19) -> Result { match value { - Nip19::Secret(..) => Err(Error::UnsupportedVariant(UnsupportedVariant::SecretKey)), + Nip19::Secret(..) => Err(unsupported_variant("secret key")), #[cfg(feature = "nip49")] - Nip19::EncryptedSecret(..) => { - Err(Error::UnsupportedVariant(UnsupportedVariant::SecretKey)) - } + Nip19::EncryptedSecret(..) => Err(unsupported_variant("secret key")), Nip19::Pubkey(val) => Ok(Self::Pubkey(val)), Nip19::Profile(val) => Ok(Self::Profile(val)), Nip19::EventId(val) => Ok(Self::EventId(val)), @@ -266,8 +221,9 @@ mod tests { fn test_unsupported_from_nostr_uri() { assert_eq!( Nip21::parse("nostr:nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99") - .unwrap_err(), - Error::UnsupportedVariant(UnsupportedVariant::SecretKey) + .unwrap_err() + .kind(), + ErrorKind::Unsupported, ); } } diff --git a/crates/nostr/src/nips/nip22.rs b/crates/nostr/src/nips/nip22.rs index 572297548..212dddff7 100644 --- a/crates/nostr/src/nips/nip22.rs +++ b/crates/nostr/src/nips/nip22.rs @@ -9,91 +9,14 @@ use alloc::borrow::Cow; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::fmt; use core::str::FromStr; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; use crate::nips::nip01::{self, Coordinate}; use crate::nips::nip73::{self, ExternalContentId, Nip73Kind}; -use crate::types::url; -use crate::{Event, EventId, Kind, PublicKey, RelayUrl, Url, event, key}; - -/// NIP-22 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Event error - Event(event::Error), - /// Keys error - Keys(key::Error), - /// NIP-01 error - Nip01(nip01::Error), - /// NIP-73 error - Nip73(nip73::Error), - /// Relay URL error - RelayUrl(url::Error), - /// URL error - Url(url::ParseError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Event(e) => e.fmt(f), - Self::Keys(e) => e.fmt(f), - Self::Nip01(e) => e.fmt(f), - Self::Nip73(e) => e.fmt(f), - Self::RelayUrl(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: nip01::Error) -> Self { - Self::Nip01(e) - } -} - -impl From for Error { - fn from(e: nip73::Error) -> Self { - Self::Nip73(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::RelayUrl(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} +use crate::nips::util::{missing_tag_kind, missing_value, unknown_tag}; +use crate::{Event, EventId, Kind, PublicKey, RelayUrl, Url}; /// Standardized NIP-22 tags /// @@ -163,7 +86,7 @@ impl TagCodec for Nip22Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { "a" => parse_a_tag(iter, false), @@ -176,7 +99,7 @@ impl TagCodec for Nip22Tag { "K" => parse_k_tag(iter, true), "p" => parse_p_tag(iter, false), "P" => parse_p_tag(iter, true), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -645,7 +568,7 @@ where T: Iterator, S: AsRef, { - let kind: S = iter.next().ok_or(TagCodecError::Missing("kind"))?; + let kind: S = iter.next().ok_or_else(|| missing_value("kind"))?; if let Ok(kind_number) = u16::from_str(kind.as_ref()) { Ok(Nip22Tag::Kind { @@ -827,7 +750,7 @@ mod tests { ]; let err = Nip22Tag::parse(&tag).unwrap_err(); - assert!(matches!(err, super::Error::Nip01(nip01::Error::Keys(_)))); + assert_eq!(err.kind(), ErrorKind::Malformed); } #[test] diff --git a/crates/nostr/src/nips/nip23.rs b/crates/nostr/src/nips/nip23.rs index 1130e765a..1b828b5c4 100644 --- a/crates/nostr/src/nips/nip23.rs +++ b/crates/nostr/src/nips/nip23.rs @@ -9,15 +9,14 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use core::num::ParseIntError; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; use crate::nips::util::{ - take_and_parse_from_str, take_and_parse_optional_from_str, take_string, take_timestamp, + missing_tag_kind, take_and_parse_from_str, take_and_parse_optional_from_str, take_string, + take_timestamp, unknown_tag, }; -use crate::types::image; -use crate::types::url::{self, Url}; +use crate::types::url::Url; use crate::{ImageDimensions, Timestamp}; const TITLE: &str = "title"; @@ -26,62 +25,6 @@ const SUMMARY: &str = "summary"; const PUBLISHED_AT: &str = "published_at"; const HASHTAG: &str = "t"; -/// NIP-23 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Image error - Image(image::Error), - /// Parse Int error - ParseInt(ParseIntError), - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Image(e) => e.fmt(f), - Self::ParseInt(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: image::Error) -> Self { - Self::Image(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(url::Error::Url(e)) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-23 tags /// /// @@ -109,7 +52,7 @@ impl TagCodec for Nip23Tag { { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { TITLE => Ok(Self::Title(take_string(&mut iter, "title")?)), @@ -119,13 +62,13 @@ impl TagCodec for Nip23Tag { } SUMMARY => Ok(Self::Summary(take_string(&mut iter, "summary")?)), PUBLISHED_AT => { - let timestamp: Timestamp = take_timestamp::<_, _, Error>(&mut iter)?; + let timestamp: Timestamp = take_timestamp(&mut iter)?; Ok(Self::PublishedAt(timestamp)) } HASHTAG => Ok(Self::Hashtag( take_string(&mut iter, "hashtag")?.to_lowercase(), )), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -157,7 +100,7 @@ where T: Iterator, S: AsRef, { - let url: Url = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "image URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "image URL")?; let dimensions: Option = take_and_parse_optional_from_str(&mut iter)?; Ok((url, dimensions)) diff --git a/crates/nostr/src/nips/nip25.rs b/crates/nostr/src/nips/nip25.rs index cd994bfcb..b48c5d7cf 100644 --- a/crates/nostr/src/nips/nip25.rs +++ b/crates/nostr/src/nips/nip25.rs @@ -2,51 +2,18 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -//! NIP25: Reactions +//! NIP-25: Reactions //! //! use alloc::string::{String, ToString}; -use core::fmt; -use core::num::ParseIntError; use super::nip01::{Coordinate, Nip01Tag}; -use super::util::take_and_parse_from_str; -use crate::event::{Tag, TagCodec, TagCodecError, Tags, impl_tag_codec_conversions}; +use super::util::{missing_tag_kind, take_and_parse_from_str, unknown_tag}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, Tags, impl_tag_codec_conversions}; use crate::{Event, EventId, Kind, PublicKey, RelayUrl}; -/// NIP-25 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Failed to parse integer - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-25 tags /// /// @@ -65,14 +32,14 @@ impl TagCodec for Nip25Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { "k" => { - let kind: Kind = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "kind")?; + let kind: Kind = take_and_parse_from_str(&mut iter, "kind")?; Ok(Self::Kind(kind)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip30.rs b/crates/nostr/src/nips/nip30.rs index fe4a93674..984007ac2 100644 --- a/crates/nostr/src/nips/nip30.rs +++ b/crates/nostr/src/nips/nip30.rs @@ -8,59 +8,18 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::fmt; -use super::nip01::{self, Coordinate}; -use super::util::{take_and_parse_from_str, take_and_parse_optional_coordinate, take_string}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::types::url::{self, Url}; +use super::nip01::Coordinate; +use super::util::{ + missing_tag_kind, take_and_parse_from_str, take_and_parse_optional_coordinate, take_string, + unknown_tag, +}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::types::url::Url; const EMOJI: &str = "emoji"; -/// NIP-30 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// NIP-01 error - Nip01(nip01::Error), - /// URL parse error - Url(url::ParseError), - /// Codec error - Codec(TagCodecError), - /// Invalid shortcode - InvalidShortcode, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Nip01(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidShortcode => f.write_str("Invalid shortcode"), - } - } -} - -impl From for Error { - fn from(e: nip01::Error) -> Self { - Self::Nip01(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-30 tags /// /// @@ -86,7 +45,7 @@ impl TagCodec for Nip30Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { EMOJI => { @@ -97,7 +56,7 @@ impl TagCodec for Nip30Tag { emoji_set, }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -133,10 +92,13 @@ where let shortcode: String = take_string(&mut iter, "shortcode")?; if !is_valid_shortcode(&shortcode) { - return Err(Error::InvalidShortcode); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid shortcode", + )); } - let image_url: Url = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "image URL")?; + let image_url: Url = take_and_parse_from_str(&mut iter, "image URL")?; let emoji_set: Option = take_and_parse_optional_coordinate(&mut iter)?; Ok((shortcode, image_url, emoji_set)) @@ -202,6 +164,9 @@ mod tests { #[test] fn test_nip30_invalid_shortcode() { let tag = vec!["emoji", "soap box", "https://example.com/emoji.png"]; - assert_eq!(Nip30Tag::parse(&tag).unwrap_err(), Error::InvalidShortcode); + assert_eq!( + Nip30Tag::parse(&tag).unwrap_err().kind(), + ErrorKind::Invalid + ); } } diff --git a/crates/nostr/src/nips/nip31.rs b/crates/nostr/src/nips/nip31.rs index aa4187589..48bfbf44c 100644 --- a/crates/nostr/src/nips/nip31.rs +++ b/crates/nostr/src/nips/nip31.rs @@ -8,36 +8,13 @@ use alloc::string::String; use alloc::vec; -use core::fmt; -use super::util::take_string; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use super::util::{missing_tag_kind, take_string, unknown_tag}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; const ALT: &str = "alt"; -/// NIP-31 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-31 tags /// /// @@ -56,14 +33,14 @@ impl TagCodec for Nip31Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { ALT => { let alt: String = take_string(&mut iter, "alt value")?; Ok(Self::Alt(alt)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -79,6 +56,7 @@ impl_tag_codec_conversions!(Nip31Tag); #[cfg(test)] mod tests { use super::*; + use crate::error::ErrorKind; #[test] fn test_nip31_alt_tag() { @@ -93,8 +71,8 @@ mod tests { fn test_invalid_alt_tag_missing_value() { let tag = vec!["alt"]; assert_eq!( - Nip31Tag::parse(&tag).unwrap_err(), - Error::Codec(TagCodecError::Missing("alt value")) + Nip31Tag::parse(&tag).unwrap_err().kind(), + ErrorKind::Missing ); } } diff --git a/crates/nostr/src/nips/nip32.rs b/crates/nostr/src/nips/nip32.rs index 5914991d5..3aca4f5ed 100644 --- a/crates/nostr/src/nips/nip32.rs +++ b/crates/nostr/src/nips/nip32.rs @@ -8,33 +8,10 @@ use alloc::string::String; use alloc::vec; -use core::fmt; -use super::util::take_string; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; - -/// NIP-32 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} +use super::util::{missing_tag_kind, take_string, unknown_tag}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; /// Standardized NIP-32 tags /// @@ -62,7 +39,7 @@ impl TagCodec for Nip32Tag { { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { "L" => Ok(Self::LabelNamespace(take_string( @@ -75,7 +52,7 @@ impl TagCodec for Nip32Tag { Ok(Self::Label { value, namespace }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip34.rs b/crates/nostr/src/nips/nip34.rs index 94e0a693f..0ffdfbad7 100644 --- a/crates/nostr/src/nips/nip34.rs +++ b/crates/nostr/src/nips/nip34.rs @@ -11,18 +11,19 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::fmt; -use core::num::ParseIntError; use core::str::FromStr; -use hashes::hex::HexToArrayError; use hashes::sha1::Hash as Sha1Hash; -use super::nip01::{self, Coordinate}; +use super::nip01::Coordinate; use super::nip22::Nip22Tag; -use super::util::{take_and_parse_from_str, take_string}; -use crate::event::{EventBuilder, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::types::url::{self, Url}; -use crate::{EventId, Kind, PublicKey, RelayUrl, Timestamp, key}; +use super::util::{ + missing_tag_kind, missing_value, take_and_parse_from_str, take_string, unknown_tag, +}; +use crate::error::{Error, ErrorKind}; +use crate::event::{EventBuilder, Tag, TagCodec, impl_tag_codec_conversions}; +use crate::types::url::Url; +use crate::{EventId, Kind, PublicKey, RelayUrl, Timestamp}; const EUC: &str = "euc"; const APPLIED_AS_COMMITS: &str = "applied-as-commits"; @@ -47,87 +48,9 @@ const SUBJECT: &str = "subject"; const WEB: &str = "web"; const RELAYS: &str = "relays"; -/// NIP-34 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Keys error - Keys(key::Error), - /// NIP-01 error - Nip01(nip01::Error), - /// Relay URL error - RelayUrl(url::Error), - /// URL error - Url(url::ParseError), - /// Hex to array error - Hex(HexToArrayError), - /// Parse integer error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), - /// Invalid `HEAD` tag - InvalidHeadTag, - /// Unexpected event kind - UnexpectedKind, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Keys(e) => e.fmt(f), - Self::Nip01(e) => e.fmt(f), - Self::RelayUrl(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Hex(e) => e.fmt(f), - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidHeadTag => f.write_str("Invalid HEAD tag"), - Self::UnexpectedKind => f.write_str("Unexpected event kind"), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: nip01::Error) -> Self { - Self::Nip01(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::RelayUrl(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: HexToArrayError) -> Self { - Self::Hex(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } +#[inline] +fn unexpected_kind() -> Error { + Error::with_static_message(ErrorKind::Invalid, "unexpected kind") } /// Standardized NIP-34 tags @@ -209,7 +132,7 @@ impl TagCodec for Nip34Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; let kind: &str = kind.as_ref(); match kind { @@ -217,8 +140,7 @@ impl TagCodec for Nip34Tag { BRANCH_NAME => Ok(Self::BranchName(take_string(&mut iter, "branch name")?)), CLONE => Ok(Self::Clone(parse_url_list(iter)?)), COMMIT => { - let commit: Sha1Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "commit ID")?; + let commit: Sha1Hash = take_and_parse_from_str(&mut iter, "commit ID")?; Ok(Self::Commit(commit)) } COMMIT_PGP_SIG => Ok(Self::CommitPgpSig(take_string( @@ -235,9 +157,8 @@ impl TagCodec for Nip34Tag { .map(|value| value.as_ref().to_string()) .unwrap_or_default(); let timestamp: Timestamp = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "committer timestamp")?; - let offset_minutes: i32 = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "committer offset")?; + take_and_parse_from_str(&mut iter, "committer timestamp")?; + let offset_minutes: i32 = take_and_parse_from_str(&mut iter, "committer offset")?; Ok(Self::Committer { name, @@ -247,49 +168,45 @@ impl TagCodec for Nip34Tag { }) } CURRENT_COMMIT => { - let commit: Sha1Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "current commit ID")?; + let commit: Sha1Hash = take_and_parse_from_str(&mut iter, "current commit ID")?; Ok(Self::CurrentCommit(commit)) } DESCRIPTION => Ok(Self::Description(take_string(&mut iter, "description")?)), GRASP => { - let relay: RelayUrl = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "grasp relay URL")?; + let relay: RelayUrl = take_and_parse_from_str(&mut iter, "grasp relay URL")?; Ok(Self::Grasp(relay)) } HEAD => { - let head: S = iter.next().ok_or(TagCodecError::Missing("head"))?; - let branch: &str = head - .as_ref() - .strip_prefix("ref: refs/heads/") - .ok_or(Error::InvalidHeadTag)?; + let head: S = iter.next().ok_or(missing_value("head"))?; + let branch: &str = + head.as_ref() + .strip_prefix("ref: refs/heads/") + .ok_or_else(|| { + Error::with_static_message(ErrorKind::Invalid, "invalid head tag") + })?; Ok(Self::Head(branch.to_string())) } MAINTAINERS => Ok(Self::Maintainers(parse_public_keys(iter)?)), MERGE_BASE => { - let commit: Sha1Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "merge base")?; + let commit: Sha1Hash = take_and_parse_from_str(&mut iter, "merge base")?; Ok(Self::MergeBase(commit)) } MERGE_COMMIT => { - let commit: Sha1Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "merge commit ID")?; + let commit: Sha1Hash = take_and_parse_from_str(&mut iter, "merge commit ID")?; Ok(Self::MergeCommit(commit)) } NAME => Ok(Self::Name(take_string(&mut iter, "name")?)), PARENT_COMMIT => { - let commit: Sha1Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "parent commit ID")?; + let commit: Sha1Hash = take_and_parse_from_str(&mut iter, "parent commit ID")?; Ok(Self::ParentCommit(commit)) } REFERENCE => { - let commit: Sha1Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "reference commit ID")?; + let commit: Sha1Hash = take_and_parse_from_str(&mut iter, "reference commit ID")?; match iter.next() { Some(marker) if marker.as_ref() == EUC => { Ok(Self::EarliestUniqueCommitId(commit)) } - Some(_) => Err(TagCodecError::Unknown.into()), + Some(_) => Err(unknown_tag()), None => Ok(Self::Reference(commit)), } } @@ -297,20 +214,22 @@ impl TagCodec for Nip34Tag { WEB => Ok(Self::Web(parse_url_list(iter)?)), RELAYS => Ok(Self::Relays(parse_relay_urls(iter)?)), _ if kind.starts_with(REFS_HEADS) => { - let commit: S = iter.next().ok_or(TagCodecError::Missing("commit id"))?; + let commit: S = iter.next().ok_or(missing_value("commit id"))?; Ok(Self::RefHead { branch: kind.trim_start_matches(REFS_HEADS).to_string(), - commit: Sha1Hash::from_str(commit.as_ref())?, + commit: Sha1Hash::from_str(commit.as_ref()) + .map_err(Error::malformed_display)?, }) } _ if kind.starts_with(REFS_TAGS) => { - let commit: S = iter.next().ok_or(TagCodecError::Missing("commit id"))?; + let commit: S = iter.next().ok_or(missing_value("commit id"))?; Ok(Self::RefTag { name: kind.trim_start_matches(REFS_TAGS).to_string(), - commit: Sha1Hash::from_str(commit.as_ref())?, + commit: Sha1Hash::from_str(commit.as_ref()) + .map_err(Error::malformed_display)?, }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -409,10 +328,11 @@ where let values: Vec = iter .into_iter() .map(|value| Sha1Hash::from_str(value.as_ref())) - .collect::>()?; + .collect::>() + .map_err(Error::malformed_display)?; if values.is_empty() { - return Err(TagCodecError::Missing("commits").into()); + return Err(missing_value("commits")); } Ok(values) @@ -429,7 +349,7 @@ where .collect::>()?; if values.is_empty() { - return Err(TagCodecError::Missing("public keys").into()); + return Err(missing_value("public keys")); } Ok(values) @@ -446,7 +366,7 @@ where .collect::>()?; if values.is_empty() { - return Err(TagCodecError::Missing("relay URLs").into()); + return Err(missing_value("relay URLs")); } Ok(values) @@ -460,10 +380,11 @@ where let values: Vec = iter .into_iter() .map(|value| Url::parse(value.as_ref())) - .collect::>()?; + .collect::>() + .map_err(Error::malformed)?; if values.is_empty() { - return Err(TagCodecError::Missing("URLs").into()); + return Err(missing_value("URLs")); } Ok(values) @@ -563,7 +484,7 @@ impl GitIssue { pub(crate) fn to_event_builder(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { - return Err(Error::UnexpectedKind); + return Err(unexpected_kind()); } // Verify coordinate @@ -670,7 +591,7 @@ impl GitPatch { pub(crate) fn to_event_builder(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { - return Err(Error::UnexpectedKind); + return Err(unexpected_kind()); } // Verify coordinate @@ -757,7 +678,7 @@ impl GitPullRequest { pub(crate) fn to_event_builder(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { - return Err(Error::UnexpectedKind); + return Err(unexpected_kind()); } // Verify coordinate @@ -832,7 +753,7 @@ impl GitPullRequestUpdate { pub(crate) fn to_event_builder(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { - return Err(Error::UnexpectedKind); + return Err(unexpected_kind()); } // Verify coordinate diff --git a/crates/nostr/src/nips/nip39.rs b/crates/nostr/src/nips/nip39.rs index 790367503..67e9842a6 100644 --- a/crates/nostr/src/nips/nip39.rs +++ b/crates/nostr/src/nips/nip39.rs @@ -11,35 +11,14 @@ use alloc::vec; use core::fmt; use core::str::FromStr; -use super::util::take_string; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use super::util::{missing_tag_kind, take_string, unknown_tag}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; const IDENTITY: &str = "i"; -/// NIP-39 error -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Codec error - Codec(TagCodecError), - /// Invalid identity - InvalidIdentity, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Codec(e) => e.fmt(f), - Self::InvalidIdentity => f.write_str("Invalid identity tag"), - } - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } +fn invalid_identity() -> Error { + Error::with_static_message(ErrorKind::Invalid, "invalid identity") } /// Supported external identity providers @@ -84,7 +63,7 @@ impl FromStr for ExternalIdentity { "twitter" => Ok(Self::Twitter), "mastodon" => Ok(Self::Mastodon), "telegram" => Ok(Self::Telegram), - _ => Err(Error::InvalidIdentity), + _ => Err(invalid_identity()), } } } @@ -110,7 +89,7 @@ impl Identity { S2: Into, { let i: &str = platform_iden.as_ref(); - let (platform, ident) = i.rsplit_once(':').ok_or(Error::InvalidIdentity)?; + let (platform, ident) = i.rsplit_once(':').ok_or_else(invalid_identity)?; Ok(Self { platform: ExternalIdentity::from_str(platform)?, @@ -143,7 +122,7 @@ impl TagCodec for Nip39Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { IDENTITY => { @@ -152,7 +131,7 @@ impl TagCodec for Nip39Tag { Ok(Self::Identity(Identity::new(platform_ident, proof)?)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -215,6 +194,6 @@ mod tests { #[test] fn test_identity_tag_missing_proof() { let err = Nip39Tag::parse(["i", "github:semisol"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("proof"))); + assert_eq!(err.kind(), ErrorKind::Missing); } } diff --git a/crates/nostr/src/nips/nip40.rs b/crates/nostr/src/nips/nip40.rs index 1d5fd41cd..da2b2d5a4 100644 --- a/crates/nostr/src/nips/nip40.rs +++ b/crates/nostr/src/nips/nip40.rs @@ -9,47 +9,14 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use core::num::ParseIntError; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::nips::util::take_timestamp; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::nips::util::{missing_tag_kind, take_timestamp, unknown_tag}; use crate::types::time::Timestamp; const EXPIRATION: &str = "expiration"; -/// NIP-40 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Parse Int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-40 tags /// /// @@ -71,15 +38,15 @@ impl TagCodec for Nip40Tag { let mut iter = tag.into_iter(); // Extract first value - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; // Match kind match kind.as_ref() { EXPIRATION => { - let timestamp: Timestamp = take_timestamp::<_, _, Error>(&mut iter)?; + let timestamp: Timestamp = take_timestamp(&mut iter)?; Ok(Self::Expiration(timestamp)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -95,19 +62,20 @@ impl_tag_codec_conversions!(Nip40Tag); #[cfg(test)] mod tests { use super::*; + use crate::error::ErrorKind; #[test] fn test_parse_empty_tag() { let tag: Vec = Vec::new(); let err = Nip40Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::missing_tag_kind())); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] fn test_non_existing_tag() { let tag = vec!["hello"]; let err = Nip40Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Unknown)); + assert_eq!(err.kind(), ErrorKind::Malformed); } #[test] @@ -124,11 +92,11 @@ mod tests { // Invalid timestamp let tag = vec!["expiration", "hello"]; let err = Nip40Tag::parse(&tag).unwrap_err(); - assert!(matches!(err, Error::ParseInt(_))); + assert_eq!(err.kind(), ErrorKind::Malformed); // Missing timestamp let tag = vec!["expiration"]; let err = Nip40Tag::parse(&tag).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("timestamp"))); + assert_eq!(err.kind(), ErrorKind::Missing); } } diff --git a/crates/nostr/src/nips/nip42.rs b/crates/nostr/src/nips/nip42.rs index 0829d98d2..0e096ea20 100644 --- a/crates/nostr/src/nips/nip42.rs +++ b/crates/nostr/src/nips/nip42.rs @@ -8,48 +8,15 @@ use alloc::string::{String, ToString}; use alloc::vec; -use core::fmt; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::nips::util::{take_relay_url, take_string}; -use crate::types::url; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::nips::util::{missing_tag_kind, take_relay_url, take_string, unknown_tag}; use crate::{Event, Kind, RelayUrl}; const CHALLENGE: &str = "challenge"; const RELAY: &str = "relay"; -/// NIP-42 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-42 tags /// /// @@ -70,15 +37,15 @@ impl TagCodec for Nip42Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { CHALLENGE => Ok(Self::Challenge(take_string(&mut iter, "challenge")?)), RELAY => { - let relay_url: RelayUrl = take_relay_url::<_, _, Error>(&mut iter)?; + let relay_url: RelayUrl = take_relay_url(&mut iter)?; Ok(Self::Relay(relay_url)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip44/impl.rs b/crates/nostr/src/nips/nip44/impl.rs index 991a53611..97dc10218 100644 --- a/crates/nostr/src/nips/nip44/impl.rs +++ b/crates/nostr/src/nips/nip44/impl.rs @@ -4,7 +4,6 @@ use alloc::string::String; use alloc::vec::Vec; -use core::fmt; use core::fmt::Debug; use base64::engine::{Engine, general_purpose}; @@ -16,62 +15,10 @@ use rand::rand_core::UnwrapErr; use rand::rngs::SysRng; use super::v2::{self, ConversationKey}; +use crate::error::{Error, ErrorKind}; +use crate::key::{PublicKey, SecretKey}; #[cfg(feature = "rand")] use crate::util; -use crate::{PublicKey, SecretKey, key}; - -/// Error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Key error - Key(key::Error), - /// NIP44 V2 error - V2(v2::ErrorV2), - /// Error while decoding from base64 - Base64Decode(base64::DecodeError), - /// Error while encoding to UTF-8 - Utf8Encode, - /// Unknown version - UnknownVersion(u8), - /// Version not found in payload - VersionNotFound, - /// Not found in payload - NotFound(String), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Key(e) => write!(f, "{e}"), - Self::V2(e) => write!(f, "{e}"), - Self::Base64Decode(e) => write!(f, "Error while decoding from base64: {e}"), - Self::Utf8Encode => f.write_str("Error while encoding to UTF-8"), - Self::UnknownVersion(v) => write!(f, "unknown version: {v}"), - Self::VersionNotFound => f.write_str("Version not found in payload"), - Self::NotFound(value) => write!(f, "{value} not found in payload"), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) - } -} - -impl From for Error { - fn from(e: v2::ErrorV2) -> Self { - Self::V2(e) - } -} - -impl From for Error { - fn from(e: base64::DecodeError) -> Self { - Self::Base64Decode(e) - } -} /// Payload version #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -96,7 +43,10 @@ impl TryFrom for Version { fn try_from(version: u8) -> Result { match version { 0x02 => Ok(Self::V2), - v => Err(Error::UnknownVersion(v)), + _ => Err(Error::new( + ErrorKind::Unsupported, + format!("unknown version: {version}"), + )), } } } @@ -198,7 +148,7 @@ where T: AsRef<[u8]>, { let bytes: Vec = decrypt_to_bytes(secret_key, public_key, payload)?; - String::from_utf8(bytes).map_err(|_| Error::Utf8Encode) + String::from_utf8(bytes).map_err(Error::malformed) } /// Decrypt **without** converting bytes to UTF-8 string @@ -211,10 +161,14 @@ where T: AsRef<[u8]>, { // Decode base64 payload - let payload: Vec = general_purpose::STANDARD.decode(payload)?; + let payload: Vec = general_purpose::STANDARD + .decode(payload) + .map_err(Error::malformed_display)?; // Get version byte - let version: u8 = *payload.first().ok_or(Error::VersionNotFound)?; + let version: u8 = *payload + .first() + .ok_or_else(|| Error::with_static_message(ErrorKind::Missing, "version not found"))?; match Version::try_from(version)? { Version::V2 => { diff --git a/crates/nostr/src/nips/nip44/traits.rs b/crates/nostr/src/nips/nip44/traits.rs index 01385a6f3..cf30b0ae1 100644 --- a/crates/nostr/src/nips/nip44/traits.rs +++ b/crates/nostr/src/nips/nip44/traits.rs @@ -13,7 +13,7 @@ use crate::util::BoxedFuture; /// Synchronous NIP-44 pub trait Nip44: Any + Debug + Send + Sync { /// NIP-44 error - type Error: core::error::Error; + type Error: core::error::Error + Send + Sync; /// Encrypts synchronously using NIP-44. /// @@ -27,7 +27,7 @@ pub trait Nip44: Any + Debug + Send + Sync { /// Asynchronous NIP-44 pub trait AsyncNip44: Any + Debug + Send + Sync { /// NIP-44 error - type Error: core::error::Error; + type Error: core::error::Error + Send + Sync; /// Encrypts asynchronously using NIP-44. /// diff --git a/crates/nostr/src/nips/nip44/v2.rs b/crates/nostr/src/nips/nip44/v2.rs index 9b1af4ba6..cf0e76ffd 100644 --- a/crates/nostr/src/nips/nip44/v2.rs +++ b/crates/nostr/src/nips/nip44/v2.rs @@ -6,7 +6,6 @@ //! //! -use alloc::string::{FromUtf8Error, String}; use alloc::vec; use alloc::vec::Vec; use core::fmt; @@ -16,9 +15,9 @@ use chacha20::ChaCha20; use chacha20::cipher::{KeyIvInit, StreamCipher}; use hashes::hmac::{Hmac, HmacEngine}; use hashes::sha256::Hash as Sha256Hash; -use hashes::{FromSliceError, Hash, HashEngine}; +use hashes::{Hash, HashEngine}; -use super::Error; +use crate::error::{Error, ErrorKind}; use crate::util::{self, hkdf}; use crate::{PublicKey, SecretKey}; @@ -31,56 +30,40 @@ const MESSAGES_KEYS_NONCE_RANGE: Range = const MESSAGES_KEYS_AUTH_RANGE: Range = MESSAGES_KEYS_ENCRYPTION_SIZE + MESSAGES_KEYS_NONCE_SIZE..MESSAGE_KEYS_SIZE; -/// Error -#[derive(Debug, PartialEq, Eq)] -pub enum ErrorV2 { - /// From slice error - FromSlice(FromSliceError), - /// Error while encoding to UTF-8 - Utf8Encode(FromUtf8Error), - /// HKDF Length - HkdfLength(usize), - /// Try from slice +enum ErrorV2 { + HkdfLength, + NotFound(&'static str), TryFromSlice, - /// Message is empty MessageEmpty, - /// Message is too long MessageTooLong, - /// Invalid HMAC InvalidHmac, - /// Invalid padding InvalidPadding, } -impl core::error::Error for ErrorV2 {} - -impl fmt::Display for ErrorV2 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::FromSlice(e) => e.fmt(f), - Self::Utf8Encode(e) => write!(f, "error while encoding to UTF-8: {e}"), - Self::HkdfLength(size) => write!(f, "invalid Length for HKDF: {size}"), - Self::TryFromSlice => f.write_str("could not convert slice to array"), - Self::MessageEmpty => f.write_str("message empty"), - Self::MessageTooLong => f.write_str("message too long"), - Self::InvalidHmac => f.write_str("invalid HMAC"), - Self::InvalidPadding => f.write_str("invalid padding"), +impl From for Error { + fn from(e: ErrorV2) -> Self { + match e { + ErrorV2::HkdfLength => { + Error::with_static_message(ErrorKind::Invalid, "invalid HKDF length") + } + ErrorV2::NotFound(value) => Error::with_static_message(ErrorKind::Missing, value), + ErrorV2::TryFromSlice => { + Error::with_static_message(ErrorKind::Malformed, "invalid slice length") + } + ErrorV2::MessageEmpty => { + Error::with_static_message(ErrorKind::Invalid, "message empty") + } + ErrorV2::MessageTooLong => { + Error::with_static_message(ErrorKind::Invalid, "message too long") + } + ErrorV2::InvalidHmac => Error::with_static_message(ErrorKind::Crypto, "invalid HMAC"), + ErrorV2::InvalidPadding => { + Error::with_static_message(ErrorKind::Invalid, "invalid padding") + } } } } -impl From for ErrorV2 { - fn from(e: FromSliceError) -> Self { - Self::FromSlice(e) - } -} - -impl From for ErrorV2 { - fn from(e: FromUtf8Error) -> Self { - Self::Utf8Encode(e) - } -} - struct MessageKeys([u8; MESSAGE_KEYS_SIZE]); impl MessageKeys { @@ -141,7 +124,7 @@ impl ConversationKey { #[inline] pub fn from_slice(slice: &[u8]) -> Result { Ok(Self( - Hmac::from_slice(slice).map_err(|e| Error::from(ErrorV2::from(e)))?, + Hmac::from_slice(slice).map_err(Error::malformed_display)?, )) } @@ -196,15 +179,11 @@ pub fn decrypt_to_bytes( ) -> Result, Error> { // Get data from payload let len: usize = payload.len(); - let nonce: &[u8] = payload - .get(1..33) - .ok_or_else(|| Error::NotFound(String::from("nonce")))?; + let nonce: &[u8] = payload.get(1..33).ok_or(ErrorV2::NotFound("nonce"))?; let buffer: &[u8] = payload .get(33..len - 32) - .ok_or_else(|| Error::NotFound(String::from("buffer")))?; - let mac: &[u8] = payload - .get(len - 32..) - .ok_or_else(|| Error::NotFound(String::from("hmac")))?; + .ok_or(ErrorV2::NotFound("buffer"))?; + let mac: &[u8] = payload.get(len - 32..).ok_or(ErrorV2::NotFound("hmac"))?; // Compose Message Keys let keys: MessageKeys = get_message_keys(conversation_key, nonce)?; @@ -254,7 +233,7 @@ fn get_message_keys( nonce: &[u8], ) -> Result { let expanded_key: Vec = hkdf::expand(conversation_key.as_bytes(), nonce, MESSAGE_KEYS_SIZE); - MessageKeys::from_slice(&expanded_key).map_err(|_| ErrorV2::HkdfLength(expanded_key.len())) + MessageKeys::from_slice(&expanded_key).map_err(|_| ErrorV2::HkdfLength) } fn pad(unpadded: &[u8]) -> Result, ErrorV2> { @@ -554,13 +533,13 @@ mod tests { fn test_invalid_decrypt() { let json: serde_json::Value = serde_json::from_str(JSON_VECTORS).unwrap(); - let known_errors = [ - Error::V2(ErrorV2::InvalidHmac), - Error::V2(ErrorV2::InvalidHmac), - Error::V2(ErrorV2::InvalidPadding), - Error::V2(ErrorV2::MessageEmpty), - Error::V2(ErrorV2::InvalidPadding), - Error::V2(ErrorV2::InvalidPadding), + let known_error_kinds = [ + ErrorKind::Crypto, + ErrorKind::Crypto, + ErrorKind::Invalid, + ErrorKind::Invalid, + ErrorKind::Invalid, + ErrorKind::Invalid, ]; for (i, vectorobj) in json @@ -595,7 +574,8 @@ mod tests { let err = result.unwrap_err(); assert_eq!( - err, known_errors[i], + err.kind(), + known_error_kinds[i], "Unexpected error in invalid decrypt #{}", i ); diff --git a/crates/nostr/src/nips/nip46.rs b/crates/nostr/src/nips/nip46.rs index 98d697d12..747f20b4a 100644 --- a/crates/nostr/src/nips/nip46.rs +++ b/crates/nostr/src/nips/nip46.rs @@ -22,118 +22,34 @@ use rand::rngs::SysRng; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use super::nip44::{AsyncNip44, Nip44}; +use super::util::{invalid_uri, unexpected_result, unsupported_method}; +use crate::error::{Error, ErrorKind}; use crate::event::{AsyncSignEvent, FinalizeEvent, FinalizeEventAsync, SignEvent, UnsignedEvent}; use crate::key::{AsyncGetPublicKey, GetPublicKey}; -use crate::signer::SignerError; -use crate::types::url::{self, ParseError, RelayUrl, Url}; +use crate::types::url::{RelayUrl, Url}; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::util; -use crate::util::{BoxedFuture, impl_json_methods}; -use crate::{Event, EventBuilder, Kind, PublicKey, Tag, event, key}; +use crate::util::{BoxedFuture, impl_json_methods, parse_json}; +use crate::{Event, EventBuilder, Kind, PublicKey, Tag}; /// NIP46 URI Scheme pub const NOSTR_CONNECT_URI_SCHEME: &str = "nostrconnect"; /// NIP46 bunker URI Scheme pub const NOSTR_CONNECT_BUNKER_URI_SCHEME: &str = "bunker"; -/// NIP46 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Signer error - Signer(SignerError), - /// Key error - Key(key::Error), - /// JSON error - Json(String), - /// Relay Url parse error - RelayUrl(url::Error), - /// Url parse error - Url(ParseError), - /// Event error - Event(event::Error), - /// Invalid request - InvalidRequest, - /// Too many/few params - InvalidParamsLength, - /// Unsupported method - UnsupportedMethod(String), - /// Invalid URI - InvalidURI, - /// Not a request - NotRequest, - /// Unexpected result - UnexpectedResponse { - /// Request method - method: NostrConnectMethod, - /// Expected response - expected: String, - /// Received response - received: String, - }, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Signer(e) => e.fmt(f), - Self::Key(e) => e.fmt(f), - Self::Json(e) => e.fmt(f), - Self::RelayUrl(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::InvalidRequest => f.write_str("Invalid request"), - Self::InvalidParamsLength => f.write_str("Invalid params len"), - Self::UnsupportedMethod(name) => write!(f, "Unsupported method: {name}"), - Self::InvalidURI => f.write_str("Invalid uri"), - Self::NotRequest => f.write_str("Not a request"), - Self::UnexpectedResponse { - method, - received, - expected, - } => write!( - f, - "Unexpected response: method={method}, expected={expected}, received={received}" - ), - } - } -} - -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e.to_string()) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::RelayUrl(e) - } +#[inline] +fn invalid_params_length() -> Error { + Error::with_static_message(ErrorKind::Invalid, "invalid params length") } -impl From for Error { - fn from(e: ParseError) -> Self { - Self::Url(e) - } +#[inline] +fn invalid_request() -> Error { + Error::with_static_message(ErrorKind::Invalid, "invalid request") } -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } +#[inline] +fn not_request() -> Error { + Error::with_static_message(ErrorKind::Invalid, "not a request") } /// NIP46 method @@ -185,7 +101,7 @@ impl FromStr for NostrConnectMethod { "nip44_encrypt" => Ok(Self::Nip44Encrypt), "nip44_decrypt" => Ok(Self::Nip44Decrypt), "ping" => Ok(Self::Ping), - other => Err(Error::UnsupportedMethod(other.to_string())), + other => Err(unsupported_method(other)), } } } @@ -260,8 +176,7 @@ impl NostrConnectRequest { pub fn from_message(method: NostrConnectMethod, params: Vec) -> Result { match method { NostrConnectMethod::Connect => { - let remote_signer_public_key: &String = - params.first().ok_or(Error::InvalidRequest)?; + let remote_signer_public_key: &String = params.first().ok_or(invalid_request())?; let remote_signer_public_key: PublicKey = PublicKey::from_hex(remote_signer_public_key)?; let secret: Option = params.get(1).cloned(); @@ -272,13 +187,13 @@ impl NostrConnectRequest { } NostrConnectMethod::GetPublicKey => Ok(Self::GetPublicKey), NostrConnectMethod::SignEvent => { - let unsigned: &String = params.first().ok_or(Error::InvalidRequest)?; + let unsigned: &String = params.first().ok_or(invalid_request())?; let unsigned_event: UnsignedEvent = UnsignedEvent::from_json(unsigned)?; Ok(Self::SignEvent(unsigned_event)) } NostrConnectMethod::Nip04Encrypt => { if params.len() != 2 { - return Err(Error::InvalidParamsLength); + return Err(invalid_params_length()); } Ok(Self::Nip04Encrypt { @@ -288,7 +203,7 @@ impl NostrConnectRequest { } NostrConnectMethod::Nip04Decrypt => { if params.len() != 2 { - return Err(Error::InvalidParamsLength); + return Err(invalid_params_length()); } Ok(Self::Nip04Decrypt { @@ -298,7 +213,7 @@ impl NostrConnectRequest { } NostrConnectMethod::Nip44Encrypt => { if params.len() != 2 { - return Err(Error::InvalidParamsLength); + return Err(invalid_params_length()); } Ok(Self::Nip44Encrypt { @@ -308,7 +223,7 @@ impl NostrConnectRequest { } NostrConnectMethod::Nip44Decrypt => { if params.len() != 2 { - return Err(Error::InvalidParamsLength); + return Err(invalid_params_length()); } Ok(Self::Nip44Decrypt { @@ -538,11 +453,7 @@ impl ResponseResult { if response == "pong" { Ok(Self::Pong) } else { - Err(Error::UnexpectedResponse { - method, - expected: String::from("pong"), - received: response, - }) + Err(unexpected_result()) } } } @@ -563,11 +474,7 @@ impl ResponseResult { if let Self::GetPublicKey(val) = self { Ok(val) } else { - Err(Error::UnexpectedResponse { - method: NostrConnectMethod::GetPublicKey, - expected: String::from("user public key"), - received: self.to_string(), - }) + Err(unexpected_result()) } } @@ -576,11 +483,7 @@ impl ResponseResult { if let Self::SignEvent(val) = self { Ok(*val) } else { - Err(Error::UnexpectedResponse { - method: NostrConnectMethod::SignEvent, - expected: String::from("signed event"), - received: self.to_string(), - }) + Err(unexpected_result()) } } @@ -589,11 +492,7 @@ impl ResponseResult { if let Self::Nip04Encrypt { ciphertext } = self { Ok(ciphertext) } else { - Err(Error::UnexpectedResponse { - method: NostrConnectMethod::Nip04Encrypt, - expected: String::from("NIP-04 encrypted text"), - received: self.to_string(), - }) + Err(unexpected_result()) } } @@ -602,11 +501,7 @@ impl ResponseResult { if let Self::Nip04Decrypt { plaintext } = self { Ok(plaintext) } else { - Err(Error::UnexpectedResponse { - method: NostrConnectMethod::Nip04Decrypt, - expected: String::from("NIP-04 decrypted text"), - received: self.to_string(), - }) + Err(unexpected_result()) } } @@ -615,11 +510,7 @@ impl ResponseResult { if let Self::Nip44Encrypt { ciphertext } = self { Ok(ciphertext) } else { - Err(Error::UnexpectedResponse { - method: NostrConnectMethod::Nip44Encrypt, - expected: String::from("NIP-44 encrypted text"), - received: self.to_string(), - }) + Err(unexpected_result()) } } @@ -628,11 +519,7 @@ impl ResponseResult { if let Self::Nip44Decrypt { plaintext } = self { Ok(plaintext) } else { - Err(Error::UnexpectedResponse { - method: NostrConnectMethod::Nip44Decrypt, - expected: String::from("NIP-44 decrypted text"), - received: self.to_string(), - }) + Err(unexpected_result()) } } @@ -641,11 +528,7 @@ impl ResponseResult { if let Self::Pong = self { Ok(()) } else { - Err(Error::UnexpectedResponse { - method: NostrConnectMethod::Ping, - expected: String::from("pong"), - received: self.to_string(), - }) + Err(unexpected_result()) } } } @@ -745,7 +628,7 @@ impl NostrConnectMessage { Self::Request { method, params, .. } => { NostrConnectRequest::from_message(method, params) } - _ => Err(Error::NotRequest), + _ => Err(not_request()), } } @@ -756,12 +639,12 @@ impl NostrConnectMessage { Self::Response { result, error, .. } => { NostrConnectResponse::parse(method, result, error) } - _ => Err(Error::NotRequest), + _ => Err(not_request()), } } } -impl_json_methods!(NostrConnectMessage, Error); +impl_json_methods!(NostrConnectMessage); /// Nostr Connect Metadata #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -822,7 +705,7 @@ impl NostrConnectMetadata { } } -impl_json_methods!(NostrConnectMetadata, Error); +impl_json_methods!(NostrConnectMetadata); #[allow(missing_docs)] #[deprecated(since = "0.45.0", note = "use `NostrConnectUri` instead")] @@ -901,7 +784,7 @@ impl NostrConnectUri { S: AsRef, { let uri: &str = uri.as_ref(); - let uri: Url = Url::parse(uri)?; + let uri: Url = Url::parse(uri).map_err(|_| invalid_uri())?; match uri.scheme() { NOSTR_CONNECT_BUNKER_URI_SCHEME => { @@ -931,7 +814,7 @@ impl NostrConnectUri { }); } - Err(Error::InvalidURI) + Err(invalid_uri()) } NOSTR_CONNECT_URI_SCHEME => { if let Some(pubkey) = uri.domain() { @@ -949,7 +832,7 @@ impl NostrConnectUri { } Cow::Borrowed("metadata") => { let value = value.to_string(); - metadata = Some(serde_json::from_str(&value)?); + metadata = Some(parse_json(&value)?); } Cow::Borrowed("secret") => { secret = Some(value.to_string()); @@ -968,9 +851,9 @@ impl NostrConnectUri { } } - Err(Error::InvalidURI) + Err(invalid_uri()) } - _ => Err(Error::InvalidURI), + _ => Err(invalid_uri()), } } @@ -1045,8 +928,8 @@ where let json: String = self.message.as_json(); let content: String = signer .nip44_encrypt(&self.receiver, &json) - .map_err(SignerError::backend)?; - Ok(make_event_builder(content, self.receiver).finalize(signer)?) + .map_err(Error::crypto)?; + make_event_builder(content, self.receiver).finalize(signer) } } @@ -1066,10 +949,10 @@ where let content: String = signer .nip44_encrypt_async(&self.receiver, &json) .await - .map_err(SignerError::backend)?; - Ok(make_event_builder(content, self.receiver) + .map_err(Error::crypto)?; + make_event_builder(content, self.receiver) .finalize_async(signer) - .await?) + .await }) } } @@ -1264,14 +1147,7 @@ mod test { assert_eq!(res, ResponseResult::Ack); let res = ResponseResult::parse(NostrConnectMethod::Ping, "ack"); - assert_eq!( - res.unwrap_err(), - Error::UnexpectedResponse { - method: NostrConnectMethod::Ping, - expected: String::from("pong"), - received: String::from("ack"), - } - ); + assert_eq!(res.unwrap_err().kind(), ErrorKind::Invalid); let res: ResponseResult = ResponseResult::parse( NostrConnectMethod::GetPublicKey, diff --git a/crates/nostr/src/nips/nip47.rs b/crates/nostr/src/nips/nip47.rs index c83ad01e4..f29890663 100644 --- a/crates/nostr/src/nips/nip47.rs +++ b/crates/nostr/src/nips/nip47.rs @@ -20,82 +20,19 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use super::nip04; +use super::util::{invalid_uri, unexpected_result, unsupported_method}; +use crate::error::{Error, ErrorKind}; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::event::FinalizeEvent; -#[cfg(all(feature = "std", feature = "os-rng"))] -use crate::signer::SignerError; use crate::types::url::form_urlencoded::byte_serialize; use crate::types::url::{RelayUrl, Url}; -use crate::util::impl_json_methods; +use crate::util::{impl_json_methods, parse_json_from_value}; use crate::{Event, PublicKey, SecretKey, Timestamp}; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::{EventBuilder, Keys, Kind, Tag}; -/// NIP47 error -#[derive(Debug)] -pub enum Error { - /// JSON error - Json(serde_json::Error), - /// NIP04 error - NIP04(nip04::Error), - /// Signer error - #[cfg(all(feature = "std", feature = "os-rng"))] - Signer(SignerError), - /// Error code - ErrorCode(NIP47Error), - /// Can't deserialize NIP-47 response - CantDeserializeResponse { - /// NIP-47 response - response: String, - /// Deserialization error - error: String, - }, - /// Unsupported method - UnsupportedMethod(Method), - /// Unexpected result - UnexpectedResult, - /// Invalid URI - InvalidURI, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Json(e) => e.fmt(f), - Self::NIP04(e) => e.fmt(f), - #[cfg(all(feature = "std", feature = "os-rng"))] - Self::Signer(e) => e.fmt(f), - Self::ErrorCode(e) => e.fmt(f), - Self::CantDeserializeResponse { response, error } => write!( - f, - "Can't deserialize response: response={response}, error={error}" - ), - Self::UnsupportedMethod(name) => write!(f, "Unsupported method: {name}"), - Self::UnexpectedResult => f.write_str("Unexpected result"), - Self::InvalidURI => f.write_str("Invalid URI"), - } - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e) - } -} - -impl From for Error { - fn from(e: nip04::Error) -> Self { - Self::NIP04(e) - } -} - -#[cfg(all(feature = "std", feature = "os-rng"))] -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } +fn error_code(code: NIP47Error) -> Error { + Error::new(ErrorKind::Other, code.to_string()) } /// NIP47 Response Error codes @@ -574,45 +511,45 @@ impl Request { /// Deserialize from [`Value`] pub fn from_value(value: Value) -> Result { - let template: RequestTemplate = serde_json::from_value(value)?; + let template: RequestTemplate = parse_json_from_value(value)?; let params = match template.method { Method::PayInvoice => { - let params: PayInvoiceRequest = serde_json::from_value(template.params)?; + let params: PayInvoiceRequest = parse_json_from_value(template.params)?; RequestParams::PayInvoice(params) } Method::PayKeysend => { - let params: PayKeysendRequest = serde_json::from_value(template.params)?; + let params: PayKeysendRequest = parse_json_from_value(template.params)?; RequestParams::PayKeysend(params) } Method::MakeInvoice => { - let params: MakeInvoiceRequest = serde_json::from_value(template.params)?; + let params: MakeInvoiceRequest = parse_json_from_value(template.params)?; RequestParams::MakeInvoice(params) } Method::LookupInvoice => { - let params: LookupInvoiceRequest = serde_json::from_value(template.params)?; + let params: LookupInvoiceRequest = parse_json_from_value(template.params)?; RequestParams::LookupInvoice(params) } Method::ListTransactions => { - let params: ListTransactionsRequest = serde_json::from_value(template.params)?; + let params: ListTransactionsRequest = parse_json_from_value(template.params)?; RequestParams::ListTransactions(params) } Method::GetBalance => RequestParams::GetBalance, Method::GetInfo => RequestParams::GetInfo, Method::MakeHoldInvoice => { - let params: MakeHoldInvoiceRequest = serde_json::from_value(template.params)?; + let params: MakeHoldInvoiceRequest = parse_json_from_value(template.params)?; RequestParams::MakeHoldInvoice(params) } Method::SettleHoldInvoice => { - let params: SettleHoldInvoiceRequest = serde_json::from_value(template.params)?; + let params: SettleHoldInvoiceRequest = parse_json_from_value(template.params)?; RequestParams::SettleHoldInvoice(params) } Method::CancelHoldInvoice => { - let params: CancelHoldInvoiceRequest = serde_json::from_value(template.params)?; + let params: CancelHoldInvoiceRequest = parse_json_from_value(template.params)?; RequestParams::CancelHoldInvoice(params) } Method::Unknown(name) => { - return Err(Error::UnsupportedMethod(Method::Unknown(name))); + return Err(unsupported_method(&name)); } }; @@ -627,13 +564,13 @@ impl Request { pub fn to_event(self, uri: &NostrWalletConnectUri) -> Result { let encrypted = nip04::encrypt(&uri.secret, &uri.public_key, self.as_json())?; let keys: Keys = Keys::new(uri.secret.clone()); - Ok(EventBuilder::new(Kind::WalletConnectRequest, encrypted) + EventBuilder::new(Kind::WalletConnectRequest, encrypted) .tag(Tag::public_key(uri.public_key)) - .finalize(&keys)?) + .finalize(&keys) } } -impl_json_methods!(Request, Error); +impl_json_methods!(Request); impl<'de> Deserialize<'de> for Request { fn deserialize(deserializer: D) -> Result @@ -920,64 +857,61 @@ impl Response { #[inline] pub fn from_event(uri: &NostrWalletConnectUri, event: &Event) -> Result { let decrypt_res: String = nip04::decrypt(&uri.secret, &event.pubkey, &event.content)?; - Self::from_json(&decrypt_res).map_err(|e| Error::CantDeserializeResponse { - response: decrypt_res, - error: e.to_string(), - }) + Self::from_json(&decrypt_res) } /// Deserialize from JSON string pub fn from_value(value: Value) -> Result { - let template: ResponseTemplate = serde_json::from_value(value)?; + let template: ResponseTemplate = parse_json_from_value(value)?; if let Some(result) = template.result { let result = match template.result_type { Method::PayInvoice => { - let result: PayInvoiceResponse = serde_json::from_value(result)?; + let result: PayInvoiceResponse = parse_json_from_value(result)?; ResponseResult::PayInvoice(result) } Method::PayKeysend => { - let result: PayKeysendResponse = serde_json::from_value(result)?; + let result: PayKeysendResponse = parse_json_from_value(result)?; ResponseResult::PayKeysend(result) } Method::MakeInvoice => { - let result: MakeInvoiceResponse = serde_json::from_value(result)?; + let result: MakeInvoiceResponse = parse_json_from_value(result)?; ResponseResult::MakeInvoice(result) } Method::LookupInvoice => { - let result: LookupInvoiceResponse = serde_json::from_value(result)?; + let result: LookupInvoiceResponse = parse_json_from_value(result)?; ResponseResult::LookupInvoice(result) } Method::ListTransactions => { let transactions: Value = result .get("transactions") .cloned() - .ok_or(Error::UnexpectedResult)?; - let result: Vec = serde_json::from_value(transactions)?; + .ok_or(unexpected_result())?; + let result: Vec = parse_json_from_value(transactions)?; ResponseResult::ListTransactions(result) } Method::GetBalance => { - let result: GetBalanceResponse = serde_json::from_value(result)?; + let result: GetBalanceResponse = parse_json_from_value(result)?; ResponseResult::GetBalance(result) } Method::GetInfo => { - let result: GetInfoResponse = serde_json::from_value(result)?; + let result: GetInfoResponse = parse_json_from_value(result)?; ResponseResult::GetInfo(result) } Method::MakeHoldInvoice => { - let result: MakeHoldInvoiceResponse = serde_json::from_value(result)?; + let result: MakeHoldInvoiceResponse = parse_json_from_value(result)?; ResponseResult::MakeHoldInvoice(result) } Method::CancelHoldInvoice => { - let result: CancelHoldInvoiceResponse = serde_json::from_value(result)?; + let result: CancelHoldInvoiceResponse = parse_json_from_value(result)?; ResponseResult::CancelHoldInvoice(result) } Method::SettleHoldInvoice => { - let result: SettleHoldInvoiceResponse = serde_json::from_value(result)?; + let result: SettleHoldInvoiceResponse = parse_json_from_value(result)?; ResponseResult::SettleHoldInvoice(result) } Method::Unknown(name) => { - return Err(Error::UnsupportedMethod(Method::Unknown(name))); + return Err(unsupported_method(&name)); } }; @@ -998,96 +932,96 @@ impl Response { /// Covert [Response] to [PayInvoiceResponse] pub fn to_pay_invoice(self) -> Result { if let Some(e) = self.error { - return Err(Error::ErrorCode(e)); + return Err(error_code(e)); } if let Some(ResponseResult::PayInvoice(result)) = self.result { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } /// Covert [Response] to [PayKeysendResponse] pub fn to_pay_keysend(self) -> Result { if let Some(e) = self.error { - return Err(Error::ErrorCode(e)); + return Err(error_code(e)); } if let Some(ResponseResult::PayKeysend(result)) = self.result { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } /// Covert [Response] to [MakeInvoiceResponse] pub fn to_make_invoice(self) -> Result { if let Some(e) = self.error { - return Err(Error::ErrorCode(e)); + return Err(error_code(e)); } if let Some(ResponseResult::MakeInvoice(result)) = self.result { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } /// Covert [Response] to [LookupInvoiceResponse] pub fn to_lookup_invoice(self) -> Result { if let Some(e) = self.error { - return Err(Error::ErrorCode(e)); + return Err(error_code(e)); } if let Some(ResponseResult::LookupInvoice(result)) = self.result { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } /// Covert [Response] to list of [LookupInvoiceResponse] pub fn to_list_transactions(self) -> Result, Error> { if let Some(e) = self.error { - return Err(Error::ErrorCode(e)); + return Err(error_code(e)); } if let Some(ResponseResult::ListTransactions(result)) = self.result { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } /// Covert [Response] to [GetBalanceResponse] pub fn to_get_balance(self) -> Result { if let Some(e) = self.error { - return Err(Error::ErrorCode(e)); + return Err(error_code(e)); } if let Some(ResponseResult::GetBalance(result)) = self.result { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } /// Covert [Response] to [GetInfoResponse] pub fn to_get_info(self) -> Result { if let Some(e) = self.error { - return Err(Error::ErrorCode(e)); + return Err(error_code(e)); } if let Some(ResponseResult::GetInfo(result)) = self.result { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } } -impl_json_methods!(Response, Error); +impl_json_methods!(Response); impl<'de> Deserialize<'de> for Response { fn deserialize(deserializer: D) -> Result @@ -1149,14 +1083,14 @@ impl NostrWalletConnectUri { where S: AsRef, { - let url: Url = Url::parse(uri.as_ref()).map_err(|_| Error::InvalidURI)?; + let url: Url = Url::parse(uri.as_ref()).map_err(|_| invalid_uri())?; if url.scheme() != NOSTR_WALLET_CONNECT_URI_SCHEME { - return Err(Error::InvalidURI); + return Err(invalid_uri()); } if let Some(pubkey) = url.domain() { - let public_key = PublicKey::from_hex(pubkey).map_err(|_| Error::InvalidURI)?; + let public_key = PublicKey::from_hex(pubkey).map_err(|_| invalid_uri())?; let mut relays: Vec = Vec::new(); let mut secret: Option = None; @@ -1190,7 +1124,7 @@ impl NostrWalletConnectUri { } } - Err(Error::InvalidURI) + Err(invalid_uri()) } } @@ -1278,7 +1212,7 @@ impl FromStr for NotificationType { "payment_received" => Ok(NotificationType::PaymentReceived), "payment_sent" => Ok(NotificationType::PaymentSent), "hold_invoice_accepted" => Ok(NotificationType::HoldInvoiceAccepted), - _ => Err(Error::InvalidURI), + _ => Err(invalid_uri()), } } } @@ -1311,20 +1245,20 @@ impl Notification { /// Deserialize from JSON string pub fn from_value(value: Value) -> Result { - let template: NotificationTemplate = serde_json::from_value(value)?; + let template: NotificationTemplate = parse_json_from_value(value)?; let result = template.notification; let result = match template.notification_type { NotificationType::PaymentReceived => { - let result: PaymentNotification = serde_json::from_value(result)?; + let result: PaymentNotification = parse_json_from_value(result)?; NotificationResult::PaymentReceived(result) } NotificationType::PaymentSent => { - let result: PaymentNotification = serde_json::from_value(result)?; + let result: PaymentNotification = parse_json_from_value(result)?; NotificationResult::PaymentSent(result) } NotificationType::HoldInvoiceAccepted => { - let result: HoldInvoiceAcceptedNotification = serde_json::from_value(result)?; + let result: HoldInvoiceAcceptedNotification = parse_json_from_value(result)?; NotificationResult::HoldInvoiceAccepted(result) } }; @@ -1344,7 +1278,7 @@ impl Notification { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } /// Convert [Notification] to [HoldInvoiceAcceptedNotification] @@ -1355,11 +1289,11 @@ impl Notification { return Ok(result); } - Err(Error::UnexpectedResult) + Err(unexpected_result()) } } -impl_json_methods!(Notification, Error); +impl_json_methods!(Notification); impl<'de> Deserialize<'de> for Notification { fn deserialize(deserializer: D) -> Result diff --git a/crates/nostr/src/nips/nip49.rs b/crates/nostr/src/nips/nip49.rs index 491ae2d4f..f8b2ad2ff 100644 --- a/crates/nostr/src/nips/nip49.rs +++ b/crates/nostr/src/nips/nip49.rs @@ -8,8 +8,6 @@ use alloc::string::String; use alloc::vec::Vec; -use core::array::TryFromSliceError; -use core::fmt; use chacha20poly1305::XChaCha20Poly1305; use chacha20poly1305::aead::{Aead, KeyInit, Payload}; @@ -20,109 +18,60 @@ use rand::rngs::SysRng; #[cfg(feature = "rand")] use rand::{CryptoRng, Rng}; use scrypt::Params as ScryptParams; -use scrypt::errors::{InvalidOutputLen, InvalidParams}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use unicode_normalization::UnicodeNormalization; use super::nip19::{FromBech32, ToBech32}; -use crate::{SecretKey, key}; +use crate::SecretKey; +use crate::error::{Error, ErrorKind}; const SALT_SIZE: usize = 16; const NONCE_SIZE: usize = 24; const CIPHERTEXT_SIZE: usize = 48; const KEY_SIZE: usize = 32; -/// NIP49 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// ChaCha20Poly1305 error - ChaCha20Poly1305(chacha20poly1305::Error), - /// Invalid scrypt params - InvalidScryptParams(InvalidParams), - /// Invalid scrypt output len - InvalidScryptOutputLen(InvalidOutputLen), - /// Keys error - Keys(key::Error), - /// Try from slice - TryFromSlice, - /// Invalid len - InvalidLength { - /// Expected bytes len - expected: usize, - /// Found bytes len - found: usize, - }, - /// Unknown version - UnknownVersion(u8), - /// Unknown Key Security - UnknownKeySecurity(u8), - /// Version not found - VersionNotFound, - /// Log2 round not found - Log2RoundNotFound, - /// Salt not found - SaltNotFound, - /// Nonce not found - NonceNotFound, - /// Key security not found - KeySecurityNotFound, - /// Cipthertext not found - CipherTextNotFound, +fn unknown_version(version: u8) -> Error { + Error::new( + ErrorKind::Unsupported, + format!("unknown version: {version}"), + ) } -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ChaCha20Poly1305(e) => e.fmt(f), - Self::InvalidScryptParams(e) => e.fmt(f), - Self::InvalidScryptOutputLen(e) => e.fmt(f), - Self::Keys(e) => e.fmt(f), - Self::TryFromSlice => f.write_str("From slice error"), - Self::InvalidLength { expected, found } => { - write!(f, "Invalid bytes len: expected={expected}, found={found}") - } - Self::UnknownVersion(v) => write!(f, "unknown version: {v}"), - Self::UnknownKeySecurity(v) => write!(f, "unknown security: {v}"), - Self::VersionNotFound => f.write_str("version not found"), - Self::Log2RoundNotFound => f.write_str("`log N` not found"), - Self::SaltNotFound => f.write_str("salt not found"), - Self::NonceNotFound => f.write_str("nonce not found"), - Self::KeySecurityNotFound => f.write_str("security not found"), - Self::CipherTextNotFound => f.write_str("ciphertext not found"), - } - } +fn unknown_key_security(key_security: u8) -> Error { + Error::new( + ErrorKind::Unsupported, + format!("unknown key security: {key_security}"), + ) } -impl From for Error { - fn from(e: chacha20poly1305::Error) -> Self { - Self::ChaCha20Poly1305(e) - } +#[inline] +fn version_not_found() -> Error { + Error::with_static_message(ErrorKind::Missing, "version not found") } -impl From for Error { - fn from(e: InvalidParams) -> Self { - Self::InvalidScryptParams(e) - } +#[inline] +fn log2_round_not_found() -> Error { + Error::with_static_message(ErrorKind::Missing, "log2 round not found") } -impl From for Error { - fn from(e: InvalidOutputLen) -> Self { - Self::InvalidScryptOutputLen(e) - } +#[inline] +fn salt_not_found() -> Error { + Error::with_static_message(ErrorKind::Missing, "salt not found") } -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } +#[inline] +fn nonce_not_found() -> Error { + Error::with_static_message(ErrorKind::Missing, "nonce not found") } -impl From for Error { - fn from(_e: TryFromSliceError) -> Self { - Self::TryFromSlice - } +#[inline] +fn key_security_not_found() -> Error { + Error::with_static_message(ErrorKind::Missing, "key security not found") +} + +#[inline] +fn cipher_text_not_found() -> Error { + Error::with_static_message(ErrorKind::Missing, "cipher text not found") } /// Encrypted Secret Key version (NIP49) @@ -140,7 +89,7 @@ impl TryFrom for Version { match version { // 0x01 => deprecated, 0x02 => Ok(Self::V2), - v => Err(Error::UnknownVersion(v)), + v => Err(unknown_version(v)), } } } @@ -165,7 +114,7 @@ impl TryFrom for KeySecurity { 0x00 => Ok(Self::Weak), 0x01 => Ok(Self::Medium), 0x02 => Ok(Self::Unknown), - v => Err(Error::UnknownKeySecurity(v)), + v => Err(unknown_key_security(v)), } } } @@ -260,8 +209,11 @@ impl EncryptedSecretKey { }; // Encrypt - let ciphertext: Vec = cipher.encrypt(&nonce.into(), payload)?; - let ciphertext: [u8; CIPHERTEXT_SIZE] = ciphertext.as_slice().try_into()?; + let ciphertext: Vec = cipher + .encrypt(&nonce.into(), payload) + .map_err(Error::crypto_display)?; + let ciphertext: [u8; CIPHERTEXT_SIZE] = + ciphertext.as_slice().try_into().map_err(Error::malformed)?; Ok(Self { version: Version::default(), @@ -276,41 +228,41 @@ impl EncryptedSecretKey { /// Parse encrypted secret key from bytes pub fn from_slice(slice: &[u8]) -> Result { if slice.len() != Self::LEN { - return Err(Error::InvalidLength { - expected: Self::LEN, - found: slice.len(), - }); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid length", + )); } // Version - let version: u8 = slice.first().copied().ok_or(Error::VersionNotFound)?; + let version: u8 = slice.first().copied().ok_or(version_not_found())?; let version: Version = Version::try_from(version)?; // Log 2 rounds - let log_n: u8 = slice.get(1).copied().ok_or(Error::Log2RoundNotFound)?; + let log_n: u8 = slice.get(1).copied().ok_or(log2_round_not_found())?; // Salt - let salt: &[u8] = slice.get(2..2 + SALT_SIZE).ok_or(Error::SaltNotFound)?; - let salt: [u8; SALT_SIZE] = salt.try_into()?; + let salt: &[u8] = slice.get(2..2 + SALT_SIZE).ok_or(salt_not_found())?; + let salt: [u8; SALT_SIZE] = salt.try_into().map_err(Error::malformed)?; // Nonce let nonce: &[u8] = slice .get(2 + SALT_SIZE..2 + SALT_SIZE + NONCE_SIZE) - .ok_or(Error::NonceNotFound)?; - let nonce: [u8; NONCE_SIZE] = nonce.try_into()?; + .ok_or(nonce_not_found())?; + let nonce: [u8; NONCE_SIZE] = nonce.try_into().map_err(Error::malformed)?; // Key security let key_security: u8 = slice .get(2 + SALT_SIZE + NONCE_SIZE) .copied() - .ok_or(Error::KeySecurityNotFound)?; + .ok_or(key_security_not_found())?; let key_security: KeySecurity = KeySecurity::try_from(key_security)?; // Ciphertext let ciphertext: &[u8] = slice .get(2 + SALT_SIZE + NONCE_SIZE + 1..) - .ok_or(Error::CipherTextNotFound)?; - let ciphertext: [u8; CIPHERTEXT_SIZE] = ciphertext.try_into()?; + .ok_or(cipher_text_not_found())?; + let ciphertext: [u8; CIPHERTEXT_SIZE] = ciphertext.try_into().map_err(Error::malformed)?; Ok(Self { version, @@ -367,10 +319,12 @@ impl EncryptedSecretKey { }; // Decrypt - let bytes: Vec = cipher.decrypt(&self.nonce.into(), payload)?; + let bytes: Vec = cipher + .decrypt(&self.nonce.into(), payload) + .map_err(Error::crypto_display)?; // Parse secret key from bytes - Ok(SecretKey::from_slice(&bytes)?) + SecretKey::from_slice(&bytes) } } @@ -399,11 +353,11 @@ fn derive_key(password: &str, salt: &[u8; SALT_SIZE], log_n: u8) -> Result<[u8; let password: String = password.nfkc().collect(); // Compose params - let params: ScryptParams = ScryptParams::new(log_n, 8, 1)?; + let params: ScryptParams = ScryptParams::new(log_n, 8, 1).map_err(Error::invalid)?; // Derive key let mut key: [u8; KEY_SIZE] = [0u8; KEY_SIZE]; - scrypt::scrypt(password.as_bytes(), salt, ¶ms, &mut key)?; + scrypt::scrypt(password.as_bytes(), salt, ¶ms, &mut key).map_err(Error::invalid)?; Ok(key) } diff --git a/crates/nostr/src/nips/nip51.rs b/crates/nostr/src/nips/nip51.rs index 32e54806a..0dc3b835a 100644 --- a/crates/nostr/src/nips/nip51.rs +++ b/crates/nostr/src/nips/nip51.rs @@ -7,14 +7,16 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::fmt; use super::nip01::Coordinate; use super::nip30::Nip30Tag; -use super::util::{take_event_id, take_public_key, take_relay_url, take_string}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::types::url::{self, RelayUrl, Url}; -use crate::{EventId, PublicKey, event, key}; +use super::util::{ + missing_tag_kind, take_event_id, take_public_key, take_relay_url, take_string, unknown_tag, +}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::types::url::{RelayUrl, Url}; +use crate::{EventId, PublicKey}; const WORD: &str = "word"; const PUBLIC_KEY: &str = "p"; @@ -22,56 +24,6 @@ const HASHTAG: &str = "t"; const EVENT: &str = "e"; const RELAY: &str = "relay"; -/// NIP-51 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Event error - Event(event::Error), - /// Key error - Key(key::Error), - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Event(e) => e.fmt(f), - Self::Key(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-51 tags /// /// @@ -98,11 +50,11 @@ impl TagCodec for Nip51Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { PUBLIC_KEY => { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; + let public_key: PublicKey = take_public_key(&mut iter)?; Ok(Self::PublicKey(public_key)) } HASHTAG => { @@ -110,15 +62,15 @@ impl TagCodec for Nip51Tag { Ok(Self::Hashtag(hashtag.to_lowercase())) } EVENT => { - let event_id: EventId = take_event_id::<_, _, Error>(&mut iter)?; + let event_id: EventId = take_event_id(&mut iter)?; Ok(Self::Event(event_id)) } RELAY => { - let relay_url: RelayUrl = take_relay_url::<_, _, Error>(&mut iter)?; + let relay_url: RelayUrl = take_relay_url(&mut iter)?; Ok(Self::Relay(relay_url)) } WORD => Ok(Self::Word(take_string(&mut iter, "word")?)), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip53.rs b/crates/nostr/src/nips/nip53.rs index a9f393e7e..11d9d594e 100644 --- a/crates/nostr/src/nips/nip53.rs +++ b/crates/nostr/src/nips/nip53.rs @@ -10,22 +10,21 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use core::fmt; -use core::num::ParseIntError; use core::str::FromStr; use secp256k1::schnorr::Signature; -use super::nip01::{self, Coordinate}; +use super::nip01::Coordinate; use super::util::{ - take_and_parse_from_str, take_and_parse_optional_from_str, take_and_parse_optional_relay_url, - take_coordinate, take_event_id, take_optional_string, take_public_key, take_string, - take_timestamp, + invalid_value, missing_tag_kind, missing_value, take_and_parse_from_str, + take_and_parse_optional_from_str, take_and_parse_optional_relay_url, take_coordinate, + take_event_id, take_optional_string, take_public_key, take_string, take_timestamp, unknown_tag, }; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::key::{self, PublicKey}; -use crate::types::image; -use crate::types::url::{self, RelayUrl, Url}; -use crate::{Event, EventId, ImageDimensions, Kind, Timestamp, event}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::key::PublicKey; +use crate::types::url::{RelayUrl, Url}; +use crate::{Event, EventId, ImageDimensions, Kind, Timestamp}; const TITLE: &str = "title"; const SUMMARY: &str = "summary"; @@ -44,102 +43,9 @@ const ENDPOINT: &str = "endpoint"; const PINNED: &str = "pinned"; const HAND: &str = "hand"; -/// NIP53 Error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Secp256k1 error - Secp256k1(secp256k1::Error), - /// Keys error - Keys(key::Error), - /// Event error - Event(event::Error), - /// Url error - Url(url::Error), - /// URL parse error - UrlParse(url::ParseError), - /// Image error - Image(image::Error), - /// NIP-01 error - NIP01(nip01::Error), - /// Parse int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), - /// Description missing from event - DescriptionMissing, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Secp256k1(e) => e.fmt(f), - Self::Keys(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::UrlParse(e) => e.fmt(f), - Self::Image(e) => e.fmt(f), - Self::NIP01(e) => e.fmt(f), - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::DescriptionMissing => f.write_str("Event missing a description"), - } - } -} - -impl From for Error { - fn from(e: secp256k1::Error) -> Self { - Self::Secp256k1(e) - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::UrlParse(e) - } -} - -impl From for Error { - fn from(e: image::Error) -> Self { - Self::Image(e) - } -} - -impl From for Error { - fn from(e: nip01::Error) -> Self { - Self::NIP01(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } +#[inline] +fn description_missing() -> Error { + Error::with_static_message(ErrorKind::Missing, "description missing") } /// Live Event Marker @@ -316,13 +222,13 @@ impl TagCodec for Nip53Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { TITLE => Ok(Self::Title(take_string(&mut iter, "title")?)), SUMMARY => Ok(Self::Summary(take_string(&mut iter, "summary")?)), IMAGE => { - let image: Url = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "image URL")?; + let image: Url = take_and_parse_from_str(&mut iter, "image URL")?; let dimensions: Option = take_and_parse_optional_from_str(&mut iter)?; Ok(Self::Image(image, dimensions)) @@ -330,42 +236,36 @@ impl TagCodec for Nip53Tag { "t" => { let hashtag: String = take_string(&mut iter, "hashtag")?; if hashtag.chars().any(char::is_uppercase) { - return Err( - TagCodecError::Invalid("hashtag contains uppercase characters").into(), - ); + return Err(invalid_value("hashtag contains uppercase characters")); } Ok(Self::Hashtag(hashtag)) } STREAMING => { - let url: Url = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "streaming URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "streaming URL")?; Ok(Self::Streaming(url)) } RECORDING => { - let url: Url = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "recording URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "recording URL")?; Ok(Self::Recording(url)) } STARTS => { - let timestamp: Timestamp = take_timestamp::<_, _, Error>(&mut iter)?; + let timestamp: Timestamp = take_timestamp(&mut iter)?; Ok(Self::Starts(timestamp)) } ENDS => { - let timestamp: Timestamp = take_timestamp::<_, _, Error>(&mut iter)?; + let timestamp: Timestamp = take_timestamp(&mut iter)?; Ok(Self::Ends(timestamp)) } STATUS => Ok(Self::Status(LiveEventStatus::from(take_string( &mut iter, "status", )?))), CURRENT_PARTICIPANTS => { - let num: u64 = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "current participants")?; + let num: u64 = take_and_parse_from_str(&mut iter, "current participants")?; Ok(Self::CurrentParticipants(num)) } TOTAL_PARTICIPANTS => { - let num: u64 = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "total participants")?; + let num: u64 = take_and_parse_from_str(&mut iter, "total participants")?; Ok(Self::TotalParticipants(num)) } RELAYS => { @@ -378,21 +278,20 @@ impl TagCodec for Nip53Tag { "p" => parse_p_tag(iter), ROOM => Ok(Self::Room(take_string(&mut iter, "room")?)), SERVICE => { - let url: Url = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "service URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "service URL")?; Ok(Self::Service(url)) } ENDPOINT => { - let url: Url = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "endpoint URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "endpoint URL")?; Ok(Self::Endpoint(url)) } PINNED => { - let event_id: EventId = take_event_id::<_, _, Error>(&mut iter)?; + let event_id: EventId = take_event_id(&mut iter)?; Ok(Self::Pinned(event_id)) } "a" => parse_a_tag(iter), HAND => parse_hand_tag(iter), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -475,10 +374,11 @@ where T: Iterator, S: AsRef, { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; + let public_key: PublicKey = take_public_key(&mut iter)?; let relay_url: Option = take_and_parse_optional_relay_url(&mut iter)?; - let marker: LiveEventMarker = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "marker")?; - let proof: Option = take_and_parse_optional_from_str(&mut iter)?; + let marker: LiveEventMarker = take_and_parse_from_str(&mut iter, "marker")?; + let proof: Option = + take_and_parse_optional_from_str(&mut iter).map_err(Error::malformed_display)?; Ok(Nip53Tag::Participant(LiveEventParticipant { public_key, @@ -493,7 +393,7 @@ where T: Iterator, S: AsRef, { - let coordinate = take_coordinate::<_, _, Error>(&mut iter)?; + let coordinate = take_coordinate(&mut iter)?; let relay_url: Option = take_and_parse_optional_relay_url(&mut iter)?; let marker: Option = take_optional_string(&mut iter); @@ -509,11 +409,11 @@ where T: Iterator, S: AsRef, { - let hand: S = iter.next().ok_or(TagCodecError::Missing("hand"))?; + let hand: S = iter.next().ok_or(missing_value("hand"))?; let hand: bool = match hand.as_ref() { "1" => true, "0" => false, - _ => return Err(TagCodecError::Unknown.into()), + _ => return Err(unknown_tag()), }; Ok(Nip53Tag::Hand(hand)) @@ -625,7 +525,7 @@ impl LiveEvent { let id = if event.kind == Kind::Custom(10312) { String::new() } else { - event.tags.identifier().ok_or(Error::DescriptionMissing)? + event.tags.identifier().ok_or(description_missing())? }; let mut live_event = Self::new(id); @@ -634,7 +534,7 @@ impl LiveEvent { for tag in event.tags.iter() { let parsed = match Nip53Tag::try_from(tag) { Ok(tag) => tag, - Err(Error::Codec(TagCodecError::Unknown)) => continue, + Err(e) if e.kind() == ErrorKind::Malformed => continue, Err(err) => return Err(err), }; @@ -879,14 +779,14 @@ impl TryFrom> for LiveEvent { .find(|t| t.kind() == "d") .and_then(|t| t.content()) .map(|value| value.to_string()) - .ok_or(Error::DescriptionMissing)?; + .ok_or(description_missing())?; let mut live_event = LiveEvent::new(id); for tag in tags.into_iter() { let parsed = match Nip53Tag::try_from(tag) { Ok(tag) => tag, - Err(Error::Codec(TagCodecError::Unknown)) => continue, + Err(e) if e.kind() == ErrorKind::Malformed => continue, Err(err) => return Err(err), }; diff --git a/crates/nostr/src/nips/nip56.rs b/crates/nostr/src/nips/nip56.rs index 24c8f53e2..71b2cf11a 100644 --- a/crates/nostr/src/nips/nip56.rs +++ b/crates/nostr/src/nips/nip56.rs @@ -11,52 +11,16 @@ use alloc::vec; use core::fmt; use core::str::FromStr; -use super::util::{take_and_parse_from_str, take_event_id, take_public_key}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::{EventId, PublicKey, event, key}; - -/// NIP56 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Keys error - Keys(key::Error), - /// Event error - Event(event::Error), - /// Codec error - Codec(TagCodecError), - /// Unknown [`Report`] - UnknownReportType, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Keys(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::UnknownReportType => f.write_str("Unknown report type"), - } - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } +use super::util::{ + missing_tag_kind, take_and_parse_from_str, take_event_id, take_public_key, unknown_tag, +}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::{EventId, PublicKey}; + +#[inline] +fn unknown_report_type() -> Error { + Error::with_static_message(ErrorKind::Unsupported, "unknown report type") } /// Report @@ -113,7 +77,7 @@ impl FromStr for Report { "spam" => Ok(Self::Spam), "impersonation" => Ok(Self::Impersonation), "other" => Ok(Self::Other), - _ => Err(Error::UnknownReportType), + _ => Err(unknown_report_type()), } } } @@ -151,7 +115,7 @@ impl TagCodec for Nip56Tag { let mut iter = tag.into_iter(); // Extract first value - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; // Match kind match kind.as_ref() { @@ -163,7 +127,7 @@ impl TagCodec for Nip56Tag { let (public_key, report) = parse_p_tag(iter)?; Ok(Self::PublicKey { public_key, report }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -188,8 +152,8 @@ where T: Iterator, S: AsRef, { - let id: EventId = take_event_id::<_, _, Error>(&mut iter)?; - let report: Report = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "report")?; + let id: EventId = take_event_id(&mut iter)?; + let report: Report = take_and_parse_from_str(&mut iter, "report")?; Ok((id, report)) } @@ -199,8 +163,8 @@ where T: Iterator, S: AsRef, { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; - let report: Report = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "report")?; + let public_key: PublicKey = take_public_key(&mut iter)?; + let report: Report = take_and_parse_from_str(&mut iter, "report")?; Ok((public_key, report)) } @@ -259,19 +223,19 @@ mod tests { "p", "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d", ]; - assert!(matches!( - Nip56Tag::parse(&tag).unwrap_err(), - Error::Codec(TagCodecError::Missing("report")) - )); + assert_eq!( + Nip56Tag::parse(&tag).unwrap_err().kind(), + ErrorKind::Missing + ); let tag = vec![ "e", "378f145897eea948952674269945e88612420db35791784abf0616b4fed56ef7", ]; - assert!(matches!( - Nip56Tag::parse(&tag).unwrap_err(), - Error::Codec(TagCodecError::Missing("report")) - )); + assert_eq!( + Nip56Tag::parse(&tag).unwrap_err().kind(), + ErrorKind::Missing + ); } #[test] @@ -281,19 +245,19 @@ mod tests { "13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d", "", ]; - assert!(matches!( - Nip56Tag::parse(&tag).unwrap_err(), - Error::UnknownReportType - )); + assert_eq!( + Nip56Tag::parse(&tag).unwrap_err().kind(), + ErrorKind::Malformed + ); let tag = vec![ "e", "378f145897eea948952674269945e88612420db35791784abf0616b4fed56ef7", "", ]; - assert!(matches!( - Nip56Tag::parse(&tag).unwrap_err(), - Error::UnknownReportType - )); + assert_eq!( + Nip56Tag::parse(&tag).unwrap_err().kind(), + ErrorKind::Malformed + ); } } diff --git a/crates/nostr/src/nips/nip57.rs b/crates/nostr/src/nips/nip57.rs index 28e7d54e3..65de0172b 100644 --- a/crates/nostr/src/nips/nip57.rs +++ b/crates/nostr/src/nips/nip57.rs @@ -9,18 +9,15 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use core::num::ParseIntError; use super::nip01::Coordinate; use super::util::{ - take_and_parse_from_str, take_and_parse_optional_from_str, take_public_key, take_relay_url, - take_string, + missing_tag_kind, take_and_parse_from_str, take_and_parse_optional_from_str, take_public_key, + take_relay_url, take_string, unknown_tag, }; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::key::Error as KeyError; -use crate::types::url; -use crate::{EventId, PublicKey, RelayUrl, event}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::{EventId, PublicKey, RelayUrl}; const AMOUNT: &str = "amount"; const BOLT11: &str = "bolt11"; @@ -31,89 +28,6 @@ const RELAYS: &str = "relays"; const SENDER: &str = "P"; const ZAP: &str = "zap"; -#[allow(missing_docs)] -#[derive(Debug)] -pub enum Error { - Key(KeyError), - Event(event::Error), - Url(url::Error), - ParseInt(ParseIntError), - Bech32Decode(bech32::DecodeError), - Bech32Encode(bech32::EncodeError), - /// Codec error - Codec(TagCodecError), - InvalidPrivateZapMessage, - PrivateZapMessageNotFound, - /// Wrong prefix or variant - WrongBech32Prefix, - /// Wrong encryption block mode - WrongBlockMode, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Key(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::ParseInt(e) => e.fmt(f), - Self::Bech32Decode(e) => e.fmt(f), - Self::Bech32Encode(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidPrivateZapMessage => f.write_str("Invalid private zap message"), - Self::PrivateZapMessageNotFound => f.write_str("Private zap message not found"), - Self::WrongBech32Prefix => f.write_str("Wrong bech32 prefix"), - Self::WrongBlockMode => f.write_str( - "Wrong encryption block mode. The content must be encrypted using CBC mode!", - ), - } - } -} - -impl From for Error { - fn from(e: KeyError) -> Self { - Self::Key(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: bech32::DecodeError) -> Self { - Self::Bech32Decode(e) - } -} - -impl From for Error { - fn from(e: bech32::EncodeError) -> Self { - Self::Bech32Encode(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-57 tags /// /// @@ -158,7 +72,7 @@ impl TagCodec for Nip57Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { RELAYS => Ok(Self::Relays(parse_relays(iter)?)), @@ -171,7 +85,7 @@ impl TagCodec for Nip57Tag { DESCRIPTION => Ok(Self::Description(take_string(&mut iter, "description")?)), PREIMAGE => Ok(Self::Preimage(take_string(&mut iter, "preimage")?)), SENDER => { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; + let public_key: PublicKey = take_public_key(&mut iter)?; Ok(Self::Sender(public_key)) } ZAP => { @@ -182,7 +96,7 @@ impl TagCodec for Nip57Tag { weight, }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -250,7 +164,7 @@ where T: Iterator, S: AsRef, { - let millisats: u64 = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "amount")?; + let millisats: u64 = take_and_parse_from_str(&mut iter, "amount")?; let bolt11: Option = iter.next().map(|bolt11| bolt11.as_ref().to_string()); Ok((millisats, bolt11)) @@ -261,9 +175,10 @@ where T: Iterator, S: AsRef, { - let public_key: PublicKey = take_public_key::<_, _, Error>(&mut iter)?; - let relay_url: RelayUrl = take_relay_url::<_, _, Error>(&mut iter)?; - let weight: Option = take_and_parse_optional_from_str(&mut iter)?; + let public_key: PublicKey = take_public_key(&mut iter)?; + let relay_url: RelayUrl = take_relay_url(&mut iter)?; + let weight: Option = + take_and_parse_optional_from_str(&mut iter).map_err(Error::malformed)?; Ok((public_key, relay_url, weight)) } diff --git a/crates/nostr/src/nips/nip58.rs b/crates/nostr/src/nips/nip58.rs index c5f3cca36..d9625ec8f 100644 --- a/crates/nostr/src/nips/nip58.rs +++ b/crates/nostr/src/nips/nip58.rs @@ -9,13 +9,16 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; use super::nip01::Nip01Tag; -use super::util::{take_and_parse_from_str, take_and_parse_optional_from_str, take_string}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::types::url::{self, Url}; -use crate::types::{RelayUrl, image}; +use super::util::{ + missing_tag_kind, take_and_parse_from_str, take_and_parse_optional_from_str, take_string, + unknown_tag, +}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::types::RelayUrl; +use crate::types::url::Url; use crate::{Event, ImageDimensions, Kind, PublicKey}; const IDENTIFIER: &str = "d"; @@ -24,66 +27,6 @@ const DESCRIPTION: &str = "description"; const IMAGE: &str = "image"; const THUMB: &str = "thumb"; -#[derive(Debug, PartialEq, Eq)] -/// Badge Award error -pub enum Error { - /// Image error - Image(image::Error), - /// Url error - Url(url::ParseError), - /// Codec error - Codec(TagCodecError), - /// Invalid length - InvalidLength, - /// Invalid kind - InvalidKind, - /// Identifier tag not found - IdentifierTagNotFound, - /// Mismatched badge definition or award - MismatchedBadgeDefinitionOrAward, - /// Badge awards lack the awarded public key - BadgeAwardsLackAwardedPublicKey, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Image(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidLength => f.write_str("invalid length"), - Self::InvalidKind => f.write_str("invalid kind"), - Self::IdentifierTagNotFound => f.write_str("identifier tag not found"), - Self::MismatchedBadgeDefinitionOrAward => { - f.write_str("mismatched badge definition/award") - } - Self::BadgeAwardsLackAwardedPublicKey => { - f.write_str("badge award events lack the awarded public key") - } - } - } -} - -impl From for Error { - fn from(e: image::Error) -> Self { - Self::Image(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-58 tags /// /// @@ -110,7 +53,7 @@ impl TagCodec for Nip58Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { IDENTIFIER => Ok(Self::Identifier(take_string(&mut iter, "identifier")?)), @@ -124,7 +67,7 @@ impl TagCodec for Nip58Tag { let (url, dimensions) = parse_url_and_dimensions_tag(iter, "thumbnail URL")?; Ok(Self::Thumb(url, dimensions)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -153,7 +96,7 @@ where T: Iterator, S: AsRef, { - let url: Url = take_and_parse_from_str::<_, _, _, Error>(&mut iter, missing_error)?; + let url: Url = take_and_parse_from_str(&mut iter, missing_error)?; let dimensions: Option = take_and_parse_optional_from_str(&mut iter)?; Ok((url, dimensions)) diff --git a/crates/nostr/src/nips/nip59.rs b/crates/nostr/src/nips/nip59.rs index c7213f066..6254167e3 100644 --- a/crates/nostr/src/nips/nip59.rs +++ b/crates/nostr/src/nips/nip59.rs @@ -8,9 +8,8 @@ #[cfg(all(feature = "std", feature = "os-rng"))] use alloc::boxed::Box; -use alloc::string::{String, ToString}; +use alloc::string::String; use alloc::vec::Vec; -use core::fmt; #[cfg(all(feature = "std", feature = "os-rng"))] use core::ops::Range; @@ -18,17 +17,16 @@ use secp256k1::{Secp256k1, Verification}; #[cfg(feature = "std")] use crate::SECP256K1; -use crate::event::{self, Event, UnsignedEvent}; +use crate::error::{Error, ErrorKind}; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::event::{AsyncSignEvent, FinalizeEvent, FinalizeEventAsync, SignEvent}; +use crate::event::{Event, UnsignedEvent}; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::key::{AsyncGetPublicKey, GetPublicKey, Keys}; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::nips::nip44; use crate::nips::nip44::{AsyncNip44, Nip44}; #[cfg(all(feature = "std", feature = "os-rng"))] -use crate::signer::SignerError; -#[cfg(all(feature = "std", feature = "os-rng"))] use crate::util::BoxedFuture; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::{EventBuilder, Timestamp}; @@ -37,48 +35,14 @@ use crate::{Kind, PublicKey, Tag}; #[cfg(all(feature = "std", feature = "os-rng"))] const RANGE_RANDOM_TIMESTAMP_TWEAK: Range = 0..172800; // From 0 secs to 2 days -/// NIP59 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Signer error - #[cfg(all(feature = "std", feature = "os-rng"))] - Signer(SignerError), - /// Event error - Event(event::Error), - /// NIP-44 error - NIP44(String), - /// Not Gift Wrap event - NotGiftWrap, - /// Rumor author does not match the seal signer - SenderMismatch, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - #[cfg(all(feature = "std", feature = "os-rng"))] - Self::Signer(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::NIP44(e) => e.fmt(f), - Self::NotGiftWrap => f.write_str("Not a Gift Wrap"), - Self::SenderMismatch => f.write_str("sender public key mismatch"), - } - } -} - -#[cfg(all(feature = "std", feature = "os-rng"))] -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } +#[inline] +fn not_gift_wrap() -> Error { + Error::with_static_message(ErrorKind::Invalid, "not a gift wrap") } -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } +#[inline] +fn sender_mismatch() -> Error { + Error::with_static_message(ErrorKind::Invalid, "sender mismatch") } /// Unwrapped Gift Wrap (NIP59) @@ -129,19 +93,19 @@ impl UnwrappedGift { { // Check event kind if gift_wrap.kind != Kind::GiftWrap { - return Err(Error::NotGiftWrap); + return Err(not_gift_wrap()); } // Decrypt and verify seal let seal: String = signer .nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content) - .map_err(|e| Error::NIP44(e.to_string()))?; + .map_err(Error::crypto)?; let seal: Event = parse_and_verify_seal(secp, seal)?; // Decrypt rumor let rumor: String = signer .nip44_decrypt(&seal.pubkey, &seal.content) - .map_err(|e| Error::NIP44(e.to_string()))?; + .map_err(Error::crypto)?; parse_unwrapped_gift(seal, rumor) } @@ -160,21 +124,21 @@ impl UnwrappedGift { { // Check event kind if gift_wrap.kind != Kind::GiftWrap { - return Err(Error::NotGiftWrap); + return Err(not_gift_wrap()); } // Decrypt and verify seal let seal: String = signer .nip44_decrypt_async(&gift_wrap.pubkey, &gift_wrap.content) .await - .map_err(|e| Error::NIP44(e.to_string()))?; + .map_err(Error::crypto)?; let seal: Event = parse_and_verify_seal(secp, seal)?; // Decrypt rumor let rumor: String = signer .nip44_decrypt_async(&seal.pubkey, &seal.content) .await - .map_err(|e| Error::NIP44(e.to_string()))?; + .map_err(Error::crypto)?; parse_unwrapped_gift(seal, rumor) } @@ -225,10 +189,10 @@ where // Encrypt content let content: String = signer .nip44_encrypt(&self.receiver, &self.rumor.as_json()) - .map_err(|e| Error::NIP44(e.to_string()))?; + .map_err(Error::crypto)?; let seal: EventBuilder = build_seal(self.rumor, content); - Ok(seal.finalize(signer)?) + seal.finalize(signer) } } @@ -249,10 +213,10 @@ where let content: String = signer .nip44_encrypt_async(&self.receiver, &self.rumor.as_json()) .await - .map_err(|e| Error::NIP44(e.to_string()))?; + .map_err(Error::crypto)?; let seal: EventBuilder = build_seal(self.rumor, content); - Ok(seal.finalize_async(signer).await?) + seal.finalize_async(signer).await }) } } @@ -353,7 +317,7 @@ fn make_gift_wrap(seal: Event, receiver: PublicKey, extra_tags: Vec) -> Res seal.as_json(), nip44::Version::default(), ) - .map_err(|e| Error::NIP44(e.to_string()))?; + .map_err(Error::crypto)?; // Collect extra tags let mut tags: Vec = extra_tags; @@ -362,10 +326,10 @@ fn make_gift_wrap(seal: Event, receiver: PublicKey, extra_tags: Vec) -> Res tags.push(Tag::public_key(receiver)); // Build the event with a tweaked timestamp - Ok(EventBuilder::new(Kind::GiftWrap, content) + EventBuilder::new(Kind::GiftWrap, content) .tags(tags) .custom_created_at(Timestamp::tweaked(RANGE_RANDOM_TIMESTAMP_TWEAK)) - .finalize(&keys)?) + .finalize(&keys) } fn parse_and_verify_seal(secp: &Secp256k1, seal: String) -> Result @@ -382,7 +346,7 @@ fn parse_unwrapped_gift(seal: Event, rumor: String) -> Result EventBuilder { #[cfg(test)] #[cfg(all(feature = "std", feature = "os-rng"))] mod tests { - use super::{Error, *}; + use super::*; use crate::prelude::*; #[test] @@ -455,10 +419,10 @@ mod tests { assert!(extract_rumor(&sender_keys, &event).is_err()); let event: Event = EventBuilder::text_note("").finalize(&sender_keys).unwrap(); - assert!(matches!( - extract_rumor(&receiver_keys, &event).unwrap_err(), - Error::NotGiftWrap - )); + assert_eq!( + extract_rumor(&receiver_keys, &event).unwrap_err().kind(), + not_gift_wrap().kind() + ); } #[tokio::test] @@ -542,9 +506,11 @@ mod tests { .finalize(&sender_keys) .unwrap(); - match extract_rumor(&receiver_keys, &gift_wrap) { - Err(Error::SenderMismatch) => {} - other => panic!("expected SenderMismatch, got {other:?}"), - } + assert_eq!( + extract_rumor(&receiver_keys, &gift_wrap) + .unwrap_err() + .kind(), + sender_mismatch().kind() + ); } } diff --git a/crates/nostr/src/nips/nip60.rs b/crates/nostr/src/nips/nip60.rs index a7a0ca18a..349125076 100644 --- a/crates/nostr/src/nips/nip60.rs +++ b/crates/nostr/src/nips/nip60.rs @@ -2,7 +2,9 @@ //! //! -use alloc::string::{String, ToString}; +use alloc::string::String; +#[cfg(all(feature = "std", feature = "os-rng"))] +use alloc::string::ToString; use alloc::vec::Vec; use core::fmt; use core::str::FromStr; @@ -10,12 +12,14 @@ use core::str::FromStr; use serde::{Deserialize, Serialize}; use super::nip44; -use crate::event::{self, Event, EventId}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Event, EventId}; #[cfg(all(feature = "std", feature = "os-rng"))] use crate::event::{EventBuilder, Kind, Tag}; use crate::key::{PublicKey, SecretKey}; use crate::types::time::Timestamp; -use crate::types::url::{ParseError, Url}; +use crate::types::url::Url; +use crate::util::parse_json; const E_TAG_STR: &str = "e"; const PRIVKEY: &str = "privkey"; @@ -26,72 +30,13 @@ const EVENT_MARKER_CREATED: &str = "created"; const EVENT_MARKER_DESTROYED: &str = "destroyed"; const EVENT_MARKER_REDEEMED: &str = "redeemed"; -/// NIP-60 error -#[derive(Debug)] -pub enum Error { - /// NIP44 error - Nip44(nip44::Error), - /// JSON error - Json(serde_json::Error), - /// Event error - Event(event::Error), - /// URL error - Url(ParseError), - /// Invalid direction - InvalidDirection, - /// Found multiple private keys - FoundMultiplePrivKeys, - /// Missing required field - MissingField(String), - /// Invalid amount - InvalidAmount, - /// Missing mint tag - MissingMintTag, - /// Invalid mint URL - InvalidMintUrl, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Nip44(e) => e.fmt(f), - Self::Json(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::InvalidDirection => f.write_str("Invalid direction"), - Self::FoundMultiplePrivKeys => f.write_str("Found multiple private keys"), - Self::MissingField(field) => write!(f, "Missing required field: {field}"), - Self::InvalidAmount => f.write_str("Invalid amount"), - Self::MissingMintTag => f.write_str("Missing mint tag"), - Self::InvalidMintUrl => f.write_str("Invalid mint URL"), - } - } +#[inline] +fn found_multiple_priv_keys() -> Error { + Error::with_static_message(ErrorKind::Invalid, "found multiple private keys") } -impl From for Error { - fn from(e: nip44::Error) -> Self { - Self::Nip44(e) - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: ParseError) -> Self { - Self::Url(e) - } +fn missing_field(field: &'static str) -> Error { + Error::with_static_message(ErrorKind::Missing, field) } /// Cashu proof @@ -139,7 +84,7 @@ impl WalletEvent { event: &Event, ) -> Result { let decrypted: String = nip44::decrypt(secret_key, public_key, &event.content)?; - let wallet_data: Vec> = serde_json::from_str(&decrypted)?; + let wallet_data: Vec> = parse_json(&decrypted)?; let mut privkey: String = String::new(); let mut mints: Vec = Vec::new(); @@ -153,7 +98,7 @@ impl WalletEvent { if privkey.is_empty() { privkey = value } else { - return Err(Error::FoundMultiplePrivKeys); + return Err(found_multiple_priv_keys()); } } MINT => { @@ -167,11 +112,11 @@ impl WalletEvent { } if privkey.is_empty() { - return Err(Error::MissingField(PRIVKEY.to_string())); + return Err(missing_field(PRIVKEY)); } if mints.is_empty() { - return Err(Error::MissingField(MINT.to_string())); + return Err(missing_field(MINT)); } Ok(Self { privkey, mints }) @@ -190,14 +135,9 @@ impl WalletEvent { wallet_data.push(vec![MINT, mint.as_str()]); } - let json: String = serde_json::to_string(&wallet_data)?; + let json: String = serde_json::to_string(&wallet_data).map_err(Error::malformed)?; - Ok(nip44::encrypt( - secret_key, - public_key, - json, - nip44::Version::V2, - )?) + nip44::encrypt(secret_key, public_key, json, nip44::Version::V2) } /// Convert to [`EventBuilder`]. @@ -245,7 +185,7 @@ impl TokenEvent { event: &Event, ) -> Result { let decrypted: String = nip44::decrypt(secret_key, public_key, &event.content)?; - Ok(serde_json::from_str(&decrypted)?) + parse_json(&decrypted) } /// Add destroyed token event ID @@ -260,13 +200,8 @@ impl TokenEvent { secret_key: &SecretKey, public_key: &PublicKey, ) -> Result { - let json: String = serde_json::to_string(self)?; - Ok(nip44::encrypt( - secret_key, - public_key, - json, - nip44::Version::V2, - )?) + let json: String = serde_json::to_string(self).map_err(Error::malformed)?; + nip44::encrypt(secret_key, public_key, json, nip44::Version::V2) } /// Convert to [`EventBuilder`]. @@ -316,7 +251,10 @@ impl FromStr for TransactionDirection { match s { "in" => Ok(Self::In), "out" => Ok(Self::Out), - _ => Err(Error::InvalidDirection), + _ => Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid direction", + )), } } } @@ -355,7 +293,7 @@ impl SpendingHistory { event: &Event, ) -> Result { let decrypted: String = nip44::decrypt(secret_key, public_key, &event.content)?; - let data: Vec> = serde_json::from_str(&decrypted)?; + let data: Vec> = parse_json(&decrypted)?; let mut direction = None; let mut amount = None; @@ -371,7 +309,9 @@ impl SpendingHistory { direction = Some(TransactionDirection::from_str(&item[1])?); } AMOUNT => { - amount = Some(item[1].parse().map_err(|_| Error::InvalidAmount)?); + amount = Some(item[1].parse().map_err(|_| { + Error::with_static_message(ErrorKind::Invalid, "invalid amount") + })?); } E_TAG_STR if item.len() >= 4 => { let event_id: EventId = EventId::from_hex(&item[1])?; @@ -395,9 +335,8 @@ impl SpendingHistory { } } - let direction: TransactionDirection = - direction.ok_or_else(|| Error::MissingField(DIRECTION.to_string()))?; - let amount: u64 = amount.ok_or_else(|| Error::MissingField(AMOUNT.to_string()))?; + let direction: TransactionDirection = direction.ok_or_else(|| missing_field(DIRECTION))?; + let amount: u64 = amount.ok_or_else(|| missing_field(AMOUNT))?; Ok(Self { direction, @@ -463,14 +402,9 @@ impl SpendingHistory { data.push(tag.to_vec()); } - let json: String = serde_json::to_string(&data)?; + let json: String = serde_json::to_string(&data).map_err(Error::malformed)?; - Ok(nip44::encrypt( - secret_key, - public_key, - json, - nip44::Version::V2, - )?) + nip44::encrypt(secret_key, public_key, json, nip44::Version::V2) } /// Convert to event builder @@ -536,9 +470,9 @@ impl QuoteEvent { .iter() .find(|t| t.kind() == MINT) .and_then(|tag| tag.content()) - .ok_or(Error::MissingMintTag)? + .ok_or_else(|| Error::with_static_message(ErrorKind::Missing, "missing mint tag"))? .parse() - .map_err(|_| Error::InvalidMintUrl)?; + .map_err(|_| Error::with_static_message(ErrorKind::Invalid, "invalid mint URL"))?; // Extract NIP-40 expiration from tags if present let expiration: Option = event.tags.expiration(); @@ -562,12 +496,7 @@ impl QuoteEvent { secret_key: &SecretKey, public_key: &PublicKey, ) -> Result { - Ok(nip44::encrypt( - secret_key, - public_key, - &self.quote_id, - nip44::Version::V2, - )?) + nip44::encrypt(secret_key, public_key, &self.quote_id, nip44::Version::V2) } /// Convert to event builder @@ -594,6 +523,8 @@ impl QuoteEvent { #[cfg(test)] mod tests { + use alloc::string::ToString; + use super::*; #[test] @@ -626,10 +557,10 @@ mod tests { fn test_token_event_data() { let mint_url = Url::parse("https://example.com").unwrap(); let proof = CashuProof { - id: "test_id".to_string(), + id: String::from("test_id"), amount: 100, - secret: "test_secret".to_string(), - c: "test_c".to_string(), + secret: String::from("test_secret"), + c: String::from("test_c"), }; let token_data = TokenEvent::new(mint_url.clone(), vec![proof.clone()]); diff --git a/crates/nostr/src/nips/nip62.rs b/crates/nostr/src/nips/nip62.rs index dcd8f2ff0..0243e6ad1 100644 --- a/crates/nostr/src/nips/nip62.rs +++ b/crates/nostr/src/nips/nip62.rs @@ -9,46 +9,15 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::types::url::{self, RelayUrl}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::nips::util::{missing_tag_kind, missing_value, unknown_tag}; +use crate::types::url::RelayUrl; const RELAY: &str = "relay"; const ALL_RELAYS: &str = "ALL_RELAYS"; -/// NIP-70 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Request to Vanish target #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum VanishTarget { @@ -107,12 +76,12 @@ impl TagCodec for Nip62Tag { let mut iter = tag.into_iter(); // Extract first value - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; // Match kind match kind.as_ref() { RELAY => parse_relay_tag(iter), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -131,7 +100,7 @@ where T: Iterator, S: AsRef, { - let relay_url: S = iter.next().ok_or(TagCodecError::Missing("relay URL"))?; + let relay_url: S = iter.next().ok_or(missing_value("relay URL"))?; match relay_url.as_ref() { ALL_RELAYS => Ok(Nip62Tag::AllRelays), diff --git a/crates/nostr/src/nips/nip65.rs b/crates/nostr/src/nips/nip65.rs index 5b7e83efc..b08f5ae85 100644 --- a/crates/nostr/src/nips/nip65.rs +++ b/crates/nostr/src/nips/nip65.rs @@ -11,48 +11,13 @@ use alloc::vec::Vec; use core::fmt; use core::str::FromStr; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::nips::util::take_relay_url; -use crate::types::url; +use crate::error::{Error, ErrorKind}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::nips::util::{missing_tag_kind, take_relay_url, unknown_tag}; use crate::{Event, RelayUrl}; const RELAY_METADATA: &str = "r"; -/// NIP56 error -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), - /// Invalid Relay Metadata - InvalidRelayMetadata, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidRelayMetadata => f.write_str("Invalid relay metadata"), - } - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Relay Metadata #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum RelayMetadata { @@ -97,7 +62,10 @@ impl FromStr for RelayMetadata { match s { "read" => Ok(Self::Read), "write" => Ok(Self::Write), - _ => Err(Error::InvalidRelayMetadata), + _ => Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid relay metadata", + )), } } } @@ -125,11 +93,11 @@ impl TagCodec for Nip65Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { RELAY_METADATA => { - let relay_url: RelayUrl = take_relay_url::<_, _, Error>(&mut iter)?; + let relay_url: RelayUrl = take_relay_url(&mut iter)?; let metadata: Option = match iter.next() { Some(metadata) => Some(RelayMetadata::from_str(metadata.as_ref())?), @@ -141,7 +109,7 @@ impl TagCodec for Nip65Tag { metadata, }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip66.rs b/crates/nostr/src/nips/nip66.rs index a93708c49..effa70c4d 100644 --- a/crates/nostr/src/nips/nip66.rs +++ b/crates/nostr/src/nips/nip66.rs @@ -9,13 +9,13 @@ use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; use core::convert::Infallible; use core::fmt; -use core::num::ParseIntError; use core::str::FromStr; use core::time::Duration; -use super::util::take_string; +use super::util::{missing_tag_kind, missing_value, take_string, unknown_tag}; use crate::Kind; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; use crate::util::UnwrapInfallible; const RTT_OPEN: &str = "rtt-open"; @@ -29,38 +29,6 @@ const TOPIC: &str = "t"; const KIND: &str = "k"; const GEOHASH: &str = "g"; -/// NIP-66 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Parse int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-66 tags /// /// @@ -107,7 +75,7 @@ impl TagCodec for Nip66Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { RTT_OPEN => Ok(Self::RttOpen(parse_time(iter, RTT_OPEN)?)), RTT_READ => Ok(Self::RttRead(parse_time(iter, RTT_READ)?)), @@ -138,12 +106,12 @@ impl TagCodec for Nip66Tag { let value = take_string(&mut iter, "kind")?; let BoolTag { value, yes } = BoolTag::parse(&value); Ok(Self::Kind { - kind: value.parse().map_err(Error::ParseInt)?, + kind: value.parse()?, is_accepted: yes, }) } GEOHASH => Ok(Self::Geohash(take_string(&mut iter, "geohash")?)), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -382,10 +350,10 @@ where { let time = iter .next() - .ok_or(TagCodecError::Missing(tag))? + .ok_or(missing_value(tag))? .as_ref() .parse::() - .map_err(Error::ParseInt)?; + .map_err(Error::malformed)?; Ok(Duration::from_millis(time)) } @@ -415,6 +383,7 @@ impl_tag_codec_conversions!(Nip66Tag); #[cfg(test)] mod tests { use super::*; + use crate::error::ErrorKind; #[test] fn test_standardized_rtt_open_tag() { @@ -425,7 +394,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["rtt-open"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("rtt-open"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -437,7 +406,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["rtt-read"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("rtt-read"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -449,7 +418,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["rtt-write"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("rtt-write"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -468,7 +437,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["n"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("network type"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -483,7 +452,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["T"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("relay type"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -495,7 +464,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["N"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("NIP"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -534,7 +503,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["R"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("requirement"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -546,7 +515,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["t"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("topic"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -574,7 +543,7 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["k"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("kind"))); + assert_eq!(err.kind(), ErrorKind::Missing); } #[test] @@ -586,6 +555,6 @@ mod tests { assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap()); let err = Nip66Tag::parse(["g"]).unwrap_err(); - assert_eq!(err, Error::Codec(TagCodecError::Missing("geohash"))); + assert_eq!(err.kind(), ErrorKind::Missing); } } diff --git a/crates/nostr/src/nips/nip70.rs b/crates/nostr/src/nips/nip70.rs index 9b41caa88..6d9cfdd16 100644 --- a/crates/nostr/src/nips/nip70.rs +++ b/crates/nostr/src/nips/nip70.rs @@ -8,32 +8,10 @@ use alloc::string::String; use alloc::vec; -use core::fmt; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; - -/// NIP-70 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::nips::util::{missing_tag_kind, unknown_tag}; /// Standardized NIP-70 tags /// @@ -58,12 +36,12 @@ impl TagCodec for Nip70Tag { let mut iter = tag.into_iter(); // Extract first value - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; // Match kind match kind.as_ref() { "-" => Ok(Self::Protected), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip73.rs b/crates/nostr/src/nips/nip73.rs index a9c0e43d8..8c73e6a4d 100644 --- a/crates/nostr/src/nips/nip73.rs +++ b/crates/nostr/src/nips/nip73.rs @@ -12,8 +12,11 @@ use alloc::vec::Vec; use core::fmt; use core::str::FromStr; -use super::util::{take_and_parse_from_str, take_and_parse_optional_from_str}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use super::util::{ + missing_tag_kind, take_and_parse_from_str, take_and_parse_optional_from_str, unknown_tag, +}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; use crate::types::Url; const HASHTAG: &str = "#"; @@ -27,44 +30,6 @@ const PAPER: &str = "doi:"; const BLOCKCHAIN_TX: &str = ":tx:"; const BLOCKCHAIN_ADDR: &str = ":address:"; -/// NIP73 error -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// URL error - Url(url::ParseError), - /// Codec error - Codec(TagCodecError), - /// Invalid external content - InvalidExternalContent, - /// Invalid NIP-73 kind - InvalidNip73Kind, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::InvalidExternalContent => f.write_str("invalid external content ID"), - Self::InvalidNip73Kind => f.write_str("Invalid NIP-73 kind"), - } - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// External Content ID #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ExternalContentId { @@ -225,7 +190,10 @@ impl FromStr for Nip73Kind { blockchain_addr.trim().replace(":address", ""), )) } - _ => Err(Error::InvalidNip73Kind), + _ => Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid NIP-73 kind", + )), } } } @@ -288,7 +256,10 @@ impl FromStr for ExternalContentId { return Ok(Self::Url(url)); } - Err(Error::InvalidExternalContent) + Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid external content", + )) } } @@ -347,7 +318,7 @@ impl TagCodec for Nip73Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { "i" => { @@ -355,10 +326,10 @@ impl TagCodec for Nip73Tag { Ok(Self::ExternalContent { content, hint }) } "k" => { - let kind: Nip73Kind = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "kind")?; + let kind: Nip73Kind = take_and_parse_from_str(&mut iter, "kind")?; Ok(Self::Kind(kind)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -377,9 +348,9 @@ where T: Iterator, S: AsRef, { - let content: ExternalContentId = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "content")?; - let hint: Option = take_and_parse_optional_from_str(&mut iter)?; + let content: ExternalContentId = take_and_parse_from_str(&mut iter, "content")?; + let hint: Option = + take_and_parse_optional_from_str(&mut iter).map_err(Error::malformed)?; Ok((content, hint)) } @@ -560,8 +531,8 @@ mod tests { #[test] fn test_invalid_content() { assert_eq!( - ExternalContentId::from_str("hello"), - Err(Error::InvalidExternalContent) + ExternalContentId::from_str("hello").unwrap_err().kind(), + ErrorKind::Invalid ); } diff --git a/crates/nostr/src/nips/nip7d.rs b/crates/nostr/src/nips/nip7d.rs index 1e9cadf0d..a4df4e275 100644 --- a/crates/nostr/src/nips/nip7d.rs +++ b/crates/nostr/src/nips/nip7d.rs @@ -8,36 +8,13 @@ use alloc::string::String; use alloc::vec; -use core::fmt; -use super::util::take_string; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use super::util::{missing_tag_kind, take_string, unknown_tag}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; const TITLE: &str = "title"; -/// NIP-7D error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Codec(err) => err.fmt(f), - } - } -} - -impl From for Error { - fn from(err: TagCodecError) -> Self { - Self::Codec(err) - } -} - /// Standardized NIP-7D tags /// /// @@ -57,11 +34,11 @@ impl TagCodec for Nip7DTag { { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { TITLE => Ok(Self::Title(take_string(&mut iter, "title")?)), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip88.rs b/crates/nostr/src/nips/nip88.rs index 8e9de7158..04d687ef5 100644 --- a/crates/nostr/src/nips/nip88.rs +++ b/crates/nostr/src/nips/nip88.rs @@ -9,15 +9,18 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::fmt; -use core::num::ParseIntError; use core::str::FromStr; -use super::util::{take_and_parse_from_str, take_relay_url, take_string, take_timestamp}; +use super::util::{ + missing_tag_kind, take_and_parse_from_str, take_relay_url, take_string, take_timestamp, + unknown_tag, +}; +use crate::error::{Error, ErrorKind}; use crate::event::{ - EventBuilderTemplate, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, + Event, EventBuilder, EventBuilderTemplate, EventId, Kind, Tag, TagCodec, + impl_tag_codec_conversions, }; -use crate::types::url; -use crate::{Event, EventBuilder, EventId, Kind, RelayUrl, Timestamp}; +use crate::{RelayUrl, Timestamp}; const ENDS_AT: &str = "endsAt"; const POLL_TYPE: &str = "polltype"; @@ -25,48 +28,9 @@ const POLL_OPTION: &str = "option"; const POLL_RESPONSE: &str = "response"; const RELAY: &str = "relay"; -/// NIP88 error -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Url error - Url(url::Error), - /// Parse Int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), - /// Unknown poll type - UnknownPollType, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::UnknownPollType => f.write_str("unknown poll type"), - } - } -} - -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } +#[inline] +fn unknown_poll_type() -> Error { + Error::with_static_message(ErrorKind::Unsupported, "unknown poll type") } /// Poll type @@ -101,7 +65,7 @@ impl FromStr for PollType { match poll_type { "singlechoice" => Ok(Self::SingleChoice), "multiplechoice" => Ok(Self::MultipleChoice), - _ => Err(Error::UnknownPollType), + _ => Err(unknown_poll_type()), } } } @@ -141,7 +105,7 @@ impl TagCodec for Nip88Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { POLL_OPTION => Ok(Self::PollOption(PollOption { @@ -150,19 +114,18 @@ impl TagCodec for Nip88Tag { })), POLL_RESPONSE => Ok(Self::PollResponse(take_string(&mut iter, "poll response")?)), POLL_TYPE => { - let poll_type: PollType = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "poll type")?; + let poll_type: PollType = take_and_parse_from_str(&mut iter, "poll type")?; Ok(Self::PollType(poll_type)) } RELAY => { - let relay: RelayUrl = take_relay_url::<_, _, Error>(&mut iter)?; + let relay: RelayUrl = take_relay_url(&mut iter)?; Ok(Self::Relay(relay)) } ENDS_AT => { - let timestamp: Timestamp = take_timestamp::<_, _, Error>(&mut iter)?; + let timestamp: Timestamp = take_timestamp(&mut iter)?; Ok(Self::PollEndsAt(timestamp)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -218,9 +181,15 @@ impl Poll { Ok(Nip88Tag::PollOption(option)) => options.push(option), Ok(Nip88Tag::Relay(url)) => relays.push(url), Ok(Nip88Tag::PollEndsAt(timestamp)) => ends_at = Some(timestamp), - Ok(Nip88Tag::PollResponse(..)) | Err(Error::Codec(TagCodecError::Unknown)) => (), - Err(Error::UnknownPollType) - | Err(Error::Codec(TagCodecError::Missing("poll type"))) => (), + Ok(Nip88Tag::PollResponse(..)) => (), + Err(e) + if matches!( + e.kind(), + ErrorKind::Invalid + | ErrorKind::Missing + | ErrorKind::Unsupported + | ErrorKind::Malformed + ) => {} Err(e) => return Err(e), } } diff --git a/crates/nostr/src/nips/nip89.rs b/crates/nostr/src/nips/nip89.rs index 0818ebece..278818d43 100644 --- a/crates/nostr/src/nips/nip89.rs +++ b/crates/nostr/src/nips/nip89.rs @@ -9,38 +9,15 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; use super::nip01::Coordinate; -use super::util::take_string; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use super::util::{missing_tag_kind, take_string, unknown_tag}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; use crate::types::url::RelayUrl; const CLIENT: &str = "client"; -/// NIP-89 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-89 tags /// /// @@ -64,14 +41,14 @@ impl TagCodec for Nip89Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { CLIENT => { let (name, address) = parse_client_tag(iter)?; Ok(Self::Client { name, address }) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nip90.rs b/crates/nostr/src/nips/nip90.rs index 390508e42..9dd0379a4 100644 --- a/crates/nostr/src/nips/nip90.rs +++ b/crates/nostr/src/nips/nip90.rs @@ -10,17 +10,16 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use core::fmt; -use core::num::ParseIntError; use core::str::FromStr; use super::util::{ - take_and_parse_from_str, take_and_parse_optional_relay_url, take_optional_string, take_string, + missing_tag_kind, missing_value, take_and_parse_from_str, take_and_parse_optional_relay_url, + take_optional_string, take_string, unknown_tag, }; use crate::PublicKey; -use crate::event::{ - self, Event, EventId, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, -}; -use crate::types::url::{self, RelayUrl}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Event, EventId, Tag, TagCodec, impl_tag_codec_conversions}; +use crate::types::url::RelayUrl; const INPUT: &str = "i"; const OUTPUT: &str = "output"; @@ -32,60 +31,14 @@ const AMOUNT: &str = "amount"; const STATUS: &str = "status"; const ENCRYPTED: &str = "encrypted"; -/// DVM Error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Event error - Event(event::Error), - /// Parse int error - ParseInt(ParseIntError), - /// Url error - Url(url::Error), - /// Codec error - Codec(TagCodecError), - /// Unknown input type - UnknownInputType, - /// Unknown status - UnknownStatus, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Event(e) => e.fmt(f), - Self::ParseInt(e) => fmt::Display::fmt(e, f), - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::UnknownInputType => f.write_str("Unknown input type"), - Self::UnknownStatus => f.write_str("Unknown status"), - } - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } +#[inline] +fn unknown_status() -> Error { + Error::with_static_message(ErrorKind::Unsupported, "unknown status") } -impl From for Error { - fn from(e: url::Error) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } +#[inline] +fn unknown_input_type() -> Error { + Error::with_static_message(ErrorKind::Unsupported, "unknown input type") } /// Data Vending Machine Status @@ -132,7 +85,7 @@ impl FromStr for DataVendingMachineStatus { "error" => Ok(Self::Error), "success" => Ok(Self::Success), "partial" => Ok(Self::Partial), - _ => Err(Error::UnknownStatus), + _ => Err(unknown_status()), } } } @@ -177,7 +130,7 @@ impl FromStr for JobInputType { "event" => Ok(Self::Event), "job" => Ok(Self::Job), "text" => Ok(Self::Text), - _ => Err(Error::UnknownInputType), + _ => Err(unknown_input_type()), } } } @@ -240,7 +193,7 @@ impl TagCodec for Nip90Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { INPUT => { @@ -258,12 +211,12 @@ impl TagCodec for Nip90Tag { value: take_string(&mut iter, "param value")?, }), BID => { - let bid: u64 = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "bid")?; + let bid: u64 = take_and_parse_from_str(&mut iter, "bid")?; Ok(Self::Bid(bid)) } RELAYS => Ok(Self::Relays(parse_relays_tag(iter)?)), REQUEST => { - let request: S = iter.next().ok_or(TagCodecError::Missing("request"))?; + let request: S = iter.next().ok_or(missing_value("request"))?; Ok(Self::Request(Event::from_json(request.as_ref())?)) } AMOUNT => { @@ -275,7 +228,7 @@ impl TagCodec for Nip90Tag { Ok(Self::Status { status, extra_info }) } ENCRYPTED => Ok(Self::Encrypted), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -350,8 +303,7 @@ where S: AsRef, { let data: String = take_string(&mut iter, "input data")?; - let input_type: JobInputType = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "input type")?; + let input_type: JobInputType = take_and_parse_from_str(&mut iter, "input type")?; let relay_hint: Option = take_and_parse_optional_relay_url(&mut iter)?; let marker: Option = take_optional_string(&mut iter); @@ -375,7 +327,7 @@ where T: Iterator, S: AsRef, { - let millisats: u64 = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "amount")?; + let millisats: u64 = take_and_parse_from_str(&mut iter, "amount")?; let bolt11: Option = take_optional_string(&mut iter); Ok((millisats, bolt11)) @@ -386,8 +338,7 @@ where T: Iterator, S: AsRef, { - let status: DataVendingMachineStatus = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "status")?; + let status: DataVendingMachineStatus = take_and_parse_from_str(&mut iter, "status")?; let extra_info: Option = take_optional_string(&mut iter); Ok((status, extra_info)) diff --git a/crates/nostr/src/nips/nip94.rs b/crates/nostr/src/nips/nip94.rs index 4b615f333..a82401b20 100644 --- a/crates/nostr/src/nips/nip94.rs +++ b/crates/nostr/src/nips/nip94.rs @@ -9,16 +9,13 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use core::num::ParseIntError; use core::str::FromStr; -use hashes::hex::HexToArrayError; use hashes::sha256::Hash as Sha256Hash; -use super::util::{take_and_parse_from_str, take_string}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::types::{image, url}; +use super::util::{missing_tag_kind, take_and_parse_from_str, take_string, unknown_tag}; +use crate::error::{Error, ErrorKind}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; use crate::{ImageDimensions, Url}; const URL: &str = "url"; @@ -37,86 +34,16 @@ const ALT: &str = "alt"; const FALLBACK: &str = "fallback"; const SERVICE: &str = "service"; -/// NIP-94 error -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Parse int error - ParseInt(ParseIntError), - /// Hex decoding error - Hex(HexToArrayError), - /// URL parse error - Url(url::ParseError), - /// Image error - Image(image::Error), - /// Codec error - Codec(TagCodecError), +fn missing_url() -> Error { + Error::with_static_message(ErrorKind::Missing, "missing url") } -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseInt(e) => e.fmt(f), - Self::Hex(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Image(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } +fn missing_mime_type() -> Error { + Error::with_static_message(ErrorKind::Missing, "missing mime type") } -impl From for Error { - fn from(e: HexToArrayError) -> Self { - Self::Hex(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: image::Error) -> Self { - Self::Image(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - -/// Potential errors returned when parsing tags into a [`FileMetadata`] struct. -#[derive(Debug, PartialEq, Eq)] -pub enum FileMetadataError { - /// The URL of the file is missing (no `url` tag) - MissingUrl, - /// The mime type of the file is missing (no `m` tag) - MissingMimeType, - /// The SHA256 hash of the file is missing (no `x` tag) - MissingSha, -} - -impl core::error::Error for FileMetadataError {} - -impl fmt::Display for FileMetadataError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::MissingUrl => f.write_str("missing url"), - Self::MissingMimeType => f.write_str("missing mime type"), - Self::MissingSha => f.write_str("missing file sha256"), - } - } +fn missing_sha() -> Error { + Error::with_static_message(ErrorKind::Missing, "missing file sha256") } /// Standardized NIP-94 tags @@ -175,31 +102,28 @@ impl TagCodec for Nip94Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { URL => { - let url: Url = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "URL")?; Ok(Self::Url(url)) } MIME_TYPE => Ok(Self::MimeType(take_string(&mut iter, "mime type")?)), SHA256 => { - let hash: Sha256Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "sha256")?; + let hash: Sha256Hash = take_and_parse_from_str(&mut iter, "sha256")?; Ok(Self::Sha256(hash)) } ORIGINAL_HASH => { - let hash: Sha256Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "original hash")?; + let hash: Sha256Hash = take_and_parse_from_str(&mut iter, "original hash")?; Ok(Self::OriginalHash(hash)) } SIZE => { - let size: usize = take_and_parse_from_str::<_, _, _, Error>(&mut iter, "size")?; + let size: usize = take_and_parse_from_str(&mut iter, "size")?; Ok(Self::Size(size)) } DIMENSIONS => { - let dim: ImageDimensions = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "dimensions")?; + let dim: ImageDimensions = take_and_parse_from_str(&mut iter, "dimensions")?; Ok(Self::Dim(dim)) } MAGNET => Ok(Self::Magnet(take_string(&mut iter, "magnet link")?)), @@ -216,12 +140,11 @@ impl TagCodec for Nip94Tag { SUMMARY => Ok(Self::Summary(take_string(&mut iter, "summary")?)), ALT => Ok(Self::Alt(take_string(&mut iter, "alt")?)), FALLBACK => { - let url: Url = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "fallback URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "fallback URL")?; Ok(Self::Fallback(url)) } SERVICE => Ok(Self::Service(take_string(&mut iter, "service name")?)), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -530,7 +453,7 @@ impl From for Vec { } impl TryFrom> for FileMetadata { - type Error = FileMetadataError; + type Error = Error; fn try_from(value: Vec) -> Result { let mut url: Option = None; @@ -633,9 +556,9 @@ impl TryFrom> for FileMetadata { } } - let url = url.ok_or(FileMetadataError::MissingUrl)?; - let mime_type = mime_type.ok_or(FileMetadataError::MissingMimeType)?; - let hash = hash.ok_or(FileMetadataError::MissingSha)?; + let url = url.ok_or_else(missing_url)?; + let mime_type = mime_type.ok_or_else(missing_mime_type)?; + let hash = hash.ok_or_else(missing_sha)?; let mut metadata = FileMetadata::new(url, mime_type, hash); @@ -707,9 +630,11 @@ where T: Iterator, S: AsRef, { - let url: Url = take_and_parse_from_str::<_, _, _, Error>(&mut iter, missing_error)?; + let url: Url = take_and_parse_from_str(&mut iter, missing_error)?; let hash: Option = match iter.next() { - Some(hash) if !hash.as_ref().is_empty() => Some(Sha256Hash::from_str(hash.as_ref())?), + Some(hash) if !hash.as_ref().is_empty() => { + Some(Sha256Hash::from_str(hash.as_ref()).map_err(Error::malformed_display)?) + } _ => None, }; @@ -810,7 +735,7 @@ mod tests { ]; let got = FileMetadata::try_from(tags).unwrap_err(); - assert_eq!(FileMetadataError::MissingUrl, got); + assert_eq!(got.kind(), ErrorKind::Missing); } #[test] @@ -828,7 +753,7 @@ mod tests { ]; let got = FileMetadata::try_from(tags).unwrap_err(); - assert_eq!(FileMetadataError::MissingMimeType, got); + assert_eq!(got.kind(), ErrorKind::Missing); } #[test] @@ -845,6 +770,6 @@ mod tests { ]; let got = FileMetadata::try_from(tags).unwrap_err(); - assert_eq!(FileMetadataError::MissingSha, got); + assert_eq!(got.kind(), ErrorKind::Missing); } } diff --git a/crates/nostr/src/nips/nip98.rs b/crates/nostr/src/nips/nip98.rs index b652f18ff..63ec8e489 100644 --- a/crates/nostr/src/nips/nip98.rs +++ b/crates/nostr/src/nips/nip98.rs @@ -9,6 +9,7 @@ //! //! +use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::fmt; @@ -18,26 +19,19 @@ use core::str::FromStr; use base64::engine::{Engine, general_purpose}; #[cfg(feature = "std")] use hashes::Hash; -use hashes::hex::HexToArrayError; use hashes::sha256::Hash as Sha256Hash; -use super::util::take_and_parse_from_str; +use super::util::{missing_tag_kind, take_and_parse_from_str, unknown_tag}; use crate::Url; -#[cfg(all(feature = "std", feature = "rand"))] -use crate::event::AsyncSignEvent; -#[cfg(all(feature = "std", feature = "rand"))] -use crate::event::EventBuilder; -#[cfg(all(feature = "std", feature = "rand"))] -use crate::event::FinalizeEventAsync; +use crate::error::{Error, ErrorKind}; #[cfg(feature = "std")] -use crate::event::{self, Event}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::event::Event; +#[cfg(all(feature = "std", feature = "rand"))] +use crate::event::{AsyncSignEvent, EventBuilder, FinalizeEventAsync}; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; #[cfg(all(feature = "std", feature = "rand"))] use crate::key::AsyncGetPublicKey; #[cfg(feature = "std")] -use crate::signer::SignerError; -use crate::types::url; -#[cfg(feature = "std")] use crate::{Kind, PublicKey, Timestamp}; #[cfg(feature = "std")] @@ -46,6 +40,40 @@ const ABSOLUTE_URL: &str = "u"; const METHOD: &str = "method"; const PAYLOAD: &str = "payload"; +#[inline] +fn unknown_method() -> Error { + Error::with_static_message(ErrorKind::Unsupported, "unknown method") +} + +#[inline] +fn missing_tag(tag: RequiredTags) -> Error { + Error::new(ErrorKind::Missing, format!("missing tag: {tag}")) +} + +#[inline] +#[cfg(feature = "std")] +fn authorization_header_missing() -> Error { + Error::with_static_message(ErrorKind::Missing, "authorization header missing") +} + +#[inline] +#[cfg(feature = "std")] +fn malformed_authorization_header() -> Error { + Error::with_static_message(ErrorKind::Malformed, "malformed authorization header") +} + +#[inline] +#[cfg(feature = "std")] +fn wrong_auth_header_kind() -> Error { + Error::with_static_message(ErrorKind::Invalid, "wrong auth header kind") +} + +#[inline] +#[cfg(feature = "std")] +fn payload_hash_mismatch() -> Error { + Error::with_static_message(ErrorKind::Invalid, "payload hash mismatch") +} + /// [`HttpData`] required tags #[derive(Debug, PartialEq, Eq)] pub enum RequiredTags { @@ -67,149 +95,6 @@ impl fmt::Display for RequiredTags { } } -/// NIP98 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Base64 error - #[cfg(feature = "std")] - Base64(base64::DecodeError), - /// Event error - #[cfg(feature = "std")] - Event(event::Error), - /// Event builder error - #[cfg(feature = "std")] - Signer(SignerError), - /// URL parse error - Url(url::ParseError), - /// Hex decoding error - Hex(HexToArrayError), - /// Codec error - Codec(TagCodecError), - /// Tag missing when parsing - MissingTag(RequiredTags), - /// Invalid HTTP Method - UnknownMethod, - /// Nostr authorization header missing - #[cfg(feature = "std")] - AuthorizationHeaderMissing, - /// Malformed authorization header - #[cfg(feature = "std")] - MalformedAuthorizationHeader, - /// Unexpected authorization header kind - #[cfg(feature = "std")] - WrongAuthHeaderKind, - /// Authorization doesn't match request - #[cfg(feature = "std")] - AuthorizationNotMatchRequest { - /// The authorized url - authorized_url: Box, - /// The authorized url - authorized_method: HttpMethod, - /// The request url - request_url: Box, - /// The request url - request_method: HttpMethod, - }, - /// Authorization is too old - #[cfg(feature = "std")] - AuthorizationTooOld { - /// Current timestamp - current: Timestamp, - /// Auth event created at - created_at: Timestamp, - }, - /// Payload hash doesn't match the body hash - #[cfg(feature = "std")] - PayloadHashMismatch, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - #[cfg(feature = "std")] - Self::Base64(e) => e.fmt(f), - #[cfg(feature = "std")] - Self::Event(e) => e.fmt(f), - #[cfg(feature = "std")] - Self::Signer(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - Self::Hex(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - Self::MissingTag(tag) => write!(f, "missing '{tag}' tag"), - Self::UnknownMethod => f.write_str("Unknown HTTP method"), - #[cfg(feature = "std")] - Self::AuthorizationHeaderMissing => f.write_str("nostr authorization header missing"), - #[cfg(feature = "std")] - Self::MalformedAuthorizationHeader => { - f.write_str("malformed nostr authorization header") - } - #[cfg(feature = "std")] - Self::WrongAuthHeaderKind => f.write_str("wrong nostr authorization header kind"), - #[cfg(feature = "std")] - Self::AuthorizationNotMatchRequest { - authorized_url, - authorized_method, - request_url, - request_method, - } => write!( - f, - "authorization doesn't match request: authorized_url={authorized_url}, authorized_method={authorized_method}, request_url={request_url}, request_method={request_method}" - ), - #[cfg(feature = "std")] - Self::AuthorizationTooOld { - current, - created_at, - } => write!( - f, - "authorization event is too old: current_time={current}, created_at={created_at}" - ), - #[cfg(feature = "std")] - Self::PayloadHashMismatch => f.write_str("payload hash doesn't match the body hash"), - } - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -#[cfg(feature = "std")] -impl From for Error { - fn from(e: base64::DecodeError) -> Self { - Self::Base64(e) - } -} - -#[cfg(feature = "std")] -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -#[cfg(feature = "std")] -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } -} - -impl From for Error { - fn from(e: HexToArrayError) -> Self { - Self::Hex(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// HTTP Method /// /// @@ -252,7 +137,7 @@ impl FromStr for HttpMethod { "POST" => Ok(Self::POST), "PUT" => Ok(Self::PUT), "PATCH" => Ok(Self::PATCH), - _ => Err(Error::UnknownMethod), + _ => Err(unknown_method()), } } } @@ -290,25 +175,22 @@ impl TagCodec for Nip98Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { ABSOLUTE_URL => { - let url: Url = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "absolute URL")?; + let url: Url = take_and_parse_from_str(&mut iter, "absolute URL")?; Ok(Self::AbsoluteURL(url)) } METHOD => { - let method: HttpMethod = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "method")?; + let method: HttpMethod = take_and_parse_from_str(&mut iter, "method")?; Ok(Self::Method(method)) } PAYLOAD => { - let payload: Sha256Hash = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "payload")?; + let payload: Sha256Hash = take_and_parse_from_str(&mut iter, "payload")?; Ok(Self::Payload(payload)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } @@ -393,8 +275,8 @@ impl TryFrom> for HttpData { } Ok(Self { - url: url.ok_or(Error::MissingTag(RequiredTags::AbsoluteURL))?, - method: method.ok_or(Error::MissingTag(RequiredTags::Method))?, + url: url.ok_or(missing_tag(RequiredTags::AbsoluteURL))?, + method: method.ok_or(missing_tag(RequiredTags::Method))?, payload, }) } @@ -424,24 +306,26 @@ pub fn verify_auth_header( // Original code at https://github.com/damus-io/notepush/blob/63c5f7e7236f7bfe09f665b5fb4a03b412284d13/src/nip98_auth.rs if auth_header.is_empty() { - return Err(Error::AuthorizationHeaderMissing); + return Err(authorization_header_missing()); } let (prefix, base64_encoded_event): (&str, &str) = auth_header .split_once(' ') - .ok_or(Error::MalformedAuthorizationHeader)?; + .ok_or(malformed_authorization_header())?; if prefix != AUTH_HEADER_PREFIX || base64_encoded_event.is_empty() { - return Err(Error::MalformedAuthorizationHeader); + return Err(malformed_authorization_header()); } // Decode event - let decoded_event_json: Vec = general_purpose::STANDARD.decode(base64_encoded_event)?; + let decoded_event_json: Vec = general_purpose::STANDARD + .decode(base64_encoded_event) + .map_err(Error::malformed)?; let event: Event = Event::from_json(decoded_event_json)?; // Check event kind if event.kind != Kind::HttpAuth { - return Err(Error::WrongAuthHeaderKind); + return Err(wrong_auth_header_kind()); } let http_data = HttpData::try_from(event.tags.iter().cloned().collect::>())?; @@ -449,29 +333,27 @@ pub fn verify_auth_header( let authorized_method: HttpMethod = http_data.method; if &authorized_url != url || authorized_method != method { - return Err(Error::AuthorizationNotMatchRequest { - authorized_url: Box::new(authorized_url.clone()), - authorized_method, - request_url: Box::new(url.clone()), - request_method: method, - }); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "authorization does not match request", + )); } let time_delta = TimeDelta::subtracting(current_time, event.created_at); if (time_delta.negative && time_delta.delta_abs_seconds > 30) || (!time_delta.negative && time_delta.delta_abs_seconds > 60) { - return Err(Error::AuthorizationTooOld { - current: current_time, - created_at: event.created_at, - }); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "authorization is too old", + )); } if let Some(body_data) = body { // Get payload hash let payload: Sha256Hash = match http_data.payload { Some(p) => p, - None => return Err(Error::MissingTag(RequiredTags::Payload)), + None => return Err(missing_tag(RequiredTags::Payload)), }; // Hash body data @@ -479,7 +361,7 @@ pub fn verify_auth_header( // Check if payload and body hash matches if payload != body_hash { - return Err(Error::PayloadHashMismatch); + return Err(payload_hash_mismatch()); } } @@ -568,8 +450,10 @@ mod tests { fn empty_auth_header() { let url = Url::parse("https://example.com/").unwrap(); assert_eq!( - verify_auth_header("", &url, HttpMethod::GET, Timestamp::now(), None).unwrap_err(), - Error::AuthorizationHeaderMissing + verify_auth_header("", &url, HttpMethod::GET, Timestamp::now(), None) + .unwrap_err() + .kind(), + ErrorKind::Missing ); } @@ -578,21 +462,25 @@ mod tests { let url = Url::parse("https://example.com/").unwrap(); let now = Timestamp::now(); assert_eq!( - verify_auth_header("Test Nostr", &url, HttpMethod::GET, now, None).unwrap_err(), - Error::MalformedAuthorizationHeader + verify_auth_header("Test Nostr", &url, HttpMethod::GET, now, None) + .unwrap_err() + .kind(), + ErrorKind::Malformed ); assert_eq!( - verify_auth_header("Nostr", &url, HttpMethod::GET, now, None).unwrap_err(), - Error::MalformedAuthorizationHeader + verify_auth_header("Nostr", &url, HttpMethod::GET, now, None) + .unwrap_err() + .kind(), + ErrorKind::Malformed ); - assert_eq!(verify_auth_header("nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1IiwiaHR0cHM6Ly9hcGkuc25vcnQuc29jaWFsL2FwaS92MS9uNXNwL2xpc3QiXSxbIm1ldGhvZCIsIkdFVCJdXSwic2lnIjoiNWVkOWQ4ZWM5NThiYzg1NGY5OTdiZGMyNGFjMzM3ZDAwNWFmMzcyMzI0NzQ3ZWZlNGEwMGUyNGY0YzMwNDM3ZmY0ZGQ4MzA4Njg0YmVkNDY3ZDlkNmJlM2U1YTUxN2JiNDNiMTczMmNjN2QzMzk0OWEzYWFmODY3MDVjMjIxODQifQ==", &url, HttpMethod::GET, now, None).unwrap_err(), Error::MalformedAuthorizationHeader); + assert_eq!(verify_auth_header("nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1IiwiaHR0cHM6Ly9hcGkuc25vcnQuc29jaWFsL2FwaS92MS9uNXNwL2xpc3QiXSxbIm1ldGhvZCIsIkdFVCJdXSwic2lnIjoiNWVkOWQ4ZWM5NThiYzg1NGY5OTdiZGMyNGFjMzM3ZDAwNWFmMzcyMzI0NzQ3ZWZlNGEwMGUyNGY0YzMwNDM3ZmY0ZGQ4MzA4Njg0YmVkNDY3ZDlkNmJlM2U1YTUxN2JiNDNiMTczMmNjN2QzMzk0OWEzYWFmODY3MDVjMjIxODQifQ==", &url, HttpMethod::GET, now, None).unwrap_err().kind(), ErrorKind::Malformed); } #[test] fn auth_header_wrong_kind() { let url = Url::parse("https://example.com/").unwrap(); let now = Timestamp::now(); - assert_eq!(verify_auth_header("Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjEsImNyZWF0ZWRfYXQiOjE2ODIzMjc4NTIsInRhZ3MiOltbInUiLCJodHRwczovL2FwaS5zbm9ydC5zb2NpYWwvYXBpL3YxL241c3AvbGlzdCJdLFsibWV0aG9kIiwiR0VUIl1dLCJzaWciOiI1ZWQ5ZDhlYzk1OGJjODU0Zjk5N2JkYzI0YWMzMzdkMDA1YWYzNzIzMjQ3NDdlZmU0YTAwZTI0ZjRjMzA0MzdmZjRkZDgzMDg2ODRiZWQ0NjdkOWQ2YmUzZTVhNTE3YmI0M2IxNzMyY2M3ZDMzOTQ5YTNhYWY4NjcwNWMyMjE4NCJ9", &url, HttpMethod::GET, now, None).unwrap_err(), Error::WrongAuthHeaderKind); + assert_eq!(verify_auth_header("Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjEsImNyZWF0ZWRfYXQiOjE2ODIzMjc4NTIsInRhZ3MiOltbInUiLCJodHRwczovL2FwaS5zbm9ydC5zb2NpYWwvYXBpL3YxL241c3AvbGlzdCJdLFsibWV0aG9kIiwiR0VUIl1dLCJzaWciOiI1ZWQ5ZDhlYzk1OGJjODU0Zjk5N2JkYzI0YWMzMzdkMDA1YWYzNzIzMjQ3NDdlZmU0YTAwZTI0ZjRjMzA0MzdmZjRkZDgzMDg2ODRiZWQ0NjdkOWQ2YmUzZTVhNTE3YmI0M2IxNzMyY2M3ZDMzOTQ5YTNhYWY4NjcwNWMyMjE4NCJ9", &url, HttpMethod::GET, now, None).unwrap_err().kind(), ErrorKind::Invalid); } #[test] @@ -600,12 +488,10 @@ mod tests { let url = Url::parse("https://example.com/").unwrap(); // Expected url: https://api.snort.social/api/v1/n5sp/list let now = Timestamp::now(); let method = HttpMethod::POST; - assert_eq!(verify_auth_header("Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1IiwiaHR0cHM6Ly9hcGkuc25vcnQuc29jaWFsL2FwaS92MS9uNXNwL2xpc3QiXSxbIm1ldGhvZCIsIkdFVCJdXSwic2lnIjoiNWVkOWQ4ZWM5NThiYzg1NGY5OTdiZGMyNGFjMzM3ZDAwNWFmMzcyMzI0NzQ3ZWZlNGEwMGUyNGY0YzMwNDM3ZmY0ZGQ4MzA4Njg0YmVkNDY3ZDlkNmJlM2U1YTUxN2JiNDNiMTczMmNjN2QzMzk0OWEzYWFmODY3MDVjMjIxODQifQ==", &url, method, now, None).unwrap_err(), Error::AuthorizationNotMatchRequest { - authorized_url: Box::new(Url::parse("https://api.snort.social/api/v1/n5sp/list").unwrap()), - authorized_method: HttpMethod::GET, - request_url: Box::new(url), - request_method: HttpMethod::POST, - }); + assert_eq!( + verify_auth_header("Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1IiwiaHR0cHM6Ly9hcGkuc25vcnQuc29jaWFsL2FwaS92MS9uNXNwL2xpc3QiXSxbIm1ldGhvZCIsIkdFVCJdXSwic2lnIjoiNWVkOWQ4ZWM5NThiYzg1NGY5OTdiZGMyNGFjMzM3ZDAwNWFmMzcyMzI0NzQ3ZWZlNGEwMGUyNGY0YzMwNDM3ZmY0ZGQ4MzA4Njg0YmVkNDY3ZDlkNmJlM2U1YTUxN2JiNDNiMTczMmNjN2QzMzk0OWEzYWFmODY3MDVjMjIxODQifQ==", &url, method, now, None).unwrap_err().kind(), + ErrorKind::Invalid + ); } #[test] @@ -613,10 +499,10 @@ mod tests { let url = Url::parse("https://api.snort.social/api/v1/n5sp/list").unwrap(); let method = HttpMethod::GET; let now = Timestamp::from_secs(1777777777); - assert_eq!(verify_auth_header("Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1IiwiaHR0cHM6Ly9hcGkuc25vcnQuc29jaWFsL2FwaS92MS9uNXNwL2xpc3QiXSxbIm1ldGhvZCIsIkdFVCJdXSwic2lnIjoiNWVkOWQ4ZWM5NThiYzg1NGY5OTdiZGMyNGFjMzM3ZDAwNWFmMzcyMzI0NzQ3ZWZlNGEwMGUyNGY0YzMwNDM3ZmY0ZGQ4MzA4Njg0YmVkNDY3ZDlkNmJlM2U1YTUxN2JiNDNiMTczMmNjN2QzMzk0OWEzYWFmODY3MDVjMjIxODQifQ==", &url, method, now, None).unwrap_err(), Error::AuthorizationTooOld { - current: now, - created_at: Timestamp::from_secs(1682327852), - }); + assert_eq!( + verify_auth_header("Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1IiwiaHR0cHM6Ly9hcGkuc25vcnQuc29jaWFsL2FwaS92MS9uNXNwL2xpc3QiXSxbIm1ldGhvZCIsIkdFVCJdXSwic2lnIjoiNWVkOWQ4ZWM5NThiYzg1NGY5OTdiZGMyNGFjMzM3ZDAwNWFmMzcyMzI0NzQ3ZWZlNGEwMGUyNGY0YzMwNDM3ZmY0ZGQ4MzA4Njg0YmVkNDY3ZDlkNmJlM2U1YTUxN2JiNDNiMTczMmNjN2QzMzk0OWEzYWFmODY3MDVjMjIxODQifQ==", &url, method, now, None).unwrap_err().kind(), + ErrorKind::Invalid + ); } #[test] diff --git a/crates/nostr/src/nips/nipb0.rs b/crates/nostr/src/nips/nipb0.rs index 88454e414..d241d67e9 100644 --- a/crates/nostr/src/nips/nipb0.rs +++ b/crates/nostr/src/nips/nipb0.rs @@ -9,52 +9,19 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use core::num::ParseIntError; -use super::util::{take_string, take_timestamp}; +use super::util::{missing_tag_kind, take_string, take_timestamp, unknown_tag}; +use crate::Timestamp; +use crate::error::Error; use crate::event::{ - EventBuilderTemplate, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, + EventBuilder, EventBuilderTemplate, Kind, Tag, TagCodec, impl_tag_codec_conversions, }; -use crate::{EventBuilder, Kind, Timestamp}; const URL: &str = "d"; const PUBLISHED_AT: &str = "published_at"; const TITLE: &str = "title"; const HASHTAG: &str = "t"; -/// NIP-B0 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Parse Int error - ParseInt(ParseIntError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseInt(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseInt(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-B0 tags /// /// @@ -80,19 +47,19 @@ impl TagCodec for NipB0Tag { { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { URL => Ok(Self::Url(take_string(&mut iter, "URL")?)), PUBLISHED_AT => { - let timestamp: Timestamp = take_timestamp::<_, _, Error>(&mut iter)?; + let timestamp: Timestamp = take_timestamp(&mut iter)?; Ok(Self::PublishedAt(timestamp)) } TITLE => Ok(Self::Title(take_string(&mut iter, "title")?)), HASHTAG => Ok(Self::Hashtag( take_string(&mut iter, "hashtag")?.to_lowercase(), )), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nipb7.rs b/crates/nostr/src/nips/nipb7.rs index 784a58e21..180d8be7f 100644 --- a/crates/nostr/src/nips/nipb7.rs +++ b/crates/nostr/src/nips/nipb7.rs @@ -8,46 +8,14 @@ use alloc::string::{String, ToString}; use alloc::vec; -use core::fmt; -use super::util::take_and_parse_from_str; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; -use crate::types::url::{self, Url}; +use super::util::{missing_tag_kind, take_and_parse_from_str, unknown_tag}; +use crate::error::Error; +use crate::event::{Tag, TagCodec, impl_tag_codec_conversions}; +use crate::types::url::Url; const SERVER: &str = "server"; -/// NIP-B7 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Url error - Url(url::ParseError), - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::Codec(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: TagCodecError) -> Self { - Self::Codec(e) - } -} - /// Standardized NIP-B7 tags /// /// @@ -66,15 +34,14 @@ impl TagCodec for NipB7Tag { S: AsRef, { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { SERVER => { - let server_url: Url = - take_and_parse_from_str::<_, _, _, Error>(&mut iter, "server URL")?; + let server_url: Url = take_and_parse_from_str(&mut iter, "server URL")?; Ok(Self::Server(server_url)) } - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/nipc0.rs b/crates/nostr/src/nips/nipc0.rs index 5df6d0257..353f2d474 100644 --- a/crates/nostr/src/nips/nipc0.rs +++ b/crates/nostr/src/nips/nipc0.rs @@ -9,13 +9,12 @@ use alloc::string::String; use alloc::vec; use alloc::vec::Vec; -use core::fmt; -use super::util::take_string; +use super::util::{missing_tag_kind, take_string, unknown_tag}; +use crate::error::Error; use crate::event::{ - EventBuilderTemplate, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, + EventBuilder, EventBuilderTemplate, Kind, Tag, TagCodec, impl_tag_codec_conversions, }; -use crate::{EventBuilder, Kind}; const LANGUAGE: &str = "l"; const NAME: &str = "name"; @@ -26,29 +25,6 @@ const LICENSE: &str = "license"; const DEPENDENCY: &str = "dep"; const REPOSITORY: &str = "repo"; -/// NIP-C0 error -#[derive(Debug, PartialEq)] -pub enum Error { - /// Codec error - Codec(TagCodecError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Codec(err) => err.fmt(f), - } - } -} - -impl From for Error { - fn from(err: TagCodecError) -> Self { - Self::Codec(err) - } -} - /// Standardized NIP-C0 tags /// /// @@ -82,7 +58,7 @@ impl TagCodec for NipC0Tag { { let mut iter = tag.into_iter(); - let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?; + let kind: S = iter.next().ok_or(missing_tag_kind())?; match kind.as_ref() { LANGUAGE => Ok(Self::Language( @@ -95,7 +71,7 @@ impl TagCodec for NipC0Tag { LICENSE => Ok(Self::License(take_string(&mut iter, "license")?)), DEPENDENCY => Ok(Self::Dependency(take_string(&mut iter, "dependency")?)), REPOSITORY => Ok(Self::Repository(take_string(&mut iter, "repository")?)), - _ => Err(TagCodecError::Unknown.into()), + _ => Err(unknown_tag()), } } diff --git a/crates/nostr/src/nips/util.rs b/crates/nostr/src/nips/util.rs index 56346f6c3..9096cd5b6 100644 --- a/crates/nostr/src/nips/util.rs +++ b/crates/nostr/src/nips/util.rs @@ -1,17 +1,57 @@ use alloc::string::{String, ToString}; -use core::num::ParseIntError; use core::str::FromStr; -use super::nip01::{self, Coordinate}; -use crate::event::{self, EventId, TagCodecError}; -use crate::key::{self, PublicKey}; +use super::nip01::Coordinate; +use crate::error::{Error, ErrorKind}; +use crate::event::EventId; +use crate::key::PublicKey; use crate::types::time::Timestamp; -use crate::types::url::{self, RelayUrl}; +use crate::types::url::RelayUrl; + +#[inline] +pub(super) fn missing_tag_kind() -> Error { + Error::with_static_message(ErrorKind::Missing, "missing tag kind") +} + +#[inline] +pub(super) fn invalid_value(value: &'static str) -> Error { + Error::with_static_message(ErrorKind::Invalid, value) +} + +#[inline] +pub(super) fn unknown_tag() -> Error { + Error::with_static_message(ErrorKind::Malformed, "unknown tag") +} + +#[inline] +pub(super) fn missing_value(missing_error: &'static str) -> Error { + Error::with_static_message(ErrorKind::Missing, missing_error) +} + +#[inline] +pub(super) fn invalid_uri() -> Error { + Error::with_static_message(ErrorKind::Invalid, "invalid URI") +} + +#[inline] +#[cfg(any(feature = "nip46", feature = "nip47"))] +pub(super) fn unexpected_result() -> Error { + Error::with_static_message(ErrorKind::Invalid, "unexpected result") +} + +#[inline] +#[cfg(any(feature = "nip46", feature = "nip47"))] +pub(super) fn unsupported_method(method: &str) -> Error { + Error::new( + ErrorKind::Unsupported, + format!("unsupported method: {method}"), + ) +} #[inline] pub(super) fn take_and_parse_optional_public_key( iter: &mut I, -) -> Result, key::Error> +) -> Result, Error> where I: Iterator, S: AsRef, @@ -22,7 +62,7 @@ where #[inline] pub(super) fn take_and_parse_optional_coordinate( iter: &mut I, -) -> Result, nip01::Error> +) -> Result, Error> where I: Iterator, S: AsRef, @@ -33,7 +73,7 @@ where #[inline] pub(super) fn take_and_parse_optional_relay_url( iter: &mut I, -) -> Result, url::Error> +) -> Result, Error> where I: Iterator, S: AsRef, @@ -97,82 +137,74 @@ where } /// Take a string -pub(super) fn take_string( - iter: &mut I, - missing_error: &'static str, -) -> Result +pub(super) fn take_string(iter: &mut I, missing_error: &'static str) -> Result where I: Iterator, S: AsRef, { - let value: S = iter.next().ok_or(TagCodecError::Missing(missing_error))?; + let value: S = iter.next().ok_or(missing_value(missing_error))?; Ok(value.as_ref().to_string()) } -pub(super) fn take_public_key(iter: &mut I) -> Result +pub(super) fn take_public_key(iter: &mut I) -> Result where I: Iterator, S: AsRef, - E: From + From, { - let public_key: S = iter.next().ok_or(TagCodecError::Missing("public key"))?; + let public_key: S = iter.next().ok_or(missing_value("public key"))?; let public_key: PublicKey = PublicKey::from_hex(public_key.as_ref())?; Ok(public_key) } -pub(super) fn take_coordinate(iter: &mut I) -> Result +pub(super) fn take_coordinate(iter: &mut I) -> Result where I: Iterator, S: AsRef, - E: From + From, { - let coordinate: S = iter.next().ok_or(TagCodecError::Missing("coordinate"))?; + let coordinate: S = iter.next().ok_or(missing_value("coordinate"))?; let coordinate: Coordinate = Coordinate::from_kpi_format(coordinate.as_ref())?; Ok(coordinate) } -pub(super) fn take_event_id(iter: &mut I) -> Result +pub(super) fn take_event_id(iter: &mut I) -> Result where I: Iterator, S: AsRef, - E: From + From, { - let event_id: S = iter.next().ok_or(TagCodecError::Missing("event ID"))?; + let event_id: S = iter.next().ok_or(missing_value("event ID"))?; let event_id: EventId = EventId::from_hex(event_id.as_ref())?; Ok(event_id) } #[inline] -pub(super) fn take_relay_url(iter: &mut T) -> Result +pub(super) fn take_relay_url(iter: &mut T) -> Result where T: Iterator, S: AsRef, - E: From + From, { take_and_parse_from_str(iter, "relay URL") } #[inline] -pub(super) fn take_timestamp(iter: &mut T) -> Result +pub(super) fn take_timestamp(iter: &mut T) -> Result where T: Iterator, S: AsRef, - E: From + From, { take_and_parse_from_str(iter, "timestamp") } -pub(super) fn take_and_parse_from_str( +pub(super) fn take_and_parse_from_str( iter: &mut T, missing_error: &'static str, -) -> Result +) -> Result where T: Iterator, S: AsRef, O: FromStr, - E: From + From, + O::Err: core::fmt::Display, { - let value: S = iter.next().ok_or(TagCodecError::Missing(missing_error))?; - let value: O = O::from_str(value.as_ref())?; + let value: S = iter.next().ok_or_else(|| missing_value(missing_error))?; + let value: O = O::from_str(value.as_ref()).map_err(Error::malformed_display)?; Ok(value) } diff --git a/crates/nostr/src/parser.rs b/crates/nostr/src/parser.rs index 55925f825..44adf4723 100644 --- a/crates/nostr/src/parser.rs +++ b/crates/nostr/src/parser.rs @@ -4,7 +4,6 @@ //! Nostr parser -use core::fmt; use core::iter::Skip; use core::str::{Chars, FromStr}; @@ -12,7 +11,7 @@ use bech32::Fe32; use crate::nips::nip19::Nip19Prefix; use crate::nips::nip21::{self, Nip21}; -use crate::types::url::{ParseError, Url}; +use crate::types::url::Url; const BECH32_SEPARATOR: u8 = b'1'; const URL_SCHEME_SEPARATOR: &[u8] = b"://"; @@ -21,38 +20,6 @@ const LINE_BREAK_BYTE: u8 = b'\n'; const LINE_BREAK: &str = "\n"; const WHITESPACE: &str = " "; -/// Parser error -#[derive(Debug, PartialEq)] -pub enum Error { - /// NIP21 error - NIP21(nip21::Error), - /// Url error - Url(ParseError), -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NIP21(e) => e.fmt(f), - Self::Url(e) => e.fmt(f), - } - } -} - -impl From for Error { - fn from(e: nip21::Error) -> Self { - Self::NIP21(e) - } -} - -impl From for Error { - fn from(e: ParseError) -> Self { - Self::Url(e) - } -} - /// Nostr parsed token #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Token<'a> { diff --git a/crates/nostr/src/prelude.rs b/crates/nostr/src/prelude.rs index e3f2925ad..655c00af6 100644 --- a/crates/nostr/src/prelude.rs +++ b/crates/nostr/src/prelude.rs @@ -17,12 +17,11 @@ pub use rand; pub use secp256k1::schnorr::Signature; pub use serde_json::Value; -// Internal modules +pub use crate::error::{self, Error, ErrorKind}; pub use crate::event::{self, *}; pub use crate::filter::{self, *}; pub use crate::key::{self, *}; pub use crate::message::{self, *}; -// NIPs pub use crate::nips::nip01::{self, *}; pub use crate::nips::nip02::{self, *}; pub use crate::nips::nip04::{self, *}; @@ -83,8 +82,5 @@ pub use crate::nips::nipb0::{self, *}; pub use crate::nips::nipb7::{self, *}; pub use crate::nips::nipc0::{self, *}; pub use crate::parser::{self, *}; -pub use crate::signer::{self, *}; pub use crate::types::*; pub use crate::util::{self, *}; -#[cfg(feature = "std")] -pub use crate::{Result, SECP256K1}; diff --git a/crates/nostr/src/signer.rs b/crates/nostr/src/signer.rs deleted file mode 100644 index 5ce7880fb..000000000 --- a/crates/nostr/src/signer.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2022-2023 Yuki Kishimoto -// Copyright (c) 2023-2025 Rust Nostr Developers -// Distributed under the MIT software license - -//! Nostr Signer - -use alloc::string::{String, ToString}; -use core::fmt::{self, Debug, Display}; - -/// Nostr Signer error -#[derive(Debug, PartialEq, Eq)] -pub struct SignerError(String); - -impl core::error::Error for SignerError {} - -impl fmt::Display for SignerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.0.as_str()) - } -} - -impl SignerError { - /// New signer error - #[inline] - pub fn backend(error: E) -> Self - where - E: Display, - { - Self(error.to_string()) - } -} - -impl From for SignerError -where - S: Into, -{ - fn from(error: S) -> Self { - Self(error.into()) - } -} diff --git a/crates/nostr/src/types/image.rs b/crates/nostr/src/types/image.rs index 6a3283f63..0993633a4 100644 --- a/crates/nostr/src/types/image.rs +++ b/crates/nostr/src/types/image.rs @@ -5,34 +5,9 @@ //! Image use core::fmt; -use core::num::ParseIntError; use core::str::{FromStr, Split}; -/// Image error -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Impossible to parse integer - ParseIntError(ParseIntError), - /// Invalid Image Dimensions - InvalidDimensions, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseIntError(e) => e.fmt(f), - Self::InvalidDimensions => f.write_str("Invalid dimensions"), - } - } -} - -impl From for Error { - fn from(e: ParseIntError) -> Self { - Self::ParseIntError(e) - } -} +use crate::error::{Error, ErrorKind}; /// Simple struct to hold `width` x `height`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -63,9 +38,15 @@ impl FromStr for ImageDimensions { fn from_str(s: &str) -> Result { let mut spitted: Split = s.split('x'); if let (Some(width), Some(height)) = (spitted.next(), spitted.next()) { - Ok(Self::new(width.parse()?, height.parse()?)) + Ok(Self::new( + width.parse().map_err(Error::malformed)?, + height.parse().map_err(Error::malformed)?, + )) } else { - Err(Error::InvalidDimensions) + Err(Error::with_static_message( + ErrorKind::Invalid, + "invalid dimensions", + )) } } } diff --git a/crates/nostr/src/types/url.rs b/crates/nostr/src/types/url.rs index f9613a3b7..0ccb69108 100644 --- a/crates/nostr/src/types/url.rs +++ b/crates/nostr/src/types/url.rs @@ -15,34 +15,7 @@ use core::str::FromStr; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub use url::*; -/// Relay URL error -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Url parse error - Url(ParseError), - /// Unsupported URL scheme - UnsupportedScheme, - /// Multiple scheme separators - MultipleSchemeSeparators, -} - -impl core::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Url(e) => e.fmt(f), - Self::UnsupportedScheme => f.write_str("Unsupported scheme"), - Self::MultipleSchemeSeparators => f.write_str("Multiple scheme separators"), - } - } -} - -impl From for Error { - fn from(e: ParseError) -> Self { - Self::Url(e) - } -} +use crate::error::{Error, ErrorKind}; /// Relay URL scheme #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -60,7 +33,10 @@ impl RelayUrlScheme { match scheme { "ws" => Ok(Self::Ws), "wss" => Ok(Self::Wss), - _ => Err(Error::UnsupportedScheme), + _ => Err(Error::with_static_message( + ErrorKind::Unsupported, + "unsupported URL scheme", + )), } } @@ -127,14 +103,17 @@ impl RelayUrl { pub fn parse(url: &str) -> Result { // Check that "://" appears only once in the URL if url.matches("://").count() > 1 { - return Err(Error::MultipleSchemeSeparators); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "multiple scheme separators", + )); } // Check if it has a trailing slash let has_trailing_slash: bool = url.ends_with('/'); // Parse URL - let url: Url = Url::parse(url)?; + let url: Url = Url::parse(url).map_err(Error::malformed)?; // Parse scheme let scheme: RelayUrlScheme = RelayUrlScheme::parse(url.scheme())?; @@ -220,7 +199,7 @@ impl RelayUrl { /// # Examples /// /// ``` - /// use nostr::types::url::{Error, RelayUrl}; + /// use nostr::types::url::RelayUrl; /// /// let url = RelayUrl::parse("wss://127.0.0.1:7777").unwrap(); /// assert_eq!(url.domain(), None); @@ -392,12 +371,12 @@ mod tests { // Invalid assert_eq!( - RelayUrlScheme::parse("http").unwrap_err(), - Error::UnsupportedScheme + RelayUrlScheme::parse("http").unwrap_err().kind(), + ErrorKind::Unsupported ); assert_eq!( - RelayUrlScheme::parse("https").unwrap_err(), - Error::UnsupportedScheme + RelayUrlScheme::parse("https").unwrap_err().kind(), + ErrorKind::Unsupported ); } @@ -412,24 +391,30 @@ mod tests { #[test] fn test_relay_url_invalid() { assert_eq!( - RelayUrl::parse("https://relay.damus.io").unwrap_err(), - Error::UnsupportedScheme + RelayUrl::parse("https://relay.damus.io") + .unwrap_err() + .kind(), + ErrorKind::Unsupported ); assert_eq!( - RelayUrl::parse("ftp://relay.damus.io").unwrap_err(), - Error::UnsupportedScheme + RelayUrl::parse("ftp://relay.damus.io").unwrap_err().kind(), + ErrorKind::Unsupported ); assert_eq!( - RelayUrl::parse("wss://relay.damus.io,ws://127.0.0.1:7777").unwrap_err(), - Error::MultipleSchemeSeparators + RelayUrl::parse("wss://relay.damus.io,ws://127.0.0.1:7777") + .unwrap_err() + .kind(), + ErrorKind::Invalid ); assert_eq!( - RelayUrl::parse("wss://relay.damus.iowss://127.0.0.1:8888").unwrap_err(), - Error::MultipleSchemeSeparators + RelayUrl::parse("wss://relay.damus.iowss://127.0.0.1:8888") + .unwrap_err() + .kind(), + ErrorKind::Invalid ); assert_eq!( - RelayUrl::parse("wss://").unwrap_err(), - Error::Url(ParseError::EmptyHost) + RelayUrl::parse("wss://").unwrap_err().kind(), + ErrorKind::Malformed ); } diff --git a/crates/nostr/src/util/json.rs b/crates/nostr/src/util/json.rs index 148c89675..ebc838bf4 100644 --- a/crates/nostr/src/util/json.rs +++ b/crates/nostr/src/util/json.rs @@ -1,20 +1,40 @@ +use serde::de; +use serde_json::Value; + +use crate::error::Error; + +#[inline] +pub(crate) fn parse_json<'a, T, V>(bytes: &'a V) -> Result +where + T: de::Deserialize<'a>, + V: AsRef<[u8]> + 'a, +{ + Ok(serde_json::from_slice(bytes.as_ref())?) +} + +#[inline] +pub(crate) fn parse_json_from_value(value: Value) -> Result +where + T: de::DeserializeOwned, +{ + Ok(serde_json::from_value(value)?) +} + macro_rules! impl_json_methods { - ($ty:ty, $err:ty) => { + ($ty:ty) => { impl_json_methods! { $ty, - $err, from_json(json) { - Ok(crate::serde_json::from_slice(json.as_ref())?) + Ok(serde_json::from_slice(json.as_ref())?) } } }; - ($ty:ty, $err:ty, from_json($json:ident) $from_json:block) => { + ($ty:ty, from_json($json:ident) $from_json:block) => { impl $ty { /// Deserialize from JSON. #[inline] - #[allow(clippy::needless_question_mark)] - pub fn from_json($json: T) -> Result + pub fn from_json($json: T) -> Result where T: AsRef<[u8]>, { @@ -31,8 +51,7 @@ macro_rules! impl_json_methods { /// Serialize as JSON string. #[inline] - #[allow(clippy::needless_question_mark)] - pub fn try_as_json(&self) -> Result { + pub fn try_as_json(&self) -> Result { Ok(serde_json::to_string(self)?) } @@ -46,8 +65,7 @@ macro_rules! impl_json_methods { /// Serialize as pretty JSON string. #[inline] - #[allow(clippy::needless_question_mark)] - pub fn try_as_pretty_json(&self) -> Result { + pub fn try_as_pretty_json(&self) -> Result { Ok(serde_json::to_string_pretty(self)?) } } diff --git a/crates/nostr/src/util/mod.rs b/crates/nostr/src/util/mod.rs index b6a2fda13..9a1f4d453 100644 --- a/crates/nostr/src/util/mod.rs +++ b/crates/nostr/src/util/mod.rs @@ -27,8 +27,9 @@ use secp256k1::{Parity, PublicKey as NormalizedPublicKey, XOnlyPublicKey, ecdh}; pub mod hkdf; mod json; -pub(crate) use self::json::impl_json_methods; -use crate::{PublicKey, SecretKey, key}; +pub(crate) use self::json::{impl_json_methods, parse_json, parse_json_from_value}; +use crate::error::Error; +use crate::key::{PublicKey, SecretKey}; /// A boxed future #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] @@ -73,7 +74,7 @@ where pub fn generate_shared_key( secret_key: &SecretKey, public_key: &PublicKey, -) -> Result<[u8; 32], key::Error> { +) -> Result<[u8; 32], Error> { let pk: XOnlyPublicKey = public_key.xonly()?; let public_key_normalized: NormalizedPublicKey = NormalizedPublicKey::from_x_only_public_key(pk, Parity::Even); diff --git a/crates/nwc/examples/notifications.rs b/crates/nwc/examples/notifications.rs index b5c36b310..4653ea483 100644 --- a/crates/nwc/examples/notifications.rs +++ b/crates/nwc/examples/notifications.rs @@ -9,7 +9,7 @@ use nostr::nips::nip47::{NotificationType, PaymentNotification}; use nwc::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let log_level = env::var("RUST_LOG") .unwrap_or_else(|_| "info,nwc=debug,nostr_relay_pool=debug".to_string()); diff --git a/crates/nwc/examples/nwc.rs b/crates/nwc/examples/nwc.rs index 92e6a5e5b..65abe5c18 100644 --- a/crates/nwc/examples/nwc.rs +++ b/crates/nwc/examples/nwc.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use nwc::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let mut nwc_uri_string = String::new(); diff --git a/crates/nwc/src/error.rs b/crates/nwc/src/error.rs index 2dab186bf..a5c9ae9c8 100644 --- a/crates/nwc/src/error.rs +++ b/crates/nwc/src/error.rs @@ -6,14 +6,13 @@ use std::fmt; -use nostr::nips::nip47; use nostr_sdk::{client, relay}; /// NWC error #[derive(Debug)] pub enum Error { - /// NIP47 error - NIP47(nip47::Error), + /// Nostr protocol error + Protocol(nostr::error::Error), /// Client error Client(client::Error), /// Relay error @@ -31,7 +30,7 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::NIP47(e) => e.fmt(f), + Self::Protocol(e) => e.fmt(f), Self::Client(e) => e.fmt(f), Self::Relay(e) => e.fmt(f), Self::ResponseNotReceived => f.write_str("response not received"), @@ -41,9 +40,9 @@ impl fmt::Display for Error { } } -impl From for Error { - fn from(e: nip47::Error) -> Self { - Self::NIP47(e) +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) } } diff --git a/database/nostr-database/src/flatbuffers/mod.rs b/database/nostr-database/src/flatbuffers/mod.rs index 98f8681ed..842ca4655 100644 --- a/database/nostr-database/src/flatbuffers/mod.rs +++ b/database/nostr-database/src/flatbuffers/mod.rs @@ -55,10 +55,10 @@ impl fmt::Display for MissingField { /// FlatBuffers Error #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// FlatBuffer FlatBuffer(InvalidFlatbuffer), - /// Event error - Event(event::Error), /// Secp256k1 error Secp256k1(secp256k1::Error), /// Field not found @@ -70,23 +70,23 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => e.fmt(f), Self::FlatBuffer(e) => write!(f, "{e}"), - Self::Event(e) => write!(f, "{e}"), Self::Secp256k1(e) => write!(f, "{e}"), Self::FieldNotFound(field) => write!(f, "'{field}' field not found"), } } } -impl From for Error { - fn from(e: InvalidFlatbuffer) -> Self { - Self::FlatBuffer(e) +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) } } -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) +impl From for Error { + fn from(e: InvalidFlatbuffer) -> Self { + Self::FlatBuffer(e) } } diff --git a/database/nostr-lmdb/src/store/error.rs b/database/nostr-lmdb/src/store/error.rs index 3573bb608..67f912f1c 100644 --- a/database/nostr-lmdb/src/store/error.rs +++ b/database/nostr-lmdb/src/store/error.rs @@ -6,7 +6,7 @@ use std::{fmt, io}; use async_utility::tokio::task::JoinError; -use nostr::{key, secp256k1}; +use nostr::secp256k1; use nostr_database::flatbuffers; use tokio::sync::oneshot; @@ -39,6 +39,8 @@ impl fmt::Display for MigrationError { #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// An upstream I/O error Io(io::Error), /// An error from LMDB @@ -46,7 +48,6 @@ pub enum Error { /// Flatbuffers error FlatBuffers(flatbuffers::Error), Thread(JoinError), - Key(key::Error), Secp256k1(secp256k1::Error), OneshotRecv(oneshot::error::RecvError), /// Database migration error @@ -66,11 +67,11 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => e.fmt(f), Self::Io(e) => write!(f, "{e}"), Self::Heed(e) => write!(f, "{e}"), Self::FlatBuffers(e) => write!(f, "{e}"), Self::Thread(e) => write!(f, "{e}"), - Self::Key(e) => write!(f, "{e}"), Self::Secp256k1(e) => write!(f, "{e}"), Self::OneshotRecv(e) => write!(f, "{e}"), Self::Migration(e) => write!(f, "Migration error: {e}"), @@ -82,6 +83,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + impl From for Error { fn from(e: io::Error) -> Self { Self::Io(e) @@ -106,12 +113,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) - } -} - impl From for Error { fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) diff --git a/database/nostr-sqlite/src/error.rs b/database/nostr-sqlite/src/error.rs index a04090e34..ad266be6a 100644 --- a/database/nostr-sqlite/src/error.rs +++ b/database/nostr-sqlite/src/error.rs @@ -4,7 +4,7 @@ use std::fmt; use std::num::TryFromIntError; use async_utility::tokio::task::JoinError; -use nostr::{event, key, secp256k1}; +use nostr::secp256k1; /// Migration error #[derive(Debug, Clone, PartialEq, Eq)] @@ -37,6 +37,8 @@ impl fmt::Display for MigrationError { /// Nostr SQL error #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// TryFromInt error TryFromInt(TryFromIntError), /// Rusqlite error @@ -49,10 +51,6 @@ pub enum Error { Json(serde_json::Error), /// Secp256k1 error Secp256k1(secp256k1::Error), - /// Event error - Event(event::Error), - /// Key error - Key(key::Error), /// Mutex poisoned MutexPoisoned, } @@ -62,19 +60,24 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => e.fmt(f), Self::TryFromInt(e) => write!(f, "{e}"), Self::Rusqlite(e) => write!(f, "{e}"), Self::Migration(e) => write!(f, "Migration error: {e}"), Self::Thread(e) => write!(f, "{e}"), Self::Json(e) => write!(f, "{e}"), Self::Secp256k1(e) => write!(f, "{e}"), - Self::Event(e) => write!(f, "{e}"), - Self::Key(e) => write!(f, "{e}"), Self::MutexPoisoned => f.write_str("mutex is poisoned"), } } } +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + impl From for Error { fn from(e: TryFromIntError) -> Self { Self::TryFromInt(e) @@ -110,15 +113,3 @@ impl From for Error { Self::Secp256k1(e) } } - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) - } -} diff --git a/gossip/nostr-gossip-sqlite/src/store.rs b/gossip/nostr-gossip-sqlite/src/store.rs index f11c21217..d4e94e6ac 100644 --- a/gossip/nostr-gossip-sqlite/src/store.rs +++ b/gossip/nostr-gossip-sqlite/src/store.rs @@ -31,7 +31,7 @@ pub struct NostrGossipSqlite { } impl NostrGossipSqlite { - async fn new(pool: Pool) -> nostr::Result { + async fn new(pool: Pool) -> Result { pool.interact(|conn| { if conn.pragma_update(None, "journal_mode", "WAL").is_err() { conn.pragma_update(None, "journal_mode", "DELETE")?; @@ -53,14 +53,14 @@ impl NostrGossipSqlite { } /// Creates an in-memory database - pub async fn in_memory() -> nostr::Result { + pub async fn in_memory() -> Result { let pool: Pool = Pool::open_in_memory()?; Self::new(pool).await } /// Connect to a SQL database #[cfg(not(target_arch = "wasm32"))] - pub async fn open

(path: P) -> nostr::Result + pub async fn open

(path: P) -> Result where P: AsRef, { @@ -72,7 +72,7 @@ impl NostrGossipSqlite { } /// Connect to a SQL database - pub async fn open_with_vfs

(path: P, vfs: &str) -> nostr::Result + pub async fn open_with_vfs

(path: P, vfs: &str) -> Result where P: AsRef, { diff --git a/rfs/nostr-blossom/examples/delete.rs b/rfs/nostr-blossom/examples/delete.rs index 738bcf527..c34ffed3f 100644 --- a/rfs/nostr-blossom/examples/delete.rs +++ b/rfs/nostr-blossom/examples/delete.rs @@ -20,7 +20,7 @@ struct Args { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let args = Args::parse(); let client = BlossomClient::new(args.server); diff --git a/rfs/nostr-blossom/examples/download.rs b/rfs/nostr-blossom/examples/download.rs index 060fd53b5..cec01b43e 100644 --- a/rfs/nostr-blossom/examples/download.rs +++ b/rfs/nostr-blossom/examples/download.rs @@ -22,7 +22,7 @@ struct Args { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let args = Args::parse(); // Initialize the client. diff --git a/rfs/nostr-blossom/examples/list.rs b/rfs/nostr-blossom/examples/list.rs index 04bdc37c3..bc13da660 100644 --- a/rfs/nostr-blossom/examples/list.rs +++ b/rfs/nostr-blossom/examples/list.rs @@ -19,7 +19,7 @@ struct Args { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let args = Args::parse(); let client = BlossomClient::new(args.server); diff --git a/rfs/nostr-blossom/examples/upload.rs b/rfs/nostr-blossom/examples/upload.rs index 371f3073f..76804555a 100644 --- a/rfs/nostr-blossom/examples/upload.rs +++ b/rfs/nostr-blossom/examples/upload.rs @@ -26,7 +26,7 @@ struct Args { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let args = Args::parse(); let client = BlossomClient::new(args.server); diff --git a/rfs/nostr-blossom/src/error.rs b/rfs/nostr-blossom/src/error.rs index 41bd80cfa..688a8b79f 100644 --- a/rfs/nostr-blossom/src/error.rs +++ b/rfs/nostr-blossom/src/error.rs @@ -6,7 +6,6 @@ use std::fmt; -use nostr::signer::SignerError; use nostr::types::ParseError; use reqwest::Response; use reqwest::header::{InvalidHeaderValue, ToStrError}; @@ -14,8 +13,8 @@ use reqwest::header::{InvalidHeaderValue, ToStrError}; /// Blossom error #[derive(Debug)] pub enum Error { - /// Nostr signer error - Signer(SignerError), + /// Nostr protocol error + Protocol(nostr::error::Error), /// Reqwest error Reqwest(reqwest::Error), /// Invalid header value @@ -55,7 +54,7 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Signer(e) => write!(f, "{e}"), + Self::Protocol(e) => write!(f, "{e}"), Self::Reqwest(e) => write!(f, "{e}"), Self::InvalidHeaderValue(e) => write!(f, "{e}"), Self::Url(e) => write!(f, "{e}"), @@ -78,9 +77,9 @@ impl fmt::Display for Error { } } -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) } } diff --git a/sdk/examples/aggregated-query.rs b/sdk/examples/aggregated-query.rs index 78055025b..9b2a10e39 100644 --- a/sdk/examples/aggregated-query.rs +++ b/sdk/examples/aggregated-query.rs @@ -9,7 +9,7 @@ use nostr_lmdb::NostrLmdb; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let database = NostrLmdb::open("./db/nostr-lmdb").await?; diff --git a/sdk/examples/blacklist.rs b/sdk/examples/blacklist.rs index 20b96dcc3..b87d10a3a 100644 --- a/sdk/examples/blacklist.rs +++ b/sdk/examples/blacklist.rs @@ -30,7 +30,7 @@ impl AdmitPolicy for Filtering { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let mut filtering = Filtering::default(); diff --git a/sdk/examples/bot.rs b/sdk/examples/bot.rs index cb1e526a1..c3a3a0b18 100644 --- a/sdk/examples/bot.rs +++ b/sdk/examples/bot.rs @@ -6,7 +6,7 @@ use nostr_gossip_memory::prelude::*; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let keys = Keys::parse("nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx")?; diff --git a/sdk/examples/client.rs b/sdk/examples/client.rs index 79ee644ab..ad9b26ee1 100644 --- a/sdk/examples/client.rs +++ b/sdk/examples/client.rs @@ -7,7 +7,7 @@ use std::num::NonZeroU8; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let client = Client::new(); diff --git a/sdk/examples/code_snippet.rs b/sdk/examples/code_snippet.rs index 7cc2fc205..a26f7879d 100644 --- a/sdk/examples/code_snippet.rs +++ b/sdk/examples/code_snippet.rs @@ -7,7 +7,7 @@ use nostr_sdk::prelude::*; const EXAMPLE_SNIPPET: &str = include_str!("code_snippet.rs"); #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let client = Client::default(); diff --git a/sdk/examples/comment.rs b/sdk/examples/comment.rs index fd25d28bd..3849c802b 100644 --- a/sdk/examples/comment.rs +++ b/sdk/examples/comment.rs @@ -7,7 +7,7 @@ use std::time::Duration; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let client = Client::default(); diff --git a/sdk/examples/fetch-events.rs b/sdk/examples/fetch-events.rs index fa481b02c..02cf1e8ee 100644 --- a/sdk/examples/fetch-events.rs +++ b/sdk/examples/fetch-events.rs @@ -7,7 +7,7 @@ use std::time::Duration; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let public_key = diff --git a/sdk/examples/gossip.rs b/sdk/examples/gossip.rs index c32528f49..a970abd9a 100644 --- a/sdk/examples/gossip.rs +++ b/sdk/examples/gossip.rs @@ -8,7 +8,7 @@ use nostr_gossip_memory::prelude::*; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let gossip = NostrGossipMemory::unbounded(); diff --git a/sdk/examples/limits.rs b/sdk/examples/limits.rs index d70635bf7..3ec0bccb4 100644 --- a/sdk/examples/limits.rs +++ b/sdk/examples/limits.rs @@ -5,7 +5,7 @@ use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); // Customize relay limits diff --git a/sdk/examples/lmdb.rs b/sdk/examples/lmdb.rs index 007f9fc7e..e0d9c866c 100644 --- a/sdk/examples/lmdb.rs +++ b/sdk/examples/lmdb.rs @@ -6,7 +6,7 @@ use nostr_lmdb::NostrLmdb; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let database = NostrLmdb::open("./db/nostr-lmdb").await?; diff --git a/sdk/examples/monitor.rs b/sdk/examples/monitor.rs index 42dbef092..0f5d081ce 100644 --- a/sdk/examples/monitor.rs +++ b/sdk/examples/monitor.rs @@ -5,7 +5,7 @@ use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let monitor = Monitor::new(4096); diff --git a/sdk/examples/nip42.rs b/sdk/examples/nip42.rs index c57b1552c..b08ccbe15 100644 --- a/sdk/examples/nip42.rs +++ b/sdk/examples/nip42.rs @@ -7,7 +7,7 @@ use std::time::Duration; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; diff --git a/sdk/examples/nostr-connect.rs b/sdk/examples/nostr-connect.rs index 30506423c..1b092372e 100644 --- a/sdk/examples/nostr-connect.rs +++ b/sdk/examples/nostr-connect.rs @@ -8,7 +8,7 @@ use nostr_connect::prelude::*; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let app_keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; diff --git a/sdk/examples/nostrdb.rs b/sdk/examples/nostrdb.rs index 7ba38fd36..455b2eb45 100644 --- a/sdk/examples/nostrdb.rs +++ b/sdk/examples/nostrdb.rs @@ -6,7 +6,7 @@ use nostr_ndb::NdbDatabase; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let database = NdbDatabase::open("./db/ndb")?; diff --git a/sdk/examples/status.rs b/sdk/examples/status.rs index 2edb3997d..1ad0e50d1 100644 --- a/sdk/examples/status.rs +++ b/sdk/examples/status.rs @@ -7,7 +7,7 @@ use std::time::Duration; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let client = Client::default(); diff --git a/sdk/examples/stream-events.rs b/sdk/examples/stream-events.rs index c4f3c9c9b..7d482f297 100644 --- a/sdk/examples/stream-events.rs +++ b/sdk/examples/stream-events.rs @@ -7,7 +7,7 @@ use std::time::Duration; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let client = Client::default(); diff --git a/sdk/examples/subscriptions.rs b/sdk/examples/subscriptions.rs index cff2dbef9..fe9cb3499 100644 --- a/sdk/examples/subscriptions.rs +++ b/sdk/examples/subscriptions.rs @@ -5,7 +5,7 @@ use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let client = Client::default(); diff --git a/sdk/examples/tor.rs b/sdk/examples/tor.rs index 3d4f0ba0d..1c7cad5f1 100644 --- a/sdk/examples/tor.rs +++ b/sdk/examples/tor.rs @@ -7,7 +7,7 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); // Configure client to use a proxy for the onion relays diff --git a/sdk/examples/whitelist.rs b/sdk/examples/whitelist.rs index 1bbac6283..56938232a 100644 --- a/sdk/examples/whitelist.rs +++ b/sdk/examples/whitelist.rs @@ -30,7 +30,7 @@ impl AdmitPolicy for WoT { } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let mut wot = WoT::default(); diff --git a/sdk/src/client/api/util.rs b/sdk/src/client/api/util.rs index 980be2066..6da81ca7d 100644 --- a/sdk/src/client/api/util.rs +++ b/sdk/src/client/api/util.rs @@ -1,6 +1,5 @@ use std::collections::{HashMap, HashSet}; -use nostr::types::url; use nostr::{Filter, RelayUrl, RelayUrlArg}; use super::req_target::{InnerReqTarget, ReqTarget}; @@ -38,7 +37,7 @@ async fn make_targets_from_filter_list( async fn convert_filters_arg_to_targets( pool: &RelayPool, target: ReqTarget<'_>, -) -> Result>, url::Error> { +) -> Result>, Error> { match target.into_inner() { InnerReqTarget::Auto(filters) => Ok(make_targets_from_filter_list(pool, filters).await), InnerReqTarget::Manual(targets) => convert_filters_arg_vec_to_map(targets), @@ -47,7 +46,7 @@ async fn convert_filters_arg_to_targets( pub(super) fn convert_filters_arg_vec_to_map( targeted: Vec<(RelayUrlArg<'_>, Vec)>, -) -> Result>, url::Error> { +) -> Result>, Error> { let mut map: HashMap> = HashMap::with_capacity(targeted.len()); for (url_arg, filters) in targeted { let url: RelayUrl = url_arg.try_into_relay_url()?.into_owned(); diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index dedf56ac2..6fee34b84 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -4,7 +4,6 @@ use std::fmt; -use nostr::prelude::*; use nostr::serde_json; use nostr_database::prelude::*; use nostr_gossip::error::GossipError; @@ -14,16 +13,14 @@ use crate::{pool, relay}; /// Client error #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// Relay error Relay(relay::Error), /// Relay Pool error RelayPool(pool::Error), - /// Relay URL error - RelayUrl(url::Error), /// Database error Database(DatabaseError), - /// Signer error - Signer(SignerError), /// Gossip error Gossip(GossipError), /// Json error @@ -43,11 +40,10 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => e.fmt(f), Self::Relay(e) => e.fmt(f), Self::RelayPool(e) => e.fmt(f), - Self::RelayUrl(e) => e.fmt(f), Self::Database(e) => e.fmt(f), - Self::Signer(e) => e.fmt(f), Self::Gossip(e) => e.fmt(f), Self::Json(e) => e.fmt(f), Self::SignerNotConfigured => f.write_str("signer not configured"), @@ -60,6 +56,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + impl From for Error { fn from(e: relay::Error) -> Self { Self::Relay(e) @@ -72,24 +74,12 @@ impl From for Error { } } -impl From for Error { - fn from(e: url::Error) -> Self { - Self::RelayUrl(e) - } -} - impl From for Error { fn from(e: DatabaseError) -> Self { Self::Database(e) } } -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } -} - impl From for Error { fn from(e: GossipError) -> Self { Self::Gossip(e) diff --git a/sdk/src/relay/api/sync.rs b/sdk/src/relay/api/sync.rs index fdcd01276..88fa4a4dd 100644 --- a/sdk/src/relay/api/sync.rs +++ b/sdk/src/relay/api/sync.rs @@ -111,7 +111,7 @@ async fn handle_neg_msg( have_ids: &mut Vec, need_ids: &mut Vec, sync_done: &mut bool, -) -> nostr::Result<(), Error> +) -> Result<(), Error> where I: Iterator, { @@ -162,7 +162,7 @@ async fn upload_neg_events( have_ids: &mut Vec, in_flight_up: &mut HashSet, opts: &SyncOptions, -) -> nostr::Result<(), Error> { +) -> Result<(), Error> { // Check if it should skip the upload if !opts.do_up() || have_ids.is_empty() || in_flight_up.len() > NEGENTROPY_LOW_WATER_UP { return Ok(()); @@ -216,7 +216,7 @@ async fn req_neg_events( in_flight_down: &mut bool, down_sub_id: &SubscriptionId, opts: &SyncOptions, -) -> nostr::Result<(), Error> { +) -> Result<(), Error> { // Check if it should skip the download if !opts.do_down() || need_ids.is_empty() || *in_flight_down { return Ok(()); @@ -309,7 +309,7 @@ pub(super) async fn sync( items: Vec<(EventId, Timestamp)>, opts: &SyncOptions, output: &mut SyncSummary, -) -> nostr::Result<(), Error> { +) -> Result<(), Error> { // Prepare the negentropy client let storage: NegentropyStorageVector = prepare_negentropy_storage(items)?; let mut negentropy: Negentropy = @@ -518,7 +518,7 @@ pub(super) async fn sync( fn prepare_negentropy_storage( items: Vec<(EventId, Timestamp)>, -) -> nostr::Result { +) -> Result { // Compose negentropy storage let mut storage = NegentropyStorageVector::with_capacity(items.len()); @@ -541,7 +541,7 @@ async fn check_negentropy_support( sub_id: &SubscriptionId, opts: &SyncOptions, temp_notifications: &mut broadcast::Receiver, -) -> nostr::Result<(), Error> { +) -> Result<(), Error> { time::timeout(Some(opts.initial_timeout), async { loop { let notification = temp_notifications.recv().await?; diff --git a/sdk/src/relay/error.rs b/sdk/src/relay/error.rs index 5c2b1c1a8..30de84bac 100644 --- a/sdk/src/relay/error.rs +++ b/sdk/src/relay/error.rs @@ -1,8 +1,6 @@ use std::fmt; use std::time::Duration; -use nostr::event; -use nostr::message::MessageHandleError; use nostr_database::DatabaseError; use nostr_gossip::error::GossipError; use tokio::sync::{broadcast, oneshot}; @@ -13,6 +11,8 @@ use crate::transport::error::TransportError; /// Relay error #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// Any error Any(Box), /// Transport error @@ -23,10 +23,6 @@ pub enum Error { Database(DatabaseError), /// Gossip error Gossip(GossipError), - /// MessageHandle error - MessageHandle(MessageHandleError), - /// Event error - Event(event::Error), /// Hex error Hex(faster_hex::Error), /// Negentropy error @@ -135,13 +131,12 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => e.fmt(f), Self::Any(e) => e.fmt(f), Self::Transport(e) => write!(f, "transport: {e}"), Self::Policy(e) => write!(f, "policy: {e}"), Self::Database(e) => write!(f, "database: {e}"), Self::Gossip(e) => write!(f, "gossip: {e}"), - Self::MessageHandle(e) => e.fmt(f), - Self::Event(e) => e.fmt(f), Self::Hex(e) => e.fmt(f), Self::Negentropy(e) => e.fmt(f), Self::OneshotRecv(e) => e.fmt(f), @@ -206,6 +201,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + impl From> for Error { #[inline] fn from(e: Box) -> Self { @@ -237,18 +238,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: MessageHandleError) -> Self { - Self::MessageHandle(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - impl From for Error { fn from(e: faster_hex::Error) -> Self { Self::Hex(e) diff --git a/signer/nostr-browser-signer-proxy/examples/browser-signer-proxy.rs b/signer/nostr-browser-signer-proxy/examples/browser-signer-proxy.rs index f40e8e551..631c03c16 100644 --- a/signer/nostr-browser-signer-proxy/examples/browser-signer-proxy.rs +++ b/signer/nostr-browser-signer-proxy/examples/browser-signer-proxy.rs @@ -8,7 +8,7 @@ use nostr_browser_signer_proxy::prelude::*; use tokio::{signal, time}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let proxy = BrowserSignerProxy::new(BrowserSignerProxyOptions::default()); diff --git a/signer/nostr-browser-signer-proxy/examples/custom_html.rs b/signer/nostr-browser-signer-proxy/examples/custom_html.rs index 71bf7468a..20af9119c 100644 --- a/signer/nostr-browser-signer-proxy/examples/custom_html.rs +++ b/signer/nostr-browser-signer-proxy/examples/custom_html.rs @@ -24,7 +24,7 @@ const CUSTOM_HTML: &str = r#" "#; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); // Use `include_str!` macro in your code. diff --git a/signer/nostr-browser-signer-proxy/src/error.rs b/signer/nostr-browser-signer-proxy/src/error.rs index 423f0bdef..ec8988c94 100644 --- a/signer/nostr-browser-signer-proxy/src/error.rs +++ b/signer/nostr-browser-signer-proxy/src/error.rs @@ -7,20 +7,19 @@ use std::{fmt, io}; use hyper::http; -use nostr::event; use tokio::sync::oneshot::error::RecvError; /// Error #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// I/O error Io(io::Error), /// HTTP error Http(http::Error), /// Json error Json(serde_json::Error), - /// Event error - Event(event::Error), /// Oneshot channel receive error OneShotRecv(RecvError), /// Generic error @@ -36,10 +35,10 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => write!(f, "{e}"), Self::Io(e) => write!(f, "{e}"), Self::Http(e) => write!(f, "{e}"), Self::Json(e) => write!(f, "{e}"), - Self::Event(e) => write!(f, "{e}"), Self::OneShotRecv(e) => write!(f, "{e}"), Self::Generic(e) => write!(f, "{e}"), Self::Timeout => write!(f, "timeout"), @@ -48,6 +47,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + impl From for Error { fn from(e: io::Error) -> Self { Self::Io(e) @@ -66,12 +71,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - impl From for Error { fn from(e: RecvError) -> Self { Self::OneShotRecv(e) diff --git a/signer/nostr-browser-signer/src/lib.rs b/signer/nostr-browser-signer/src/lib.rs index c86faa568..e359901be 100644 --- a/signer/nostr-browser-signer/src/lib.rs +++ b/signer/nostr-browser-signer/src/lib.rs @@ -20,7 +20,6 @@ use std::str::FromStr; use js_sys::{Array, Function, JsString, Object, Promise, Reflect}; use nostr::prelude::*; -use nostr::secp256k1; use nostr::secp256k1::schnorr::Signature; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; @@ -77,14 +76,10 @@ impl From for ExtensionError { /// NIP-07 error #[derive(Debug)] pub enum Error { + /// Nostr protocol error + Protocol(nostr::error::Error), /// Browser/Extension-related errors Extension(ExtensionError), - /// Secp256k1 error - Secp256k1(secp256k1::Error), - /// Keys error - Keys(key::Error), - /// Unsigned event error - Event(event::Error), } impl std::error::Error for Error {} @@ -92,14 +87,18 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Protocol(e) => write!(f, "{e}"), Self::Extension(e) => write!(f, "{e}"), - Self::Secp256k1(e) => write!(f, "{e}"), - Self::Keys(e) => write!(f, "{e}"), - Self::Event(e) => write!(f, "{e}"), } } } +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) + } +} + impl From for Error { fn from(e: ExtensionError) -> Self { Self::Extension(e) @@ -112,24 +111,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: secp256k1::Error) -> Self { - Self::Secp256k1(e) - } -} - -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Keys(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - /// Signer for interaction with browser extensions (ex. Alby) /// /// Browser extensions: @@ -262,7 +243,12 @@ impl BrowserSigner { .get_value_by_key(&event_obj, "sig")? .as_string() .ok_or(ExtensionError::TypeMismatch)?; - let sig: Signature = Signature::from_str(&sig)?; + let sig: Signature = Signature::from_str(&sig).map_err(|e| { + Error::Protocol(nostr::error::Error::new( + nostr::error::ErrorKind::Malformed, + e, + )) + })?; // Add signature Ok(unsigned.add_signature(sig)?) diff --git a/signer/nostr-connect/examples/handle-auth-url.rs b/signer/nostr-connect/examples/handle-auth-url.rs index a306385e1..a0a615f99 100644 --- a/signer/nostr-connect/examples/handle-auth-url.rs +++ b/signer/nostr-connect/examples/handle-auth-url.rs @@ -10,17 +10,20 @@ use nostr_connect::prelude::*; struct MyAuthUrlHandler; impl AuthUrlHandler for MyAuthUrlHandler { - fn on_auth_url(&self, auth_url: Url) -> BoxedFuture<'_, Result<()>> { + fn on_auth_url( + &self, + auth_url: Url, + ) -> BoxedFuture<'_, Result<(), nostr_connect::error::Error>> { Box::pin(async move { println!("Opening auth url: {auth_url}"); - webbrowser::open(auth_url.as_str())?; + webbrowser::open(auth_url.as_str()).unwrap(); Ok(()) }) } } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let uri = NostrConnectUri::parse( diff --git a/signer/nostr-connect/examples/nostr-connect-signer.rs b/signer/nostr-connect/examples/nostr-connect-signer.rs index 2120a05c3..78669e61a 100644 --- a/signer/nostr-connect/examples/nostr-connect-signer.rs +++ b/signer/nostr-connect/examples/nostr-connect-signer.rs @@ -9,7 +9,7 @@ const SIGNER_SECRET_KEY: &str = "nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc const USER_SECRET_KEY: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let keys = NostrConnectKeys::new( diff --git a/signer/nostr-connect/src/client.rs b/signer/nostr-connect/src/client.rs index 67a9aed13..8bc828d83 100644 --- a/signer/nostr-connect/src/client.rs +++ b/signer/nostr-connect/src/client.rs @@ -452,7 +452,7 @@ fn is_valid_connect_response(response: &ResponseResult, expected_secret: Option< /// Nostr Connect auth_url handler pub trait AuthUrlHandler: fmt::Debug + Send + Sync { /// Handle `auth_url` message - fn on_auth_url(&self, auth_url: Url) -> BoxedFuture<'_, Result<()>>; + fn on_auth_url(&self, auth_url: Url) -> BoxedFuture<'_, Result<(), Error>>; } #[doc(hidden)] diff --git a/signer/nostr-connect/src/error.rs b/signer/nostr-connect/src/error.rs index 090e9b2e5..88119dbe3 100644 --- a/signer/nostr-connect/src/error.rs +++ b/signer/nostr-connect/src/error.rs @@ -7,27 +7,16 @@ use std::fmt; use nostr::PublicKey; -use nostr::nips::{nip04, nip44, nip46}; -use nostr::signer::SignerError; -use nostr::types::url; use nostr_sdk::client; use tokio::sync::SetError; /// Nostr Connect error #[derive(Debug)] pub enum Error { - /// Signer error - Signer(SignerError), - /// NIP04 error - NIP04(nip04::Error), - /// NIP44 error - NIP44(nip44::Error), - /// NIP46 error - NIP46(nip46::Error), + /// Nostr protocol error + Protocol(nostr::error::Error), /// Client Client(client::Error), - /// Url parse error - RelayUrl(url::Error), /// Set user public key error SetUserPublicKey(SetError), /// Invalid response from remote signer @@ -51,12 +40,8 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Signer(e) => e.fmt(f), - Self::NIP04(e) => e.fmt(f), - Self::NIP44(e) => e.fmt(f), - Self::NIP46(e) => e.fmt(f), + Self::Protocol(e) => e.fmt(f), Self::Client(e) => e.fmt(f), - Self::RelayUrl(e) => e.fmt(f), Self::SetUserPublicKey(e) => e.fmt(f), Self::InvalidResponse(e) => e.fmt(f), Self::Response(e) => e.fmt(f), @@ -69,27 +54,9 @@ impl fmt::Display for Error { } } -impl From for Error { - fn from(e: SignerError) -> Self { - Self::Signer(e) - } -} - -impl From for Error { - fn from(e: nip04::Error) -> Self { - Self::NIP04(e) - } -} - -impl From for Error { - fn from(e: nip44::Error) -> Self { - Self::NIP44(e) - } -} - -impl From for Error { - fn from(e: nip46::Error) -> Self { - Self::NIP46(e) +impl From for Error { + fn from(e: nostr::error::Error) -> Self { + Self::Protocol(e) } } @@ -99,12 +66,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: url::Error) -> Self { - Self::RelayUrl(e) - } -} - impl From> for Error { fn from(e: SetError) -> Self { Self::SetUserPublicKey(e) From e4562eb2d5d28858eac544507a9259dba86e02fe Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 20 May 2026 16:29:16 +0200 Subject: [PATCH 02/11] keyring: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + crates/nostr-keyring/Cargo.toml | 1 + crates/nostr-keyring/README.md | 2 +- crates/nostr-keyring/src/error.rs | 104 ++++++++++++++++++++++++++++ crates/nostr-keyring/src/lib.rs | 49 +------------ crates/nostr-keyring/src/prelude.rs | 2 + 6 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 crates/nostr-keyring/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 4eba29ac8..801e8b622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1978,6 +1978,7 @@ dependencies = [ "async-utility", "keyring", "nostr", + "opaquerr", "tokio", ] diff --git a/crates/nostr-keyring/Cargo.toml b/crates/nostr-keyring/Cargo.toml index 3c64c6e8c..3b3697216 100644 --- a/crates/nostr-keyring/Cargo.toml +++ b/crates/nostr-keyring/Cargo.toml @@ -24,6 +24,7 @@ async = ["dep:async-utility"] async-utility = { workspace = true, optional = true } keyring = { version = "3.6", features = ["apple-native", "linux-native", "linux-native-sync-persistent", "windows-native"] } # MSRV: 1.75.0 nostr = { workspace = true, features = ["std"] } +opaquerr.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/nostr-keyring/README.md b/crates/nostr-keyring/README.md index 15fbc2058..7e2bc84e9 100644 --- a/crates/nostr-keyring/README.md +++ b/crates/nostr-keyring/README.md @@ -8,7 +8,7 @@ The crate keeps all serialization in-memory and relies on the OS-provided creden ```rust,no_run use nostr_keyring::prelude::*; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { let keyring = NostrKeyring::new("my-nostr-app"); // Save a key diff --git a/crates/nostr-keyring/src/error.rs b/crates/nostr-keyring/src/error.rs new file mode 100644 index 000000000..68351c977 --- /dev/null +++ b/crates/nostr-keyring/src/error.rs @@ -0,0 +1,104 @@ +//! Nostr Keyring error. + +use std::{error, fmt}; + +#[cfg(feature = "async")] +use async_utility::tokio; + +opaquerr::define_kind! { + /// Category for a [`Error`]. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// Keyring error. + Keyring => "keyring error", + /// Anything not covered by the stable categories above. + Other => "other error", + } +} + +enum Inner { + Protocol(nostr::error::Error), + Keyring(keyring::Error), + #[cfg(feature = "async")] + Join(tokio::task::JoinError), +} + +impl Inner { + const fn kind(&self) -> ErrorKind { + match self { + Inner::Protocol(_) => ErrorKind::Protocol, + Inner::Keyring(_) => ErrorKind::Keyring, + #[cfg(feature = "async")] + Inner::Join(_) => ErrorKind::Other, + } + } +} + +/// Nostr keyring error +pub struct Error(Inner); + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let kind = self.kind(); + + match &self.0 { + Inner::Protocol(e) => f.debug_tuple("Error").field(&kind).field(e).finish(), + Inner::Keyring(e) => f.debug_tuple("Error").field(&kind).field(e).finish(), + #[cfg(feature = "async")] + Inner::Join(e) => f.debug_tuple("Error").field(&kind).field(e).finish(), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match &self.0 { + Inner::Protocol(e) => Some(e), + Inner::Keyring(e) => Some(e), + #[cfg(feature = "async")] + Inner::Join(e) => Some(e), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Inner::Protocol(e) => e.fmt(f), + Inner::Keyring(e) => e.fmt(f), + #[cfg(feature = "async")] + Inner::Join(e) => e.fmt(f), + } + } +} + +impl Error { + /// Returns the error category. + #[inline] + pub const fn kind(&self) -> ErrorKind { + self.0.kind() + } +} + +impl From for Error { + #[inline] + fn from(inner: nostr::error::Error) -> Self { + Self(Inner::Protocol(inner)) + } +} + +impl From for Error { + #[inline] + fn from(inner: keyring::Error) -> Self { + Self(Inner::Keyring(inner)) + } +} + +#[cfg(feature = "async")] +impl From for Error { + #[inline] + fn from(inner: tokio::task::JoinError) -> Self { + Self(Inner::Join(inner)) + } +} diff --git a/crates/nostr-keyring/src/lib.rs b/crates/nostr-keyring/src/lib.rs index e3b7bf075..5182e0b1b 100644 --- a/crates/nostr-keyring/src/lib.rs +++ b/crates/nostr-keyring/src/lib.rs @@ -10,58 +10,15 @@ #![warn(clippy::large_futures)] #![doc = include_str!("../README.md")] -use std::fmt; - #[cfg(feature = "async")] -use async_utility::{task, tokio}; +use async_utility::task; pub use keyring::{Entry, Error as KeyringError}; use nostr::key::{Keys, SecretKey}; +pub mod error; pub mod prelude; -/// Keyring error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Join error - #[cfg(feature = "async")] - Join(tokio::task::JoinError), - /// Keyring error - Keyring(KeyringError), -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => e.fmt(f), - #[cfg(feature = "async")] - Self::Join(e) => write!(f, "{e}"), - Self::Keyring(e) => write!(f, "{e}"), - } - } -} - -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} - -#[cfg(feature = "async")] -impl From for Error { - fn from(e: tokio::task::JoinError) -> Self { - Self::Join(e) - } -} - -impl From for Error { - fn from(e: KeyringError) -> Self { - Self::Keyring(e) - } -} +use self::error::Error; /// Nostr keyring #[derive(Debug, Clone)] diff --git a/crates/nostr-keyring/src/prelude.rs b/crates/nostr-keyring/src/prelude.rs index f21884745..42ddb4506 100644 --- a/crates/nostr-keyring/src/prelude.rs +++ b/crates/nostr-keyring/src/prelude.rs @@ -4,10 +4,12 @@ //! Prelude +#![allow(unused_imports)] #![allow(unknown_lints)] #![allow(ambiguous_glob_reexports)] #![doc(hidden)] pub use nostr::prelude::*; +pub use crate::error::{Error, ErrorKind}; pub use crate::*; From add592bdbd3f5442d0ad6132aacadbb323e27f76 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 22 May 2026 10:56:40 +0200 Subject: [PATCH 03/11] database: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + crates/nostr-relay-builder/src/error.rs | 7 +- database/nostr-database/Cargo.toml | 1 + database/nostr-database/src/error.rs | 64 +++++++----- database/nostr-database/src/ext.rs | 26 ++--- database/nostr-database/src/lib.rs | 20 ++-- database/nostr-database/src/prelude.rs | 1 + database/nostr-lmdb/src/lib.rs | 65 ++++-------- database/nostr-lmdb/src/store/error.rs | 42 ++++---- database/nostr-lmdb/src/store/ingester.rs | 52 ++++----- database/nostr-lmdb/src/store/lmdb/mod.rs | 122 ++++++++++++---------- database/nostr-lmdb/src/store/mod.rs | 42 +++++--- database/nostr-memory/src/lib.rs | 17 +-- database/nostr-ndb/src/lib.rs | 62 ++++++----- database/nostr-sqlite/src/builder.rs | 2 +- database/nostr-sqlite/src/error.rs | 47 ++++----- database/nostr-sqlite/src/lib.rs | 2 +- database/nostr-sqlite/src/migration.rs | 12 +-- database/nostr-sqlite/src/model.rs | 4 +- database/nostr-sqlite/src/pool.rs | 24 ++--- database/nostr-sqlite/src/prelude.rs | 1 - database/nostr-sqlite/src/store.rs | 94 +++++++++-------- sdk/src/client/error.rs | 7 +- sdk/src/events_tracker.rs | 23 ++-- sdk/src/relay/error.rs | 7 +- 25 files changed, 381 insertions(+), 364 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 801e8b622..e155a8ae0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1919,6 +1919,7 @@ dependencies = [ "btreecap", "flatbuffers 25.12.19", "nostr", + "opaquerr", ] [[package]] diff --git a/crates/nostr-relay-builder/src/error.rs b/crates/nostr-relay-builder/src/error.rs index b6dea5671..b49890644 100644 --- a/crates/nostr-relay-builder/src/error.rs +++ b/crates/nostr-relay-builder/src/error.rs @@ -7,7 +7,6 @@ use std::{fmt, io}; use nostr::Event; -use nostr_database::DatabaseError; use nostr_sdk::client; use tokio::sync::broadcast; @@ -17,7 +16,7 @@ pub enum Error { /// I/O error IO(io::Error), /// Database error - Database(DatabaseError), + Database(nostr_database::error::Error), /// Client error Client(client::Error), /// Nostr protocol error @@ -52,8 +51,8 @@ impl From for Error { } } -impl From for Error { - fn from(e: DatabaseError) -> Self { +impl From for Error { + fn from(e: nostr_database::error::Error) -> Self { Self::Database(e) } } diff --git a/database/nostr-database/Cargo.toml b/database/nostr-database/Cargo.toml index d8e4b2a94..0b8431309 100644 --- a/database/nostr-database/Cargo.toml +++ b/database/nostr-database/Cargo.toml @@ -23,6 +23,7 @@ flatbuf = ["dep:flatbuffers"] btreecap.workspace = true flatbuffers = { version = "25.12", optional = true } nostr = { workspace = true, features = ["std"] } +opaquerr = { workspace = true, features = ["alloc"] } [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(bench)'] } diff --git a/database/nostr-database/src/error.rs b/database/nostr-database/src/error.rs index 7714b1073..a4f0932cc 100644 --- a/database/nostr-database/src/error.rs +++ b/database/nostr-database/src/error.rs @@ -4,37 +4,53 @@ //! Nostr Database Error -use std::fmt; - -/// Database Error -#[derive(Debug)] -pub enum DatabaseError { - /// An error happened in the underlying database backend. - Backend(Box), - /// Not supported - NotSupported, +opaquerr::define_kind! { + /// Nostr database error kind. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// I/O error. + IO => "I/O error", + /// Storage error + Storage => "storage error", + /// Database migration error. + Migration => "migration error", + /// The operation is known but not supported. + Unsupported => "the operation is known but not supported", + /// Anything not covered by the stable categories above. + Other => "other error", + } } -impl std::error::Error for DatabaseError {} +opaquerr::define_error! { + /// Nostr database error. + pub Error(ErrorKind) -impl fmt::Display for DatabaseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Backend(e) => e.fmt(f), - Self::NotSupported => f.write_str("not supported"), - } + from { + nostr::error::Error => ErrorKind::Protocol, + std::io::Error => ErrorKind::IO, } } -impl DatabaseError { - /// Create a new backend error - /// - /// Shorthand for `Error::Backend(Box::new(error))`. - #[inline] - pub fn backend(error: E) -> Self +impl Error { + /// Storage error + pub fn storage(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Storage, error) + } + + /// Migration error + pub fn migration(error: E) -> Self where - E: std::error::Error + Send + Sync + 'static, + E: Into>, { - Self::Backend(Box::new(error)) + Self::new(ErrorKind::Migration, error) + } + + /// unsupported feature + pub const fn unsupported(message: &'static str) -> Self { + Self::with_static_message(ErrorKind::Unsupported, message) } } diff --git a/database/nostr-database/src/ext.rs b/database/nostr-database/src/ext.rs index 9c0bad518..23ec42f27 100644 --- a/database/nostr-database/src/ext.rs +++ b/database/nostr-database/src/ext.rs @@ -8,15 +8,13 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use nostr::prelude::*; -use crate::{DatabaseError, Events, NostrDatabase, Profile, RelaysMap}; +use crate::error::Error; +use crate::{Events, NostrDatabase, Profile, RelaysMap}; /// Nostr Event Store Extension pub trait NostrDatabaseExt: NostrDatabase { /// Get public key metadata - fn metadata( - &self, - public_key: PublicKey, - ) -> BoxedFuture<'_, Result, DatabaseError>> { + fn metadata(&self, public_key: PublicKey) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { let filter = Filter::new() .author(public_key) @@ -24,9 +22,7 @@ pub trait NostrDatabaseExt: NostrDatabase { .limit(1); let events: Events = self.query(filter).await?; match events.first_owned() { - Some(event) => Ok(Some( - Metadata::from_json(event.content).map_err(DatabaseError::backend)?, - )), + Some(event) => Ok(Some(Metadata::from_json(event.content)?)), None => Ok(None), } }) @@ -36,7 +32,7 @@ pub trait NostrDatabaseExt: NostrDatabase { fn contacts_public_keys( &self, public_key: PublicKey, - ) -> BoxedFuture<'_, Result, DatabaseError>> { + ) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { let filter = Filter::new() .author(public_key) @@ -51,10 +47,7 @@ pub trait NostrDatabaseExt: NostrDatabase { } /// Get contact list with metadata of [`PublicKey`] - fn contacts( - &self, - public_key: PublicKey, - ) -> BoxedFuture<'_, Result, DatabaseError>> { + fn contacts(&self, public_key: PublicKey) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { let filter = Filter::new() .author(public_key) @@ -91,10 +84,7 @@ pub trait NostrDatabaseExt: NostrDatabase { /// Get relays list for [PublicKey] /// /// - fn relay_list( - &self, - public_key: PublicKey, - ) -> BoxedFuture<'_, Result> { + fn relay_list(&self, public_key: PublicKey) -> BoxedFuture<'_, Result> { Box::pin(async move { // Query let filter: Filter = Filter::default() @@ -117,7 +107,7 @@ pub trait NostrDatabaseExt: NostrDatabase { fn relay_lists<'a, I>( &'a self, public_keys: I, - ) -> BoxedFuture<'a, Result, DatabaseError>> + ) -> BoxedFuture<'a, Result, Error>> where I: IntoIterator + Send + 'a, { diff --git a/database/nostr-database/src/lib.rs b/database/nostr-database/src/lib.rs index 74cb91b32..ba7d8eab8 100644 --- a/database/nostr-database/src/lib.rs +++ b/database/nostr-database/src/lib.rs @@ -22,7 +22,7 @@ pub use nostr; use nostr::prelude::*; mod collections; -mod error; +pub mod error; pub mod ext; #[cfg(feature = "flatbuf")] pub mod flatbuffers; @@ -30,7 +30,7 @@ pub mod prelude; pub mod profile; pub use self::collections::events::Events; -pub use self::error::DatabaseError; +use self::error::Error; #[cfg(feature = "flatbuf")] pub use self::flatbuffers::{FlatBufferBuilder, FlatBufferDecode, FlatBufferEncode}; pub use self::profile::Profile; @@ -190,7 +190,7 @@ pub trait NostrDatabase: Any + Debug + Send + Sync { fn save_event<'a>( &'a self, event: &'a Event, - ) -> BoxedFuture<'a, Result>; + ) -> BoxedFuture<'a, Result>; /// Check event status by ID /// @@ -198,27 +198,27 @@ pub trait NostrDatabase: Any + Debug + Send + Sync { fn check_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result>; + ) -> BoxedFuture<'a, Result>; /// Get [`Event`] by [`EventId`] fn event_by_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result, DatabaseError>>; + ) -> BoxedFuture<'a, Result, Error>>; /// Count the number of events found with [`Filter`]. /// /// Use `Filter::new()` or `Filter::default()` to count all events. - fn count(&self, filter: Filter) -> BoxedFuture<'_, Result>; + fn count(&self, filter: Filter) -> BoxedFuture<'_, Result>; /// Query stored events. - fn query(&self, filter: Filter) -> BoxedFuture<'_, Result>; + fn query(&self, filter: Filter) -> BoxedFuture<'_, Result>; /// Get `negentropy` items fn negentropy_items( &self, filter: Filter, - ) -> BoxedFuture<'_, Result, DatabaseError>> { + ) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { let events: Events = self.query(filter).await?; Ok(events.into_iter().map(|e| (e.id, e.created_at)).collect()) @@ -226,8 +226,8 @@ pub trait NostrDatabase: Any + Debug + Send + Sync { } /// Delete all events that match the [Filter] - fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), DatabaseError>>; + fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), Error>>; /// Wipe all data - fn wipe(&self) -> BoxedFuture<'_, Result<(), DatabaseError>>; + fn wipe(&self) -> BoxedFuture<'_, Result<(), Error>>; } diff --git a/database/nostr-database/src/prelude.rs b/database/nostr-database/src/prelude.rs index 14f5fdb29..2a99bbf4a 100644 --- a/database/nostr-database/src/prelude.rs +++ b/database/nostr-database/src/prelude.rs @@ -10,5 +10,6 @@ pub use nostr::prelude::*; +pub use crate::error::{Error, ErrorKind}; pub use crate::ext::*; pub use crate::*; diff --git a/database/nostr-lmdb/src/lib.rs b/database/nostr-lmdb/src/lib.rs index b8f81ca9d..a98318b39 100644 --- a/database/nostr-lmdb/src/lib.rs +++ b/database/nostr-lmdb/src/lib.rs @@ -12,6 +12,7 @@ use std::path::{Path, PathBuf}; +use nostr_database::error::Error; use nostr_database::prelude::*; pub mod prelude; @@ -129,10 +130,8 @@ impl NostrLmdbBuilder { } /// Build - pub async fn build(self) -> Result { - let db: Store = Store::from_builder(self) - .await - .map_err(DatabaseError::backend)?; + pub async fn build(self) -> Result { + let db: Store = Store::from_builder(self).await?; Ok(NostrLmdb { db }) } } @@ -146,7 +145,7 @@ pub struct NostrLmdb { impl NostrLmdb { /// Open LMDB database #[inline] - pub async fn open

(path: P) -> Result + pub async fn open

(path: P) -> Result where P: AsRef, { @@ -164,8 +163,8 @@ impl NostrLmdb { /// Re-index the database. #[inline] - pub async fn reindex(&self) -> Result<(), DatabaseError> { - self.db.reindex().await.map_err(DatabaseError::backend) + pub async fn reindex(&self) -> Result<(), Error> { + Ok(self.db.reindex().await?) } } @@ -187,66 +186,46 @@ impl NostrDatabase for NostrLmdb { fn save_event<'a>( &'a self, event: &'a Event, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - self.db - .save_event(event) - .await - .map_err(DatabaseError::backend) - }) + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { Ok(self.db.save_event(event).await?) }) } fn check_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - self.db - .check_id(*event_id) - .await - .map_err(DatabaseError::backend) - }) + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { Ok(self.db.check_id(*event_id).await?) }) } fn event_by_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result, DatabaseError>> { - Box::pin(async move { - self.db - .get_event_by_id(*event_id) - .await - .map_err(DatabaseError::backend) - }) + ) -> BoxedFuture<'a, Result, Error>> { + Box::pin(async move { Ok(self.db.get_event_by_id(*event_id).await?) }) } - fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { - Box::pin(async move { self.db.count(filter).await.map_err(DatabaseError::backend) }) + fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { + Box::pin(async move { Ok(self.db.count(filter).await?) }) } - fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { - Box::pin(async move { self.db.query(filter).await.map_err(DatabaseError::backend) }) + fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { + Box::pin(async move { Ok(self.db.query(filter).await?) }) } fn negentropy_items( &self, filter: Filter, - ) -> BoxedFuture<'_, Result, DatabaseError>> { - Box::pin(async move { - self.db - .negentropy_items(filter) - .await - .map_err(DatabaseError::backend) - }) + ) -> BoxedFuture<'_, Result, Error>> { + Box::pin(async move { Ok(self.db.negentropy_items(filter).await?) }) } - fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), DatabaseError>> { - Box::pin(async move { self.db.delete(filter).await.map_err(DatabaseError::backend) }) + fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), Error>> { + Box::pin(async move { Ok(self.db.delete(filter).await?) }) } #[inline] - fn wipe(&self) -> BoxedFuture<'_, Result<(), DatabaseError>> { - Box::pin(async move { self.db.wipe().await.map_err(DatabaseError::backend) }) + fn wipe(&self) -> BoxedFuture<'_, Result<(), Error>> { + Box::pin(async move { Ok(self.db.wipe().await?) }) } } diff --git a/database/nostr-lmdb/src/store/error.rs b/database/nostr-lmdb/src/store/error.rs index 67f912f1c..cadf64566 100644 --- a/database/nostr-lmdb/src/store/error.rs +++ b/database/nostr-lmdb/src/store/error.rs @@ -11,7 +11,7 @@ use nostr_database::flatbuffers; use tokio::sync::oneshot; #[derive(Debug, PartialEq, Eq)] -pub enum MigrationError { +pub(crate) enum MigrationError { /// Database version is newer than supported one NewerVersion { /// Current version of the database @@ -38,33 +38,24 @@ impl fmt::Display for MigrationError { } #[derive(Debug)] -pub enum Error { - /// Nostr protocol error +pub(crate) enum StoreError { Protocol(nostr::error::Error), - /// An upstream I/O error Io(io::Error), - /// An error from LMDB Heed(heed::Error), - /// Flatbuffers error FlatBuffers(flatbuffers::Error), Thread(JoinError), Secp256k1(secp256k1::Error), OneshotRecv(oneshot::error::RecvError), - /// Database migration error Migration(MigrationError), - /// Flume channel send error FlumeSend, - /// The event kind is wrong WrongEventKind, - /// Not found NotFound, - /// Batched transaction failed - sent to operations that didn't cause the error BatchTransactionFailed, } -impl std::error::Error for Error {} +impl std::error::Error for StoreError {} -impl fmt::Display for Error { +impl fmt::Display for StoreError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Protocol(e) => e.fmt(f), @@ -83,44 +74,55 @@ impl fmt::Display for Error { } } -impl From for Error { +impl From for StoreError { fn from(e: nostr::error::Error) -> Self { Self::Protocol(e) } } -impl From for Error { +impl From for StoreError { fn from(e: io::Error) -> Self { Self::Io(e) } } -impl From for Error { +impl From for StoreError { fn from(e: heed::Error) -> Self { Self::Heed(e) } } -impl From for Error { +impl From for StoreError { fn from(e: flatbuffers::Error) -> Self { Self::FlatBuffers(e) } } -impl From for Error { +impl From for StoreError { fn from(e: JoinError) -> Self { Self::Thread(e) } } -impl From for Error { +impl From for StoreError { fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) } } -impl From for Error { +impl From for StoreError { fn from(e: oneshot::error::RecvError) -> Self { Self::OneshotRecv(e) } } + +impl From for nostr_database::error::Error { + fn from(e: StoreError) -> Self { + match e { + StoreError::Protocol(e) => e.into(), + StoreError::Io(e) => e.into(), + StoreError::Migration(e) => Self::migration(e), + e => Self::storage(e), + } + } +} diff --git a/database/nostr-lmdb/src/store/ingester.rs b/database/nostr-lmdb/src/store/ingester.rs index 698326e54..02f8aa83c 100644 --- a/database/nostr-lmdb/src/store/ingester.rs +++ b/database/nostr-lmdb/src/store/ingester.rs @@ -18,7 +18,7 @@ use nostr::{Event, Filter}; use nostr_database::{FlatBufferBuilder, SaveEventStatus}; use tokio::sync::oneshot; -use super::error::Error; +use super::error::StoreError; use super::lmdb::Lmdb; /// Pre-allocated buffer size for FlatBufferBuilder @@ -29,20 +29,20 @@ const FLATBUFFER_CAPACITY: usize = 70_000; enum OperationResult { Reindex { - result: Result<(), Error>, - tx: Option>>, + result: Result<(), StoreError>, + tx: Option>>, }, Save { - result: Result, - tx: Option>>, + result: Result, + tx: Option>>, }, Delete { - result: Result<(), Error>, - tx: Option>>, + result: Result<(), StoreError>, + tx: Option>>, }, Wipe { - result: Result<(), Error>, - tx: Option>>, + result: Result<(), StoreError>, + tx: Option>>, }, } @@ -92,24 +92,24 @@ impl OperationResult { enum IngesterOperation { Reindex { - tx: Option>>, + tx: Option>>, }, SaveEvent { event: Event, - tx: Option>>, + tx: Option>>, }, Delete { filter: Filter, - tx: Option>>, + tx: Option>>, }, Wipe { - tx: Option>>, + tx: Option>>, }, } impl IngesterOperation { /// Create an error result for this operation type, moving the channel - fn into_error_result(self, error: Error) -> OperationResult { + fn into_error_result(self, error: StoreError) -> OperationResult { match self { Self::Reindex { tx } => OperationResult::Reindex { result: Err(error), @@ -137,7 +137,7 @@ pub(super) struct IngesterItem { impl IngesterItem { #[must_use] - pub(super) fn reindex() -> (Self, oneshot::Receiver>) { + pub(super) fn reindex() -> (Self, oneshot::Receiver>) { let (tx, rx) = oneshot::channel(); let item: Self = Self { operation: IngesterOperation::Reindex { tx: Some(tx) }, @@ -148,7 +148,7 @@ impl IngesterItem { #[must_use] pub(super) fn save_event_with_feedback( event: Event, - ) -> (Self, oneshot::Receiver>) { + ) -> (Self, oneshot::Receiver>) { let (tx, rx) = oneshot::channel(); let item: Self = Self { operation: IngesterOperation::SaveEvent { @@ -162,7 +162,7 @@ impl IngesterItem { #[must_use] pub(super) fn delete_with_feedback( filter: Filter, - ) -> (Self, oneshot::Receiver>) { + ) -> (Self, oneshot::Receiver>) { let (tx, rx) = oneshot::channel(); let item: Self = Self { operation: IngesterOperation::Delete { @@ -174,7 +174,7 @@ impl IngesterItem { } #[must_use] - pub(super) fn wipe_with_feedback() -> (Self, oneshot::Receiver>) { + pub(super) fn wipe_with_feedback() -> (Self, oneshot::Receiver>) { let (tx, rx) = oneshot::channel(); let item: Self = Self { operation: IngesterOperation::Wipe { tx: Some(tx) }, @@ -260,7 +260,7 @@ impl Ingester { for item in batch { results.push( item.operation - .into_error_result(Error::BatchTransactionFailed), + .into_error_result(StoreError::BatchTransactionFailed), ); } @@ -302,7 +302,7 @@ impl Ingester { for item in batch_iter { results.push( item.operation - .into_error_result(Error::BatchTransactionFailed), + .into_error_result(StoreError::BatchTransactionFailed), ); } @@ -354,13 +354,17 @@ fn mark_all_as_failed(results: &mut [OperationResult]) { for prev_result in results.iter_mut() { match prev_result { OperationResult::Reindex { result: res, .. } => { - *res = Err(Error::BatchTransactionFailed) + *res = Err(StoreError::BatchTransactionFailed) + } + OperationResult::Save { result: res, .. } => { + *res = Err(StoreError::BatchTransactionFailed) } - OperationResult::Save { result: res, .. } => *res = Err(Error::BatchTransactionFailed), OperationResult::Delete { result: res, .. } => { - *res = Err(Error::BatchTransactionFailed) + *res = Err(StoreError::BatchTransactionFailed) + } + OperationResult::Wipe { result: res, .. } => { + *res = Err(StoreError::BatchTransactionFailed) } - OperationResult::Wipe { result: res, .. } => *res = Err(Error::BatchTransactionFailed), } } } diff --git a/database/nostr-lmdb/src/store/lmdb/mod.rs b/database/nostr-lmdb/src/store/lmdb/mod.rs index 79be6ccfb..79eace63a 100644 --- a/database/nostr-lmdb/src/store/lmdb/mod.rs +++ b/database/nostr-lmdb/src/store/lmdb/mod.rs @@ -18,7 +18,7 @@ use nostr_database::{FlatBufferBuilder, FlatBufferEncode, RejectedReason, SaveEv mod index; use self::index::EventIndexKeys; -use super::error::{Error, MigrationError}; +use super::error::{MigrationError, StoreError}; use super::filter::DatabaseFilter; use crate::NostrLmdbBuilder; @@ -113,7 +113,7 @@ pub(crate) struct Lmdb { } impl Lmdb { - pub(super) fn from_builder(builder: NostrLmdbBuilder) -> Result { + pub(super) fn from_builder(builder: NostrLmdbBuilder) -> Result { // Construct LMDB env let env: Env = unsafe { EnvOpenOptions::new() @@ -221,7 +221,7 @@ impl Lmdb { } /// Check database version and run migrations if needed - fn migrate(&self) -> Result<(), Error> { + fn migrate(&self) -> Result<(), StoreError> { let mut txn = self.write_txn()?; // Get current database version (defaults to 0 if not set) @@ -254,7 +254,7 @@ impl Lmdb { } Ordering::Greater => { txn.abort(); - Err(Error::Migration(MigrationError::NewerVersion { + Err(StoreError::Migration(MigrationError::NewerVersion { current_version, new_version: DB_VERSION, })) @@ -263,7 +263,7 @@ impl Lmdb { } /// Migrate from version 1 to version 2: Build kc_index - fn migrate_v1_to_v2(&self, txn: &mut RwTxn) -> Result<(), Error> { + fn migrate_v1_to_v2(&self, txn: &mut RwTxn) -> Result<(), StoreError> { tracing::info!("Building kc_index for existing events..."); let event_count = self.events.len(txn)?; @@ -299,7 +299,7 @@ impl Lmdb { /// /// This should never block the current thread #[inline] - pub(crate) fn read_txn(&self) -> Result, Error> { + pub(crate) fn read_txn(&self) -> Result, StoreError> { Ok(self.env.read_txn()?) } @@ -307,7 +307,7 @@ impl Lmdb { /// /// This blocks the current thread if there is another write txn #[inline] - pub(crate) fn write_txn(&self) -> Result, Error> { + pub(crate) fn write_txn(&self) -> Result, StoreError> { Ok(self.env.write_txn()?) } @@ -317,7 +317,7 @@ impl Lmdb { txn: &mut RwTxn, fbb: &mut FlatBufferBuilder, event: &Event, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // Store event self.events .put(txn, event.id.as_bytes(), event.encode(fbb))?; @@ -328,7 +328,7 @@ impl Lmdb { self.index_event(txn, index) } - fn index_event(&self, txn: &mut RwTxn, index: EventIndexKeys) -> Result<(), Error> { + fn index_event(&self, txn: &mut RwTxn, index: EventIndexKeys) -> Result<(), StoreError> { self.ci_index.put(txn, &index.ci_index, &index.id)?; self.akc_index.put(txn, &index.akc_index, &index.id)?; self.ac_index.put(txn, &index.ac_index, &index.id)?; @@ -360,7 +360,7 @@ impl Lmdb { /// - Check if the event exists /// /// It only performs the mechanical deletion from all indexes. - fn remove(&self, txn: &mut RwTxn, index: &EventIndexKeys) -> Result<(), Error> { + fn remove(&self, txn: &mut RwTxn, index: &EventIndexKeys) -> Result<(), StoreError> { self.events.delete(txn, &index.id)?; self.ci_index.delete(txn, &index.ci_index)?; self.akc_index.delete(txn, &index.akc_index)?; @@ -377,7 +377,7 @@ impl Lmdb { Ok(()) } - pub(crate) fn wipe(&self, txn: &mut RwTxn) -> Result<(), Error> { + pub(crate) fn wipe(&self, txn: &mut RwTxn) -> Result<(), StoreError> { // Wipe events self.events.clear(txn)?; @@ -387,7 +387,7 @@ impl Lmdb { Ok(()) } - fn wipe_indexes(&self, txn: &mut RwTxn) -> Result<(), Error> { + fn wipe_indexes(&self, txn: &mut RwTxn) -> Result<(), StoreError> { self.ci_index.clear(txn)?; self.tc_index.clear(txn)?; self.ac_index.clear(txn)?; @@ -401,7 +401,7 @@ impl Lmdb { Ok(()) } - pub(super) fn reindex(&self, txn: &mut RwTxn) -> Result<(), Error> { + pub(super) fn reindex(&self, txn: &mut RwTxn) -> Result<(), StoreError> { // First, wipe all indexes self.wipe_indexes(txn)?; @@ -429,7 +429,7 @@ impl Lmdb { } #[inline] - pub(crate) fn has_event(&self, txn: &RoTxn, event_id: &EventId) -> Result { + pub(crate) fn has_event(&self, txn: &RoTxn, event_id: &EventId) -> Result { Ok(self.get_event_by_id(txn, event_id.as_bytes())?.is_some()) } @@ -439,7 +439,7 @@ impl Lmdb { txn: &mut RwTxn, fbb: &mut FlatBufferBuilder, event: &Event, - ) -> Result { + ) -> Result { if event.kind.is_ephemeral() { return Ok(SaveEventStatus::Rejected(RejectedReason::Ephemeral)); } @@ -525,7 +525,7 @@ impl Lmdb { &self, txn: &'a RoTxn, event_id: &[u8], - ) -> Result>, Error> { + ) -> Result>, StoreError> { match self.events.get(txn, event_id)? { Some(bytes) => Ok(Some(EventBorrow::decode(bytes)?)), None => Ok(None), @@ -533,7 +533,7 @@ impl Lmdb { } /// Delete events - pub fn delete(&self, txn: &mut RwTxn, filter: Filter) -> Result<(), Error> { + pub fn delete(&self, txn: &mut RwTxn, filter: Filter) -> Result<(), StoreError> { // First, collect all deletion info while we have immutable borrows let indexes: Vec = { let events = self.query(txn, filter)?; @@ -551,7 +551,7 @@ impl Lmdb { Ok(()) } - pub fn count(&self, txn: &RoTxn, filter: Filter) -> Result { + pub fn count(&self, txn: &RoTxn, filter: Filter) -> Result { // Check if we can use fast counting let can_fast_count: bool = filter.ids.is_none() && filter.authors.is_none() @@ -598,7 +598,7 @@ impl Lmdb { &'a self, txn: &'a RoTxn, filter: Filter, - ) -> Result> + 'a>, Error> { + ) -> Result> + 'a>, StoreError> { if let (Some(since), Some(until)) = (filter.since, filter.until) { if since > until { return Ok(Box::new(iter::empty())); @@ -661,7 +661,7 @@ impl Lmdb { filter: DatabaseFilter, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // Fetch by id for id in filter.ids.iter() { // Check if limit is set @@ -690,7 +690,7 @@ impl Lmdb { until: Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // We may bring since forward if we hit the limit without going back that // far, so we use a mutable since: let mut since: Timestamp = since; @@ -705,7 +705,9 @@ impl Lmdb { 'per_event: for result in iter { let (_key, value) = result?; - let event = self.get_event_by_id(txn, value)?.ok_or(Error::NotFound)?; + let event = self + .get_event_by_id(txn, value)? + .ok_or(StoreError::NotFound)?; // If we have gone beyond since, we can stop early // (We have to check because `since` might change in this loop) @@ -757,7 +759,7 @@ impl Lmdb { until: Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // We may bring since forward if we hit the limit without going back that // far, so we use a mutable since: let mut since: Timestamp = since; @@ -787,7 +789,7 @@ impl Lmdb { until: Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // We may bring since forward if we hit the limit without going back that // far, so we use a mutable since: let mut since: Timestamp = since; @@ -841,7 +843,7 @@ impl Lmdb { until: Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // We may bring since forward if we hit the limit without going back that // far, so we use a mutable since: let mut since: Timestamp = since; @@ -871,7 +873,7 @@ impl Lmdb { until: Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // We may bring since forward if we hit the limit without going back that // far, so we use a mutable since: let mut since: Timestamp = since; @@ -899,7 +901,7 @@ impl Lmdb { until: Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // We may bring since forward if we hit the limit without going back that // far, so we use a mutable since: let mut since: Timestamp = since; @@ -923,7 +925,7 @@ impl Lmdb { until: Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // We may bring since forward if we hit the limit without going back that // far, so we use a mutable since: let mut since: Timestamp = since; @@ -949,7 +951,7 @@ impl Lmdb { since: Timestamp, until: Timestamp, limit: Option, - ) -> Result> + 'a>, Error> { + ) -> Result> + 'a>, StoreError> { // Iterate over created _at index, so events are already sorted Ok(Box::new( self.ci_iter(txn, since, until)? @@ -975,7 +977,7 @@ impl Lmdb { since: &mut Timestamp, limit: Option, output: &mut BTreeSet>, - ) -> Result<(), Error> + ) -> Result<(), StoreError> where I: IntoIterator, { @@ -983,7 +985,7 @@ impl Lmdb { for id in iter { // Get event by ID - let event = self.get_event_by_id(txn, id)?.ok_or(Error::NotFound)?; + let event = self.get_event_by_id(txn, id)?.ok_or(StoreError::NotFound)?; if event.created_at < *since { break; @@ -1018,9 +1020,9 @@ impl Lmdb { txn: &'a RoTxn, author: &PublicKey, kind: Kind, - ) -> Result>, Error> { + ) -> Result>, StoreError> { if !kind.is_replaceable() { - return Err(Error::WrongEventKind); + return Err(StoreError::WrongEventKind); } let mut iter = self.akc_iter( @@ -1043,9 +1045,9 @@ impl Lmdb { &'a self, txn: &'a RoTxn, addr: &Coordinate, - ) -> Result>, Error> { + ) -> Result>, StoreError> { if !addr.kind.is_addressable() { - return Err(Error::WrongEventKind); + return Err(StoreError::WrongEventKind); } let iter = self.atc_iter( @@ -1059,7 +1061,7 @@ impl Lmdb { for result in iter { let (_key, id) = result?; - let event = self.get_event_by_id(txn, id)?.ok_or(Error::NotFound)?; + let event = self.get_event_by_id(txn, id)?.ok_or(StoreError::NotFound)?; // the atc index doesn't have kind, so we have to compare the kinds if event.kind != addr.kind.as_u16() { @@ -1079,9 +1081,9 @@ impl Lmdb { txn: &mut RwTxn, coordinate: &Coordinate, until: Timestamp, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { if !coordinate.kind.is_replaceable() { - return Err(Error::WrongEventKind); + return Err(StoreError::WrongEventKind); } let iter = self.akc_iter( @@ -1117,9 +1119,9 @@ impl Lmdb { txn: &mut RwTxn, coordinate: &Coordinate, until: Timestamp, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { if !coordinate.kind.is_addressable() { - return Err(Error::WrongEventKind); + return Err(StoreError::WrongEventKind); } let iter = self.atc_iter( @@ -1153,11 +1155,15 @@ impl Lmdb { } #[inline] - pub(crate) fn is_deleted(&self, txn: &RoTxn, event_id: &EventId) -> Result { + pub(crate) fn is_deleted(&self, txn: &RoTxn, event_id: &EventId) -> Result { Ok(self.deleted_ids.get(txn, event_id.as_bytes())?.is_some()) } - pub(crate) fn mark_deleted(&self, txn: &mut RwTxn, event_id: &EventId) -> Result<(), Error> { + pub(crate) fn mark_deleted( + &self, + txn: &mut RwTxn, + event_id: &EventId, + ) -> Result<(), StoreError> { self.deleted_ids.put(txn, event_id.as_bytes(), &())?; Ok(()) } @@ -1167,7 +1173,7 @@ impl Lmdb { txn: &mut RwTxn, coordinate: &Coordinate, when: Timestamp, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { let key: Vec = index::make_coordinate_index_key(coordinate); self.deleted_coordinates.put(txn, &key, &when.as_secs())?; Ok(()) @@ -1177,7 +1183,7 @@ impl Lmdb { &self, txn: &RoTxn, coordinate: &Coordinate, - ) -> Result, Error> { + ) -> Result, StoreError> { let key: Vec = index::make_coordinate_index_key(coordinate); Ok(self .deleted_coordinates @@ -1189,13 +1195,17 @@ impl Lmdb { &self, txn: &mut RwTxn, pk: &PublicKey, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { self.vanished_public_keys.put(txn, pk.as_bytes(), &())?; Ok(()) } #[inline] - pub(crate) fn is_pubkey_vanished(&self, txn: &RoTxn, pk: &PublicKey) -> Result { + pub(crate) fn is_pubkey_vanished( + &self, + txn: &RoTxn, + pk: &PublicKey, + ) -> Result { Ok(self.vanished_public_keys.get(txn, pk.as_bytes())?.is_some()) } @@ -1203,7 +1213,7 @@ impl Lmdb { &self, txn: &mut RwTxn, pubkey: &PublicKey, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { // Mark public key as vanished self.mark_pubkey_vanished(txn, pubkey)?; // Delete all authored events @@ -1220,7 +1230,7 @@ impl Lmdb { txn: &'a RoTxn, since: Timestamp, until: Timestamp, - ) -> Result, Error> { + ) -> Result, StoreError> { let start_prefix = index::make_ci_index_key(until, &EVENT_ID_ALL_ZEROS); let end_prefix = index::make_ci_index_key(since, &EVENT_ID_ALL_255); let range = ( @@ -1237,7 +1247,7 @@ impl Lmdb { tag_value: &str, since: Timestamp, until: Timestamp, - ) -> Result, Error> { + ) -> Result, StoreError> { let start_prefix = index::make_tc_index_key( tag_name, tag_value, @@ -1258,7 +1268,7 @@ impl Lmdb { author: &[u8; 32], since: Timestamp, until: Timestamp, - ) -> Result, Error> { + ) -> Result, StoreError> { let start_prefix = index::make_ac_index_key(author, until, &EVENT_ID_ALL_ZEROS); let end_prefix = index::make_ac_index_key(author, since, &EVENT_ID_ALL_255); let range = ( @@ -1275,7 +1285,7 @@ impl Lmdb { kind: u16, since: Timestamp, until: Timestamp, - ) -> Result, Error> { + ) -> Result, StoreError> { let start_prefix = index::make_akc_index_key(author, kind, until, &EVENT_ID_ALL_ZEROS); let end_prefix = index::make_akc_index_key(author, kind, since, &EVENT_ID_ALL_255); let range = ( @@ -1291,7 +1301,7 @@ impl Lmdb { kind: u16, since: Timestamp, until: Timestamp, - ) -> Result, Error> { + ) -> Result, StoreError> { let start_prefix = index::make_kc_index_key(kind, until, &EVENT_ID_ALL_ZEROS); let end_prefix = index::make_kc_index_key(kind, since, &EVENT_ID_ALL_255); let range = ( @@ -1309,7 +1319,7 @@ impl Lmdb { tag_value: &str, since: Timestamp, until: Timestamp, - ) -> Result, Error> { + ) -> Result, StoreError> { let start_prefix: Vec = index::make_atc_index_key( author, tag_name, @@ -1326,7 +1336,7 @@ impl Lmdb { Ok(self.atc_index.range(txn, &range)?) } - fn handle_deletion_event(&self, txn: &mut RwTxn, event: &Event) -> Result { + fn handle_deletion_event(&self, txn: &mut RwTxn, event: &Event) -> Result { // Collect DeletionInfo and EventIds for all valid targets first let mut deletions_to_process = Vec::new(); @@ -1378,7 +1388,7 @@ impl Lmdb { tag_value: &str, since: Timestamp, until: Timestamp, - ) -> Result, Error> { + ) -> Result, StoreError> { let start_prefix = index::make_ktc_index_key( kind, tag_name, @@ -1524,7 +1534,7 @@ mod tests { let result = Lmdb::from_builder(lmdb_builder); assert!(matches!( result.unwrap_err(), - Error::Migration(MigrationError::NewerVersion { .. }) + StoreError::Migration(MigrationError::NewerVersion { .. }) )); } } diff --git a/database/nostr-lmdb/src/store/mod.rs b/database/nostr-lmdb/src/store/mod.rs index 6a28e0631..fbe58821b 100644 --- a/database/nostr-lmdb/src/store/mod.rs +++ b/database/nostr-lmdb/src/store/mod.rs @@ -15,7 +15,7 @@ mod filter; mod ingester; pub(crate) mod lmdb; -use self::error::Error; +use self::error::StoreError; use self::ingester::{Ingester, IngesterItem}; use self::lmdb::Lmdb; use crate::NostrLmdbBuilder; @@ -27,7 +27,7 @@ pub(super) struct Store { } impl Store { - pub(super) async fn from_builder(builder: NostrLmdbBuilder) -> Result { + pub(super) async fn from_builder(builder: NostrLmdbBuilder) -> Result { // Open the database in a blocking task let db: Lmdb = task::spawn_blocking(move || { // Create the directory if it doesn't exist @@ -35,7 +35,7 @@ impl Store { let db: Lmdb = Lmdb::from_builder(builder)?; - Ok::(db) + Ok::(db) }) .await??; @@ -46,7 +46,7 @@ impl Store { } #[inline] - async fn interact(&self, f: F) -> Result + async fn interact(&self, f: F) -> Result where F: FnOnce(Lmdb) -> R + Send + 'static, R: Send + 'static, @@ -56,19 +56,23 @@ impl Store { Ok(task::spawn_blocking(move || f(db)).await?) } - pub(crate) async fn reindex(&self) -> Result<(), Error> { + pub(crate) async fn reindex(&self) -> Result<(), StoreError> { let (item, rx) = IngesterItem::reindex(); - self.ingester.send(item).map_err(|_| Error::FlumeSend)?; + self.ingester + .send(item) + .map_err(|_| StoreError::FlumeSend)?; rx.await? } - pub(super) async fn save_event(&self, event: &Event) -> Result { + pub(super) async fn save_event(&self, event: &Event) -> Result { let (item, rx) = IngesterItem::save_event_with_feedback(event.clone()); - self.ingester.send(item).map_err(|_| Error::FlumeSend)?; + self.ingester + .send(item) + .map_err(|_| StoreError::FlumeSend)?; rx.await? } - pub(super) async fn get_event_by_id(&self, id: EventId) -> Result, Error> { + pub(super) async fn get_event_by_id(&self, id: EventId) -> Result, StoreError> { self.interact(move |db| { let txn = db.read_txn()?; let event: Option = db @@ -80,7 +84,7 @@ impl Store { .await? } - pub(super) async fn check_id(&self, id: EventId) -> Result { + pub(super) async fn check_id(&self, id: EventId) -> Result { self.interact(move |db| { let txn = db.read_txn()?; @@ -99,7 +103,7 @@ impl Store { .await? } - pub(super) async fn count(&self, filter: Filter) -> Result { + pub(super) async fn count(&self, filter: Filter) -> Result { self.interact(move |db| { let txn = db.read_txn()?; let len: usize = db.count(&txn, filter)?; @@ -110,7 +114,7 @@ impl Store { } // Lookup ID: EVENT_ORD_IMPL - pub(super) async fn query(&self, filter: Filter) -> Result { + pub(super) async fn query(&self, filter: Filter) -> Result { self.interact(move |db| { let mut events: Events = Events::new(&filter); @@ -127,7 +131,7 @@ impl Store { pub(super) async fn negentropy_items( &self, filter: Filter, - ) -> Result, Error> { + ) -> Result, StoreError> { let txn = self.db.read_txn()?; let events = self.db.query(&txn, filter)?; let items = events @@ -138,15 +142,19 @@ impl Store { Ok(items) } - pub(super) async fn delete(&self, filter: Filter) -> Result<(), Error> { + pub(super) async fn delete(&self, filter: Filter) -> Result<(), StoreError> { let (item, rx) = IngesterItem::delete_with_feedback(filter); - self.ingester.send(item).map_err(|_| Error::FlumeSend)?; + self.ingester + .send(item) + .map_err(|_| StoreError::FlumeSend)?; rx.await? } - pub(super) async fn wipe(&self) -> Result<(), Error> { + pub(super) async fn wipe(&self) -> Result<(), StoreError> { let (item, rx) = IngesterItem::wipe_with_feedback(); - self.ingester.send(item).map_err(|_| Error::FlumeSend)?; + self.ingester + .send(item) + .map_err(|_| StoreError::FlumeSend)?; rx.await? } } diff --git a/database/nostr-memory/src/lib.rs b/database/nostr-memory/src/lib.rs index fbb7e32cb..b70b79f56 100644 --- a/database/nostr-memory/src/lib.rs +++ b/database/nostr-memory/src/lib.rs @@ -10,6 +10,7 @@ use core::num::NonZeroUsize; use nostr::prelude::*; +use nostr_database::error::Error; use nostr_database::prelude::*; use tokio::sync::RwLock; @@ -76,7 +77,7 @@ impl NostrDatabase for MemoryDatabase { fn save_event<'a>( &'a self, event: &'a Event, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let mut store = self.store.write().await; Ok(store.index_event(event)) @@ -86,7 +87,7 @@ impl NostrDatabase for MemoryDatabase { fn check_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let store = self.store.read().await; @@ -103,21 +104,21 @@ impl NostrDatabase for MemoryDatabase { fn event_by_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result, DatabaseError>> { + ) -> BoxedFuture<'a, Result, Error>> { Box::pin(async move { let store = self.store.read().await; Ok(store.event_by_id(event_id).cloned()) }) } - fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { + fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { let store = self.store.read().await; Ok(store.count(filter)) }) } - fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { + fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { let store = self.store.read().await; let mut events = Events::new(&filter); @@ -129,14 +130,14 @@ impl NostrDatabase for MemoryDatabase { fn negentropy_items( &self, filter: Filter, - ) -> BoxedFuture<'_, Result, DatabaseError>> { + ) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { let store = self.store.read().await; Ok(store.negentropy_items(filter)) }) } - fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), DatabaseError>> { + fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), Error>> { Box::pin(async move { let mut store = self.store.write().await; store.delete(filter); @@ -144,7 +145,7 @@ impl NostrDatabase for MemoryDatabase { }) } - fn wipe(&self) -> BoxedFuture<'_, Result<(), DatabaseError>> { + fn wipe(&self) -> BoxedFuture<'_, Result<(), Error>> { Box::pin(async move { let mut store = self.store.write().await; store.clear(); diff --git a/database/nostr-ndb/src/lib.rs b/database/nostr-ndb/src/lib.rs index b07de300b..3f868ddac 100644 --- a/database/nostr-ndb/src/lib.rs +++ b/database/nostr-ndb/src/lib.rs @@ -10,7 +10,6 @@ #![allow(clippy::mutable_key_type)] // TODO: remove when possible. Needed to suppress false positive for async_trait use std::borrow::Cow; -use std::io::{Error, ErrorKind}; use std::ops::{Deref, DerefMut}; use std::path::Path; @@ -18,6 +17,7 @@ pub extern crate nostr; pub extern crate nostr_database as database; pub extern crate nostrdb; +use nostr_database::error::{Error, ErrorKind}; use nostr_database::prelude::*; use nostrdb::{ Config, Filter as NdbFilter, IngestMetadata, Ndb, NdbStrVariant, Note, QueryResult, Transaction, @@ -35,19 +35,19 @@ pub struct NdbDatabase { impl NdbDatabase { /// Open nostrdb - pub fn open

(path: P) -> Result + pub fn open

(path: P) -> Result where P: AsRef, { let path: &Path = path.as_ref(); - let path: &str = path.to_str().ok_or_else(|| { - DatabaseError::backend(Error::new(ErrorKind::InvalidInput, "path is not valid ")) - })?; + let path: &str = path + .to_str() + .ok_or_else(|| Error::with_static_message(ErrorKind::Other, "path is not valid"))?; let config: Config = Config::new(); Ok(Self { - db: Ndb::new(path, &config).map_err(DatabaseError::backend)?, + db: Ndb::new(path, &config).map_err(Error::storage)?, }) } } @@ -89,7 +89,7 @@ impl NostrDatabase for NdbDatabase { fn save_event<'a>( &'a self, event: &'a Event, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let msg = RelayMessage::Event { subscription_id: Cow::Owned(SubscriptionId::new("ndb")), @@ -98,7 +98,7 @@ impl NostrDatabase for NdbDatabase { let json: String = msg.as_json(); self.db .process_event_with(&json, IngestMetadata::new()) - .map_err(DatabaseError::backend)?; + .map_err(Error::storage)?; // TODO: shouldn't return a success since we don't know if the ingestion was successful or not. Ok(SaveEventStatus::Success) }) @@ -107,9 +107,9 @@ impl NostrDatabase for NdbDatabase { fn check_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { - let txn = Transaction::new(&self.db).map_err(DatabaseError::backend)?; + let txn = Transaction::new(&self.db).map_err(Error::storage)?; let res = self.db.get_note_by_id(&txn, event_id.as_bytes()); Ok(if res.is_ok() { DatabaseEventStatus::Saved @@ -122,30 +122,30 @@ impl NostrDatabase for NdbDatabase { fn event_by_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result, DatabaseError>> { + ) -> BoxedFuture<'a, Result, Error>> { Box::pin(async move { - let txn: Transaction = Transaction::new(&self.db).map_err(DatabaseError::backend)?; + let txn: Transaction = Transaction::new(&self.db).map_err(Error::storage)?; let res: Result = self.db.get_note_by_id(&txn, event_id.as_bytes()); match res { Ok(note) => Ok(Some(ndb_note_to_event(note)?.into_owned())), Err(nostrdb::Error::NotFound) => Ok(None), - Err(e) => Err(DatabaseError::backend(e)), + Err(e) => Err(Error::storage(e)), } }) } - fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { + fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { - let txn: Transaction = Transaction::new(&self.db).map_err(DatabaseError::backend)?; + let txn: Transaction = Transaction::new(&self.db).map_err(Error::storage)?; let res: Vec = ndb_query(&self.db, &txn, &filter)?; Ok(res.len()) }) } - fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { + fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { - let txn: Transaction = Transaction::new(&self.db).map_err(DatabaseError::backend)?; + let txn: Transaction = Transaction::new(&self.db).map_err(Error::storage)?; let mut events: Events = Events::new(&filter); let res: Vec = ndb_query(&self.db, &txn, &filter)?; events.extend( @@ -160,9 +160,9 @@ impl NostrDatabase for NdbDatabase { fn negentropy_items( &self, filter: Filter, - ) -> BoxedFuture<'_, Result, DatabaseError>> { + ) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { - let txn: Transaction = Transaction::new(&self.db).map_err(DatabaseError::backend)?; + let txn: Transaction = Transaction::new(&self.db).map_err(Error::storage)?; let res: Vec = ndb_query(&self.db, &txn, &filter)?; Ok(res .into_iter() @@ -171,13 +171,14 @@ impl NostrDatabase for NdbDatabase { }) } - fn delete(&self, _filter: Filter) -> BoxedFuture<'_, Result<(), DatabaseError>> { - Box::pin(async move { Err(DatabaseError::NotSupported) }) + #[inline] + fn delete(&self, _filter: Filter) -> BoxedFuture<'_, Result<(), Error>> { + Box::pin(async move { Err(Error::unsupported("delete is not supported by nostrdb")) }) } #[inline] - fn wipe(&self) -> BoxedFuture<'_, Result<(), DatabaseError>> { - Box::pin(async move { Err(DatabaseError::NotSupported) }) + fn wipe(&self) -> BoxedFuture<'_, Result<(), Error>> { + Box::pin(async move { Err(Error::unsupported("wiping is not supported by nostrdb")) }) } } @@ -185,7 +186,7 @@ fn ndb_query<'a>( db: &Ndb, txn: &'a Transaction, filter: &Filter, -) -> Result>, DatabaseError> { +) -> Result>, Error> { let filter: nostrdb::Filter = ndb_filter_conversion(filter); let max_results = filter .limit() @@ -193,7 +194,7 @@ fn ndb_query<'a>( .unwrap_or(MAX_RESULTS); db.query(txn, &[filter], max_results) - .map_err(DatabaseError::backend) + .map_err(Error::storage) } fn ndb_filter_conversion(f: &Filter) -> nostrdb::Filter { @@ -242,19 +243,22 @@ fn ndb_filter_conversion(f: &Filter) -> nostrdb::Filter { filter.build() } -fn ndb_note_to_event(note: Note) -> Result { +fn ndb_note_to_event(note: Note) -> Result { Ok(EventBorrow { id: note.id(), pubkey: note.pubkey(), created_at: Timestamp::from(note.created_at()), - kind: note.kind().try_into().map_err(DatabaseError::backend)?, + kind: note + .kind() + .try_into() + .map_err(|e| Error::new(ErrorKind::Protocol, e))?, tags: ndb_note_to_tags(¬e)?, content: note.content(), sig: note.sig(), }) } -fn ndb_note_to_tags<'a>(note: &Note<'a>) -> Result>, DatabaseError> { +fn ndb_note_to_tags<'a>(note: &Note<'a>) -> Result>, Error> { let ndb_tags = note.tags(); let mut tags: Vec> = Vec::with_capacity(ndb_tags.count() as usize); for tag in ndb_tags.iter() { @@ -265,7 +269,7 @@ fn ndb_note_to_tags<'a>(note: &Note<'a>) -> Result>, DatabaseErro NdbStrVariant::Str(s) => Cow::Borrowed(s), }) .collect(); - let tag = CowTag::parse(tag_str).map_err(DatabaseError::backend)?; + let tag = CowTag::parse(tag_str)?; tags.push(tag); } Ok(tags) diff --git a/database/nostr-sqlite/src/builder.rs b/database/nostr-sqlite/src/builder.rs index 329e0d8f4..a05c04df3 100644 --- a/database/nostr-sqlite/src/builder.rs +++ b/database/nostr-sqlite/src/builder.rs @@ -3,8 +3,8 @@ use std::path::{Path, PathBuf}; use nostr::types::RelayUrl; +use nostr_database::error::Error; -use crate::error::Error; use crate::store::NostrSqlite; #[derive(Default)] diff --git a/database/nostr-sqlite/src/error.rs b/database/nostr-sqlite/src/error.rs index ad266be6a..34e9f7e72 100644 --- a/database/nostr-sqlite/src/error.rs +++ b/database/nostr-sqlite/src/error.rs @@ -6,11 +6,8 @@ use std::num::TryFromIntError; use async_utility::tokio::task::JoinError; use nostr::secp256k1; -/// Migration error #[derive(Debug, Clone, PartialEq, Eq)] -pub enum MigrationError { - /// Migration is in a dirty state - Dirty(i64), +pub(crate) enum MigrationError { /// Database version is newer than supported one NewerVersion { /// Current database version @@ -25,7 +22,6 @@ impl std::error::Error for MigrationError {} impl fmt::Display for MigrationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Dirty(version) => write!(f, "migration {version} is partially applied"), Self::NewerVersion { current, supported } => write!( f, "database version {current} is newer than supported version {supported}" @@ -34,30 +30,20 @@ impl fmt::Display for MigrationError { } } -/// Nostr SQL error #[derive(Debug)] -pub enum Error { - /// Nostr protocol error +pub(crate) enum StoreError { Protocol(nostr::error::Error), - /// TryFromInt error TryFromInt(TryFromIntError), - /// Rusqlite error Rusqlite(rusqlite::Error), - /// Migration error Migration(MigrationError), - /// Thread error Thread(JoinError), - /// JSON error Json(serde_json::Error), - /// Secp256k1 error Secp256k1(secp256k1::Error), - /// Mutex poisoned - MutexPoisoned, } -impl std::error::Error for Error {} +impl std::error::Error for StoreError {} -impl fmt::Display for Error { +impl fmt::Display for StoreError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Protocol(e) => e.fmt(f), @@ -67,49 +53,58 @@ impl fmt::Display for Error { Self::Thread(e) => write!(f, "{e}"), Self::Json(e) => write!(f, "{e}"), Self::Secp256k1(e) => write!(f, "{e}"), - Self::MutexPoisoned => f.write_str("mutex is poisoned"), } } } -impl From for Error { +impl From for StoreError { fn from(e: nostr::error::Error) -> Self { Self::Protocol(e) } } -impl From for Error { +impl From for StoreError { fn from(e: TryFromIntError) -> Self { Self::TryFromInt(e) } } -impl From for Error { +impl From for StoreError { fn from(e: rusqlite::Error) -> Self { Self::Rusqlite(e) } } -impl From for Error { +impl From for StoreError { fn from(e: MigrationError) -> Self { Self::Migration(e) } } -impl From for Error { +impl From for StoreError { fn from(e: JoinError) -> Self { Self::Thread(e) } } -impl From for Error { +impl From for StoreError { fn from(e: serde_json::Error) -> Self { Self::Json(e) } } -impl From for Error { +impl From for StoreError { fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) } } + +impl From for nostr_database::error::Error { + fn from(error: StoreError) -> Self { + match error { + StoreError::Protocol(e) => e.into(), + StoreError::Migration(e) => Self::migration(e), + e => Self::storage(e), + } + } +} diff --git a/database/nostr-sqlite/src/lib.rs b/database/nostr-sqlite/src/lib.rs index d964f8e30..7470475b0 100644 --- a/database/nostr-sqlite/src/lib.rs +++ b/database/nostr-sqlite/src/lib.rs @@ -7,7 +7,7 @@ #![doc = include_str!("../README.md")] pub mod builder; -pub mod error; +mod error; mod migration; mod model; mod pool; diff --git a/database/nostr-sqlite/src/migration.rs b/database/nostr-sqlite/src/migration.rs index cbd165c2b..749d1179b 100644 --- a/database/nostr-sqlite/src/migration.rs +++ b/database/nostr-sqlite/src/migration.rs @@ -2,11 +2,11 @@ use std::cmp::Ordering; use rusqlite::Transaction; -use crate::error::{Error, MigrationError}; +use crate::error::{MigrationError, StoreError}; const DB_VERSION: i64 = 2; -pub(super) fn run(tx: &Transaction<'_>) -> Result<(), Error> { +pub(super) fn run(tx: &Transaction<'_>) -> Result<(), StoreError> { // Get the current version let mut curr_version: i64 = curr_db_version(tx)?; @@ -35,23 +35,23 @@ pub(super) fn run(tx: &Transaction<'_>) -> Result<(), Error> { Ok(()) } -fn curr_db_version(tx: &Transaction<'_>) -> Result { +fn curr_db_version(tx: &Transaction<'_>) -> Result { let version: i64 = tx.query_row("PRAGMA user_version", [], |row| row.get(0))?; Ok(version) } -fn set_db_version(tx: &Transaction<'_>, version: i64) -> Result<(), Error> { +fn set_db_version(tx: &Transaction<'_>, version: i64) -> Result<(), StoreError> { tx.pragma_update(None, "user_version", version)?; Ok(()) } -fn mig_init(tx: &Transaction<'_>) -> Result { +fn mig_init(tx: &Transaction<'_>) -> Result { tx.execute_batch(include_str!("../migrations/001_init.sql"))?; set_db_version(tx, 1)?; Ok(1) } -fn mig_1_to_2(tx: &Transaction<'_>) -> Result { +fn mig_1_to_2(tx: &Transaction<'_>) -> Result { tx.execute_batch(include_str!("../migrations/002_vanished_public_keys.sql"))?; set_db_version(tx, 2)?; Ok(2) diff --git a/database/nostr-sqlite/src/model.rs b/database/nostr-sqlite/src/model.rs index a661a6c4d..d88b7c060 100644 --- a/database/nostr-sqlite/src/model.rs +++ b/database/nostr-sqlite/src/model.rs @@ -3,7 +3,7 @@ use nostr::secp256k1::schnorr::Signature; use nostr::{EventId, Kind, PublicKey, SingleLetterTag, Tags, Timestamp}; use rusqlite::Row; -use crate::error::Error; +use crate::error::StoreError; #[derive(Debug, Clone)] pub(crate) struct EventDb { @@ -30,7 +30,7 @@ impl EventDb { } #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_event(self) -> Result { + pub(crate) fn to_event(self) -> Result { let id: EventId = EventId::from_slice(&self.id)?; let pubkey: PublicKey = PublicKey::from_slice(&self.pubkey)?; let created_at: Timestamp = self.created_at.try_into()?; diff --git a/database/nostr-sqlite/src/pool.rs b/database/nostr-sqlite/src/pool.rs index 6fdd3c41f..e9cb2f593 100644 --- a/database/nostr-sqlite/src/pool.rs +++ b/database/nostr-sqlite/src/pool.rs @@ -8,7 +8,7 @@ use async_utility::task; use rusqlite::{Connection, OpenFlags}; use tokio::sync::Mutex; -use crate::error::Error; +use crate::error::StoreError; use crate::store::NostrSqliteOptions; #[derive(Debug, Clone)] @@ -25,7 +25,7 @@ impl Pool { } } - pub(crate) fn open_in_memory(options: NostrSqliteOptions) -> Result { + pub(crate) fn open_in_memory(options: NostrSqliteOptions) -> Result { let conn: Connection = Connection::open_in_memory()?; Ok(Self::new(conn, options)) } @@ -35,7 +35,7 @@ impl Pool { pub(crate) async fn open_with_path( path: PathBuf, options: NostrSqliteOptions, - ) -> Result { + ) -> Result { let conn: Connection = task::spawn_blocking(move || Connection::open(path)).await??; Ok(Self::new(conn, options)) } @@ -44,7 +44,7 @@ impl Pool { path: P, vfs: &str, options: NostrSqliteOptions, - ) -> Result + ) -> Result where P: AsRef, { @@ -54,9 +54,9 @@ impl Pool { } #[cfg(not(target_arch = "wasm32"))] - pub async fn interact(&self, f: F) -> Result + pub(crate) async fn interact(&self, f: F) -> Result where - F: FnOnce(&mut Connection) -> Result + Send + 'static, + F: FnOnce(&mut Connection) -> Result + Send + 'static, R: Send + 'static, { let arc: Arc> = self.conn.clone(); @@ -65,9 +65,9 @@ impl Pool { } #[cfg(target_arch = "wasm32")] - pub async fn interact(&self, f: F) -> Result + pub(crate) async fn interact(&self, f: F) -> Result where - F: FnOnce(&mut Connection) -> Result + 'static, + F: FnOnce(&mut Connection) -> Result + 'static, R: 'static, { let mut conn = self.conn.lock().await; @@ -75,9 +75,9 @@ impl Pool { } #[cfg(not(target_arch = "wasm32"))] - pub async fn interact_options(&self, f: F) -> Result + pub(crate) async fn interact_options(&self, f: F) -> Result where - F: FnOnce(&mut Connection, &NostrSqliteOptions) -> Result + Send + 'static, + F: FnOnce(&mut Connection, &NostrSqliteOptions) -> Result + Send + 'static, R: Send + 'static, { let arc: Arc> = self.conn.clone(); @@ -87,9 +87,9 @@ impl Pool { } #[cfg(target_arch = "wasm32")] - pub async fn interact_options(&self, f: F) -> Result + pub(crate) async fn interact_options(&self, f: F) -> Result where - F: FnOnce(&mut Connection, &NostrSqliteOptions) -> Result + 'static, + F: FnOnce(&mut Connection, &NostrSqliteOptions) -> Result + 'static, R: 'static, { let mut conn = self.conn.lock().await; diff --git a/database/nostr-sqlite/src/prelude.rs b/database/nostr-sqlite/src/prelude.rs index 91d1443d6..0a75cee93 100644 --- a/database/nostr-sqlite/src/prelude.rs +++ b/database/nostr-sqlite/src/prelude.rs @@ -7,6 +7,5 @@ pub use nostr_database::prelude::*; pub use crate::builder::*; -pub use crate::error::*; pub use crate::store::*; pub use crate::*; diff --git a/database/nostr-sqlite/src/store.rs b/database/nostr-sqlite/src/store.rs index 96c518bad..3e1301032 100644 --- a/database/nostr-sqlite/src/store.rs +++ b/database/nostr-sqlite/src/store.rs @@ -5,12 +5,13 @@ use std::path::Path; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; +use nostr_database::error::Error; use nostr_database::prelude::*; use rusqlite::types::Value; use rusqlite::{Connection, OptionalExtension, Transaction, params, params_from_iter}; use crate::builder::{DatabaseConnType, NostrSqliteBuilder}; -use crate::error::Error; +use crate::error::StoreError; use crate::migration; use crate::model::{EventDb, extract_tags}; use crate::pool::Pool; @@ -121,7 +122,7 @@ impl NostrSqlite { } /// Returns true if successfully inserted - fn insert_event_tx(tx: &Transaction<'_>, event: &Event) -> Result { + fn insert_event_tx(tx: &Transaction<'_>, event: &Event) -> Result { let tags = serde_json::to_string(&event.tags)?; let rows = tx.execute( @@ -140,7 +141,7 @@ impl NostrSqlite { Ok(rows > 0) } - fn handle_deletion_event(tx: &Transaction<'_>, event: &Event) -> Result { + fn handle_deletion_event(tx: &Transaction<'_>, event: &Event) -> Result { for id in event.tags.event_ids() { if let Some(pubkey) = Self::get_pubkey_of_event_by_id(tx, &id)? { // Author must match @@ -180,7 +181,7 @@ impl NostrSqlite { conn: &mut Connection, event: &Event, options: &NostrSqliteOptions, - ) -> Result { + ) -> Result { if event.kind.is_ephemeral() { return Ok(SaveEventStatus::Rejected(RejectedReason::Ephemeral)); } @@ -313,7 +314,7 @@ impl NostrSqlite { } } - fn mark_event_as_deleted(tx: &Transaction<'_>, id: &EventId) -> Result<(), Error> { + fn mark_event_as_deleted(tx: &Transaction<'_>, id: &EventId) -> Result<(), StoreError> { tx.execute( "INSERT OR IGNORE INTO deleted_ids(event_id) VALUES (?1)", params![id.as_bytes().as_slice()], @@ -325,7 +326,7 @@ impl NostrSqlite { tx: &Transaction<'_>, coordinate: &Coordinate, deleted_at: Timestamp, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { tx.execute( "INSERT OR IGNORE INTO deleted_coordinates(pubkey, kind, identifier, deleted_at) VALUES (?1, ?2, ?3, ?4)", params![ @@ -338,7 +339,7 @@ impl NostrSqlite { Ok(()) } - fn event_is_deleted(tx: &Transaction<'_>, id: &EventId) -> Result { + fn event_is_deleted(tx: &Transaction<'_>, id: &EventId) -> Result { let is_deleted: i64 = tx.query_row( "SELECT EXISTS(SELECT 1 FROM deleted_ids WHERE event_id = ?1)", params![id.as_bytes().as_slice()], @@ -350,7 +351,7 @@ impl NostrSqlite { fn when_is_coordinate_deleted( tx: &Transaction<'_>, coordinate: &Coordinate, - ) -> Result, Error> { + ) -> Result, StoreError> { let timestamp: Option = tx .query_row( "SELECT deleted_at FROM deleted_coordinates WHERE pubkey = ?1 AND kind = ?2 AND identifier = ?3", @@ -369,7 +370,7 @@ impl NostrSqlite { } } - fn pubkey_is_vanished(tx: &Transaction<'_>, pubkey: &PublicKey) -> Result { + fn pubkey_is_vanished(tx: &Transaction<'_>, pubkey: &PublicKey) -> Result { let is_vanished: i64 = tx.query_row( "SELECT EXISTS(SELECT 1 FROM vanished_public_keys WHERE pubkey = ?1)", params![pubkey.as_bytes().as_slice()], @@ -378,7 +379,7 @@ impl NostrSqlite { Ok(is_vanished != 0) } - fn mark_pubkey_as_vanished(tx: &Transaction<'_>, pubkey: &PublicKey) -> Result<(), Error> { + fn mark_pubkey_as_vanished(tx: &Transaction<'_>, pubkey: &PublicKey) -> Result<(), StoreError> { tx.execute( "INSERT OR IGNORE INTO vanished_public_keys(pubkey) VALUES (?1)", params![pubkey.as_bytes().as_slice()], @@ -386,7 +387,10 @@ impl NostrSqlite { Ok(()) } - fn handle_request_to_vanish(tx: &Transaction<'_>, pubkey: &PublicKey) -> Result<(), Error> { + fn handle_request_to_vanish( + tx: &Transaction<'_>, + pubkey: &PublicKey, + ) -> Result<(), StoreError> { Self::mark_pubkey_as_vanished(tx, pubkey)?; // Delete all user events @@ -416,7 +420,7 @@ impl NostrSqlite { Ok(()) } - fn has_event(tx: &Transaction<'_>, id: &EventId) -> Result { + fn has_event(tx: &Transaction<'_>, id: &EventId) -> Result { let exists: i64 = tx.query_row( "SELECT EXISTS(SELECT 1 FROM events WHERE id = ?1)", params![id.as_bytes().as_slice()], @@ -428,7 +432,7 @@ impl NostrSqlite { fn get_pubkey_of_event_by_id( tx: &Transaction<'_>, id: &EventId, - ) -> Result, Error> { + ) -> Result, StoreError> { let pubkey: Option> = tx .query_row( "SELECT pubkey FROM events WHERE id = ?1", @@ -442,7 +446,7 @@ impl NostrSqlite { } } - fn get_event_by_id(conn: &Connection, id: &EventId) -> Result, Error> { + fn get_event_by_id(conn: &Connection, id: &EventId) -> Result, StoreError> { let mut stmt = conn.prepare("SELECT * FROM events WHERE id = ?1")?; let event: Option = stmt .query_row(params![id.as_bytes().as_slice()], EventDb::from_row) @@ -453,7 +457,7 @@ impl NostrSqlite { } } - fn remove_event(tx: &Transaction<'_>, id: &EventId) -> Result<(), Error> { + fn remove_event(tx: &Transaction<'_>, id: &EventId) -> Result<(), StoreError> { tx.execute( "DELETE FROM events where id = ?1", params![id.as_bytes().as_slice()], @@ -467,7 +471,7 @@ impl NostrSqlite { tx: &Transaction<'_>, coordinate: &Coordinate, until: &Timestamp, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { tx.execute( "DELETE FROM events\n WHERE pubkey = ?1 AND kind = ?2 AND created_at <= ?3", params![ @@ -486,7 +490,7 @@ impl NostrSqlite { tx: &Transaction<'_>, coordinate: &Coordinate, until: Timestamp, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { tx.execute( "DELETE FROM events\n WHERE id IN (\n SELECT e.id FROM events e\n INNER JOIN event_tags t ON e.id = t.event_id\n WHERE e.pubkey = ?1 AND e.kind = ?2\n AND t.tag_name = 'd' AND t.tag_value = ?3\n AND e.created_at <= ?4\n )", params![ @@ -518,23 +522,24 @@ impl NostrDatabase for NostrSqlite { fn save_event<'a>( &'a self, event: &'a Event, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let event = event.clone(); - self.pool + Ok(self + .pool .interact_options(move |conn, options| Self::save_event_sync(conn, &event, options)) - .await - .map_err(DatabaseError::backend) + .await?) }) } fn check_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let event_id = *event_id; - self.pool + Ok(self + .pool .interact(move |conn| { let tx = conn.transaction()?; @@ -546,28 +551,28 @@ impl NostrDatabase for NostrSqlite { Ok(DatabaseEventStatus::NotExistent) } }) - .await - .map_err(DatabaseError::backend) + .await?) }) } fn event_by_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result, DatabaseError>> { + ) -> BoxedFuture<'a, Result, Error>> { Box::pin(async move { let event_id = *event_id; - self.pool + Ok(self + .pool .interact(move |conn| Self::get_event_by_id(conn, &event_id)) - .await - .map_err(DatabaseError::backend) + .await?) }) } - fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { + fn count(&self, filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { let filter = with_limit(filter, EVENTS_QUERY_LIMIT); - self.pool + Ok(self + .pool .interact(move |conn| { let query = build_filter(&filter, SqlSelectClause::Count); let mut stmt = conn.prepare(&query.sql)?; @@ -575,15 +580,15 @@ impl NostrDatabase for NostrSqlite { stmt.query_row(params_from_iter(query.params), |row| row.get(0))?; Ok(count as usize) }) - .await - .map_err(DatabaseError::backend) + .await?) }) } - fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { + fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { let filter = with_limit(filter, EVENTS_QUERY_LIMIT); - self.pool + Ok(self + .pool .interact(move |conn| { let mut events = Events::new(&filter); let query = build_filter(&filter, SqlSelectClause::Select); @@ -598,29 +603,29 @@ impl NostrDatabase for NostrSqlite { Ok(events) }) - .await - .map_err(DatabaseError::backend) + .await?) }) } // TODO: impl negentropy_items deserializing only ids and timestamps - fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), DatabaseError>> { + fn delete(&self, filter: Filter) -> BoxedFuture<'_, Result<(), Error>> { Box::pin(async move { - self.pool + Ok(self + .pool .interact(move |conn| { let query = build_filter(&filter, SqlSelectClause::Delete); conn.execute(&query.sql, params_from_iter(query.params))?; Ok(()) }) - .await - .map_err(DatabaseError::backend) + .await?) }) } - fn wipe(&self) -> BoxedFuture<'_, Result<(), DatabaseError>> { + fn wipe(&self) -> BoxedFuture<'_, Result<(), Error>> { Box::pin(async move { - self.pool + Ok(self + .pool .interact(move |conn| { // Delete all data (CASCADE will handle event_tags) conn.execute("DELETE FROM events", [])?; @@ -632,8 +637,7 @@ impl NostrDatabase for NostrSqlite { Ok(()) }) - .await - .map_err(DatabaseError::backend) + .await?) }) } } diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index 6fee34b84..ae4e944cc 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -5,7 +5,6 @@ use std::fmt; use nostr::serde_json; -use nostr_database::prelude::*; use nostr_gossip::error::GossipError; use crate::{pool, relay}; @@ -20,7 +19,7 @@ pub enum Error { /// Relay Pool error RelayPool(pool::Error), /// Database error - Database(DatabaseError), + Database(nostr_database::error::Error), /// Gossip error Gossip(GossipError), /// Json error @@ -74,8 +73,8 @@ impl From for Error { } } -impl From for Error { - fn from(e: DatabaseError) -> Self { +impl From for Error { + fn from(e: nostr_database::error::Error) -> Self { Self::Database(e) } } diff --git a/sdk/src/events_tracker.rs b/sdk/src/events_tracker.rs index edbce950b..b6475d9c8 100644 --- a/sdk/src/events_tracker.rs +++ b/sdk/src/events_tracker.rs @@ -2,6 +2,7 @@ use std::num::NonZeroUsize; use lru::LruCache; use nostr::prelude::*; +use nostr_database::error::Error; use nostr_database::prelude::*; use tokio::sync::RwLock; @@ -38,7 +39,7 @@ impl NostrDatabase for MemoryEventsTracker { fn save_event<'a>( &'a self, event: &'a Event, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { // Mark it as seen let mut seen_event_ids = self.tracker.write().await; @@ -51,7 +52,7 @@ impl NostrDatabase for MemoryEventsTracker { fn check_id<'a>( &'a self, event_id: &'a EventId, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let seen_event_ids = self.tracker.read().await; @@ -66,30 +67,34 @@ impl NostrDatabase for MemoryEventsTracker { fn event_by_id<'a>( &'a self, _event_id: &'a EventId, - ) -> BoxedFuture<'a, Result, DatabaseError>> { + ) -> BoxedFuture<'a, Result, Error>> { Box::pin(async move { Ok(None) }) } - fn count(&self, _filter: Filter) -> BoxedFuture<'_, Result> { + fn count(&self, _filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { Ok(0) }) } - fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { + fn query(&self, filter: Filter) -> BoxedFuture<'_, Result> { Box::pin(async move { Ok(Events::new(&filter)) }) } fn negentropy_items( &self, _filter: Filter, - ) -> BoxedFuture<'_, Result, DatabaseError>> { + ) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { Ok(Vec::new()) }) } - fn delete(&self, _filter: Filter) -> BoxedFuture<'_, Result<(), DatabaseError>> { - Box::pin(async move { Err(DatabaseError::NotSupported) }) + fn delete(&self, _filter: Filter) -> BoxedFuture<'_, Result<(), Error>> { + Box::pin(async move { + Err(Error::unsupported( + "delete is not supported for the in-memory events tracker", + )) + }) } - fn wipe(&self) -> BoxedFuture<'_, Result<(), DatabaseError>> { + fn wipe(&self) -> BoxedFuture<'_, Result<(), Error>> { Box::pin(async move { let mut seen_event_ids = self.tracker.write().await; seen_event_ids.clear(); diff --git a/sdk/src/relay/error.rs b/sdk/src/relay/error.rs index 30de84bac..b18c716b4 100644 --- a/sdk/src/relay/error.rs +++ b/sdk/src/relay/error.rs @@ -1,7 +1,6 @@ use std::fmt; use std::time::Duration; -use nostr_database::DatabaseError; use nostr_gossip::error::GossipError; use tokio::sync::{broadcast, oneshot}; @@ -20,7 +19,7 @@ pub enum Error { /// Policy error Policy(PolicyError), /// Database error - Database(DatabaseError), + Database(nostr_database::error::Error), /// Gossip error Gossip(GossipError), /// Hex error @@ -226,8 +225,8 @@ impl From for Error { } } -impl From for Error { - fn from(e: DatabaseError) -> Self { +impl From for Error { + fn from(e: nostr_database::error::Error) -> Self { Self::Database(e) } } From 9fa3cfd32b694e8ebc2178e393ef50ed51064235 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 22 May 2026 11:19:22 +0200 Subject: [PATCH 04/11] relay-builder: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + crates/nostr-relay-builder/Cargo.toml | 1 + crates/nostr-relay-builder/src/error.rs | 116 +++++------------- crates/nostr-relay-builder/src/local/inner.rs | 16 +-- crates/nostr-relay-builder/src/prelude.rs | 1 + 5 files changed, 40 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e155a8ae0..9fdca23ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2049,6 +2049,7 @@ dependencies = [ "nostr-lmdb", "nostr-memory", "nostr-sdk", + "opaquerr", "tokio", "tracing", "tracing-subscriber", diff --git a/crates/nostr-relay-builder/Cargo.toml b/crates/nostr-relay-builder/Cargo.toml index 9c635e597..bb8849a7c 100644 --- a/crates/nostr-relay-builder/Cargo.toml +++ b/crates/nostr-relay-builder/Cargo.toml @@ -34,6 +34,7 @@ nostr = { workspace = true, default-features = false, features = ["std", "rand"] nostr-database.workspace = true nostr-memory.workspace = true nostr-sdk.workspace = true +opaquerr = { workspace = true, features = ["alloc"] } tokio = { workspace = true, features = ["macros", "net", "sync"] } tracing.workspace = true diff --git a/crates/nostr-relay-builder/src/error.rs b/crates/nostr-relay-builder/src/error.rs index b49890644..a4ac935d8 100644 --- a/crates/nostr-relay-builder/src/error.rs +++ b/crates/nostr-relay-builder/src/error.rs @@ -4,97 +4,37 @@ //! Relay builder error -use std::{fmt, io}; - use nostr::Event; use nostr_sdk::client; use tokio::sync::broadcast; -/// Relay builder error -#[derive(Debug)] -pub enum Error { - /// I/O error - IO(io::Error), - /// Database error - Database(nostr_database::error::Error), - /// Client error - Client(client::Error), - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Other error - Other(String), - /// Relay already running - AlreadyRunning, - /// Premature exit - PrematureExit, -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::IO(e) => write!(f, "{e}"), - Self::Database(e) => write!(f, "{e}"), - Self::Client(e) => e.fmt(f), - Self::Protocol(e) => e.fmt(f), - Self::Other(e) => f.write_str(e), - Self::AlreadyRunning => write!(f, "the relay is already running"), - Self::PrematureExit => write!(f, "premature exit"), - } - } -} - -impl From for Error { - fn from(e: io::Error) -> Self { - Self::IO(e) - } -} - -impl From for Error { - fn from(e: nostr_database::error::Error) -> Self { - Self::Database(e) - } -} - -impl From for Error { - fn from(e: client::Error) -> Self { - Self::Client(e) - } -} - -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} - -impl From for Error { - fn from(e: async_wsocket::Error) -> Self { - Self::Other(e.to_string()) - } -} - -impl From for Error { - fn from(e: tokio::sync::TryAcquireError) -> Self { - Self::Other(e.to_string()) - } -} - -impl From> for Error { - fn from(e: broadcast::error::SendError) -> Self { - Self::Other(e.to_string()) - } -} - -impl From for Error { - fn from(e: negentropy::Error) -> Self { - Self::Other(e.to_string()) - } -} - -impl From for Error { - fn from(e: faster_hex::Error) -> Self { - Self::Other(e.to_string()) +opaquerr::define_kind! { + /// Relay builder error kind + pub ErrorKind { + /// Nostr protocol error + Protocol => "nostr protocol error", + /// Database error + Database => "database error", + /// I/O error + IO => "I/O error", + /// Anything not covered by the stable categories above. + Other => "other error", + } +} + +opaquerr::define_error! { + /// Relay builder error + pub Error(ErrorKind) + + from { + nostr::error::Error => ErrorKind::Protocol, + nostr_database::error::Error => ErrorKind::Database, + std::io::Error => ErrorKind::IO, + client::Error => ErrorKind::Other, + async_wsocket::Error => ErrorKind::Other, + negentropy::Error => ErrorKind::Other, + tokio::sync::TryAcquireError => ErrorKind::Other, + broadcast::error::SendError => ErrorKind::Other, + faster_hex::Error => ErrorKind::Other, } } diff --git a/crates/nostr-relay-builder/src/local/inner.rs b/crates/nostr-relay-builder/src/local/inner.rs index 886fc483f..a6ea9e6be 100644 --- a/crates/nostr-relay-builder/src/local/inner.rs +++ b/crates/nostr-relay-builder/src/local/inner.rs @@ -27,7 +27,7 @@ use crate::builder::{ LocalRelayBuilder, LocalRelayBuilderMode, LocalRelayBuilderNip42, LocalRelayTestOptions, QueryPolicy, QueryPolicyResult, RateLimit, WritePolicy, WritePolicyResult, }; -use crate::error::Error; +use crate::error::{Error, ErrorKind}; type WsTx = SplitSink, Message>; const P_TAG: SingleLetterTag = SingleLetterTag::lowercase(Alphabet::P); @@ -117,9 +117,11 @@ impl InnerLocalRelay { } /// Start socket to listen for new websocket connections - pub async fn run(&self) -> Result<(), Error> { + /// + /// Returns `true` if the relay has started, `false` if it's already running. + pub async fn run(&self) -> Result { if self.running.load(Ordering::SeqCst) { - return Err(Error::AlreadyRunning); + return Ok(false); } // Get the address @@ -158,7 +160,7 @@ impl InnerLocalRelay { tracing::info!("Local relay listener loop terminated."); }); - Ok(()) + Ok(true) } #[inline] @@ -212,7 +214,7 @@ impl InnerLocalRelay { // Return reconciliation output Ok(result?) }, - _ = fut => Err(Error::PrematureExit) + _ = fut => Err(Error::with_static_message(ErrorKind::Other, "notifications exited before sync completed")) } } @@ -1014,7 +1016,7 @@ where { tx.send(Message::Text(msg.as_json().into())) .await - .map_err(|e| Error::Other(e.to_string()))?; + .map_err(|e| Error::new(ErrorKind::Other, e))?; Ok(()) } @@ -1102,6 +1104,6 @@ where let mut stream = stream::iter(json_msgs).map(|msg| Ok(Message::Text(msg.into()))); tx.send_all(&mut stream) .await - .map_err(|e| Error::Other(e.to_string()))?; + .map_err(|e| Error::new(ErrorKind::Other, e))?; Ok(()) } diff --git a/crates/nostr-relay-builder/src/prelude.rs b/crates/nostr-relay-builder/src/prelude.rs index b145b93e9..5ef70f38b 100644 --- a/crates/nostr-relay-builder/src/prelude.rs +++ b/crates/nostr-relay-builder/src/prelude.rs @@ -14,6 +14,7 @@ pub use nostr_database::prelude::*; pub use nostr_sdk::relay::SyncOptions; pub use crate::builder::{self, *}; +pub use crate::error::{Error, ErrorKind}; pub use crate::local::{self, *}; pub use crate::mock::{self, *}; pub use crate::*; From c56b984bf21db584e757c3442fe20348e3aedc2b Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 27 May 2026 10:22:12 +0200 Subject: [PATCH 05/11] nwc: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + crates/nwc/Cargo.toml | 1 + crates/nwc/src/error.rs | 66 ++++++++++++++------------------------- crates/nwc/src/lib.rs | 2 +- crates/nwc/src/prelude.rs | 3 +- 5 files changed, 27 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fdca23ce..a1080469f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2130,6 +2130,7 @@ dependencies = [ "futures-core", "nostr", "nostr-sdk", + "opaquerr", "tokio", "tracing", "tracing-subscriber", diff --git a/crates/nwc/Cargo.toml b/crates/nwc/Cargo.toml index 683e68d9e..9670e6a4c 100644 --- a/crates/nwc/Cargo.toml +++ b/crates/nwc/Cargo.toml @@ -28,6 +28,7 @@ rustls-tls-webpki-roots = ["nostr-sdk/rustls-tls-webpki-roots"] futures-core = "0.3" nostr = { workspace = true, features = ["std", "nip47"] } nostr-sdk.workspace = true +opaquerr = { workspace = true, features = ["alloc"] } tracing = { workspace = true, features = ["std"] } [dev-dependencies] diff --git a/crates/nwc/src/error.rs b/crates/nwc/src/error.rs index a5c9ae9c8..d1fc809a5 100644 --- a/crates/nwc/src/error.rs +++ b/crates/nwc/src/error.rs @@ -2,58 +2,38 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -//! NWC error - -use std::fmt; +//! Nostr Wallet Connect error use nostr_sdk::{client, relay}; -/// NWC error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Client error - Client(client::Error), - /// Relay error - Relay(relay::Error), - /// Response not received - ResponseNotReceived, - /// Request timeout - Timeout, - /// Handler error - Handler(String), -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => e.fmt(f), - Self::Client(e) => e.fmt(f), - Self::Relay(e) => e.fmt(f), - Self::ResponseNotReceived => f.write_str("response not received"), - Self::Timeout => f.write_str("timeout"), - Self::Handler(e) => f.write_str(e), - } +opaquerr::define_kind! { + /// Nostr Wallet Connect error kind. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// SDK error + Sdk => "SDK error", + /// Wallet response was not received. + NoResponse => "response not received", + /// Anything not covered by the stable categories above. + Other => "other error", } } -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} +opaquerr::define_error! { + /// Nostr Wallet Connect error. + pub Error(ErrorKind) -impl From for Error { - fn from(e: client::Error) -> Self { - Self::Client(e) + from { + nostr::error::Error => ErrorKind::Protocol, + client::Error => ErrorKind::Sdk, + relay::Error => ErrorKind::Sdk, } } -impl From for Error { - fn from(e: relay::Error) -> Self { - Self::Relay(e) +impl Error { + #[inline] + pub(super) fn no_response() -> Self { + Self::simple(ErrorKind::NoResponse) } } diff --git a/crates/nwc/src/lib.rs b/crates/nwc/src/lib.rs index a446ddd85..1892b22dc 100644 --- a/crates/nwc/src/lib.rs +++ b/crates/nwc/src/lib.rs @@ -158,7 +158,7 @@ impl NostrWalletConnect { self.client.send_event(&event).await?; // Wait for the response - let (_, res) = stream.next().await.ok_or(Error::ResponseNotReceived)?; + let (_, res) = stream.next().await.ok_or_else(Error::no_response)?; // Unwrap event let received_event: Event = res?; diff --git a/crates/nwc/src/prelude.rs b/crates/nwc/src/prelude.rs index d3e50493e..47ff00e2f 100644 --- a/crates/nwc/src/prelude.rs +++ b/crates/nwc/src/prelude.rs @@ -12,6 +12,5 @@ pub use nostr::prelude::*; pub use nostr_sdk::prelude::*; pub use crate::builder::*; -#[allow(unused_imports)] -pub use crate::error::*; +pub use crate::error::{Error, ErrorKind}; pub use crate::*; From e965cf2e62e968ec2edde723ff7c6be48519955f Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 27 May 2026 10:41:43 +0200 Subject: [PATCH 06/11] blossom: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + rfs/nostr-blossom/Cargo.toml | 1 + rfs/nostr-blossom/src/client.rs | 14 +++- rfs/nostr-blossom/src/error.rs | 127 +++++++++---------------------- rfs/nostr-blossom/src/prelude.rs | 2 +- 5 files changed, 48 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1080469f..24ff3d6bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1862,6 +1862,7 @@ dependencies = [ "base64 0.22.1", "clap", "nostr", + "opaquerr", "reqwest", "serde", "tokio", diff --git a/rfs/nostr-blossom/Cargo.toml b/rfs/nostr-blossom/Cargo.toml index b7eb8e786..34e8d4da7 100644 --- a/rfs/nostr-blossom/Cargo.toml +++ b/rfs/nostr-blossom/Cargo.toml @@ -14,6 +14,7 @@ keywords = ["nostr", "blossom"] [dependencies] base64.workspace = true nostr = { workspace = true, features = ["std"] } +opaquerr = { workspace = true, features = ["alloc"] } reqwest = { workspace = true, default-features = false, features = ["http2", "json", "rustls-tls"] } serde = { workspace = true, features = ["derive"] } diff --git a/rfs/nostr-blossom/src/client.rs b/rfs/nostr-blossom/src/client.rs index 409030f1e..bd8313efa 100644 --- a/rfs/nostr-blossom/src/client.rs +++ b/rfs/nostr-blossom/src/client.rs @@ -14,7 +14,7 @@ use reqwest::{Response, StatusCode}; use crate::bud01::{BlossomAuthorization, BlossomAuthorizationScope, BlossomAuthorizationVerb}; use crate::bud02::BlobDescriptor; -use crate::error::Error; +use crate::error::{Error, ErrorKind}; /// A client for interacting with a Blossom server /// @@ -192,10 +192,18 @@ impl BlossomClient { Some(location) => { let location_str: &str = location.to_str()?; if !location_str.contains(&sha256.to_string()) { - return Err(Error::RedirectUrlDoesNotContainSha256); + return Err(Error::with_static_message( + ErrorKind::Invalid, + "Redirect URL does not contain SHA256", + )); } } - None => return Err(Error::RedirectResponseMissingLocationHeader), + None => { + return Err(Error::with_static_message( + ErrorKind::Invalid, + "Redirect response missing 'Location' header", + )); + } } } diff --git a/rfs/nostr-blossom/src/error.rs b/rfs/nostr-blossom/src/error.rs index 688a8b79f..120d396db 100644 --- a/rfs/nostr-blossom/src/error.rs +++ b/rfs/nostr-blossom/src/error.rs @@ -4,105 +4,46 @@ //! Blossom error -use std::fmt; - -use nostr::types::ParseError; -use reqwest::Response; -use reqwest::header::{InvalidHeaderValue, ToStrError}; - -/// Blossom error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Reqwest error - Reqwest(reqwest::Error), - /// Invalid header value - InvalidHeaderValue(InvalidHeaderValue), - /// Url parse error - Url(ParseError), - /// To string error - ToStr(ToStrError), - /// Response error - Response { - /// Prefix for the error message - prefix: String, - /// Response - res: Response, - }, - /// Returned when a redirect URL does not contain the expected hash - RedirectUrlDoesNotContainSha256, - /// Returned when a redirect response is missing the Location header - RedirectResponseMissingLocationHeader, -} - -impl Error { - #[inline] - pub(super) fn response(prefix: S, res: Response) -> Self - where - S: Into, - { - Self::Response { - prefix: prefix.into(), - res, - } - } -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => write!(f, "{e}"), - Self::Reqwest(e) => write!(f, "{e}"), - Self::InvalidHeaderValue(e) => write!(f, "{e}"), - Self::Url(e) => write!(f, "{e}"), - Self::ToStr(e) => write!(f, "{e}"), - Self::Response { prefix, res } => { - let reason: &str = res - .headers() - .get("X-Reason") - .map(|h| h.to_str().unwrap_or("Unknown reason")) - .unwrap_or_else(|| "No reason provided"); - write!(f, "{prefix}: {} - {reason}", res.status()) - } - Self::RedirectUrlDoesNotContainSha256 => { - write!(f, "Redirect URL does not contain SHA256") - } - Self::RedirectResponseMissingLocationHeader => { - write!(f, "Redirect response missing 'Location' header") - } - } +use nostr::types::url; +use reqwest::{Response, header}; + +opaquerr::define_kind! { + /// Nostr blossom error kind. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// HTTP error + Http => "HTTP error", + /// Input is not well-formed and cannot be parsed. + Malformed => "input is malformed", + /// Input is well-formed, but violates a protocol/library invariant. + Invalid => "input violates a protocol/library invariant", + /// Anything not covered by the stable categories above. + Other => "other error", } } -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Self::Reqwest(e) - } -} +opaquerr::define_error! { + /// Nostr blossom error. + pub Error(ErrorKind) -impl From for Error { - fn from(e: InvalidHeaderValue) -> Self { - Self::InvalidHeaderValue(e) + from { + nostr::error::Error => ErrorKind::Protocol, + reqwest::Error => ErrorKind::Http, + header::InvalidHeaderValue => ErrorKind::Http, + url::ParseError => ErrorKind::Malformed, + header::ToStrError => ErrorKind::Invalid, } } -impl From for Error { - fn from(e: ParseError) -> Self { - Self::Url(e) - } -} - -impl From for Error { - fn from(e: ToStrError) -> Self { - Self::ToStr(e) +impl Error { + pub(crate) fn response(prefix: &'static str, res: Response) -> Self { + let reason: &str = res + .headers() + .get("X-Reason") + .map(|h| h.to_str().unwrap_or("Unknown reason")) + .unwrap_or_else(|| "No reason provided"); + let msg: String = format!("{prefix}: {} - {reason}", res.status()); + Self::new(ErrorKind::Http, msg) } } diff --git a/rfs/nostr-blossom/src/prelude.rs b/rfs/nostr-blossom/src/prelude.rs index 6486e3101..6e65db6e6 100644 --- a/rfs/nostr-blossom/src/prelude.rs +++ b/rfs/nostr-blossom/src/prelude.rs @@ -12,4 +12,4 @@ pub use crate::bud01::*; pub use crate::bud02::*; pub use crate::client::*; -pub use crate::error::*; +pub use crate::error::{Error, ErrorKind}; From f29c7f21f555c3db9a3dab26b5c3e2a41e875096 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 27 May 2026 11:09:12 +0200 Subject: [PATCH 07/11] sdk: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + crates/nostr-relay-builder/src/error.rs | 3 +- crates/nwc/src/error.rs | 5 +- sdk/Cargo.toml | 1 + sdk/README.md | 2 +- sdk/examples/blacklist.rs | 2 +- sdk/examples/whitelist.rs | 2 +- sdk/src/authenticator.rs | 8 +- sdk/src/client/api/add.rs | 12 +- sdk/src/client/api/remove.rs | 17 +- sdk/src/client/api/send_event.rs | 27 ++- sdk/src/client/api/send_msg.rs | 5 +- sdk/src/client/api/stream_events.rs | 7 +- sdk/src/client/api/subscribe.rs | 5 +- sdk/src/client/api/sync.rs | 11 +- sdk/src/client/error.rs | 92 --------- sdk/src/client/gossip/updater.rs | 2 +- sdk/src/client/mod.rs | 28 ++- sdk/src/error.rs | 224 ++++++++++++++++++++ sdk/src/lib.rs | 1 + sdk/src/policy.rs | 37 +--- sdk/src/pool/error.rs | 53 ----- sdk/src/pool/mod.rs | 41 ++-- sdk/src/prelude.rs | 1 + sdk/src/relay/api/fetch_events.rs | 18 +- sdk/src/relay/api/send_event.rs | 31 ++- sdk/src/relay/api/send_msg.rs | 12 +- sdk/src/relay/api/stream_events.rs | 9 +- sdk/src/relay/api/subscribe.rs | 7 +- sdk/src/relay/api/sync.rs | 35 ++-- sdk/src/relay/api/try_connect.rs | 14 +- sdk/src/relay/api/unsubscribe.rs | 3 +- sdk/src/relay/api/unsubscribe_all.rs | 3 +- sdk/src/relay/error.rs | 262 ------------------------ sdk/src/relay/inner.rs | 81 ++++---- sdk/src/relay/mod.rs | 32 ++- sdk/src/transport/error.rs | 47 ----- sdk/src/transport/mod.rs | 1 - sdk/src/transport/websocket.rs | 29 ++- signer/nostr-connect/src/error.rs | 13 +- 40 files changed, 463 insertions(+), 721 deletions(-) delete mode 100644 sdk/src/client/error.rs create mode 100644 sdk/src/error.rs delete mode 100644 sdk/src/pool/error.rs delete mode 100644 sdk/src/relay/error.rs delete mode 100644 sdk/src/transport/error.rs diff --git a/Cargo.lock b/Cargo.lock index 24ff3d6bd..2d2d68805 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2075,6 +2075,7 @@ dependencies = [ "nostr-memory", "nostr-ndb", "nostr-relay-builder", + "opaquerr", "tokio", "tokio-stream", "tracing", diff --git a/crates/nostr-relay-builder/src/error.rs b/crates/nostr-relay-builder/src/error.rs index a4ac935d8..ca5585157 100644 --- a/crates/nostr-relay-builder/src/error.rs +++ b/crates/nostr-relay-builder/src/error.rs @@ -5,7 +5,6 @@ //! Relay builder error use nostr::Event; -use nostr_sdk::client; use tokio::sync::broadcast; opaquerr::define_kind! { @@ -30,7 +29,7 @@ opaquerr::define_error! { nostr::error::Error => ErrorKind::Protocol, nostr_database::error::Error => ErrorKind::Database, std::io::Error => ErrorKind::IO, - client::Error => ErrorKind::Other, + nostr_sdk::error::Error => ErrorKind::Other, async_wsocket::Error => ErrorKind::Other, negentropy::Error => ErrorKind::Other, tokio::sync::TryAcquireError => ErrorKind::Other, diff --git a/crates/nwc/src/error.rs b/crates/nwc/src/error.rs index d1fc809a5..f4e3c358b 100644 --- a/crates/nwc/src/error.rs +++ b/crates/nwc/src/error.rs @@ -4,8 +4,6 @@ //! Nostr Wallet Connect error -use nostr_sdk::{client, relay}; - opaquerr::define_kind! { /// Nostr Wallet Connect error kind. pub ErrorKind { @@ -26,8 +24,7 @@ opaquerr::define_error! { from { nostr::error::Error => ErrorKind::Protocol, - client::Error => ErrorKind::Sdk, - relay::Error => ErrorKind::Sdk, + nostr_sdk::error::Error => ErrorKind::Sdk, } } diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 7e3036b72..90494471d 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -34,6 +34,7 @@ negentropy = { workspace = true, features = ["std"] } nostr = { workspace = true, features = ["std", "rand", "os-rng"] } nostr-database.workspace = true nostr-gossip.workspace = true +opaquerr = { workspace = true, features = ["alloc"] } tokio = { workspace = true, features = ["macros", "sync"] } tokio-stream = { version = "0.1", features = ["sync"] } tracing = { workspace = true, features = ["std"] } diff --git a/sdk/README.md b/sdk/README.md index 7987ae818..cdde84ecc 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -18,7 +18,7 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use nostr_sdk::prelude::*; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // Generate new random keys let keys = Keys::generate(); diff --git a/sdk/examples/blacklist.rs b/sdk/examples/blacklist.rs index b87d10a3a..d23944ec6 100644 --- a/sdk/examples/blacklist.rs +++ b/sdk/examples/blacklist.rs @@ -18,7 +18,7 @@ impl AdmitPolicy for Filtering { _relay_url: &'a RelayUrl, _subscription_id: &'a SubscriptionId, event: &'a Event, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { if self.muted_public_keys.contains(&event.pubkey) { return Ok(AdmitStatus::rejected("Muted")); diff --git a/sdk/examples/whitelist.rs b/sdk/examples/whitelist.rs index 56938232a..3b63c6041 100644 --- a/sdk/examples/whitelist.rs +++ b/sdk/examples/whitelist.rs @@ -18,7 +18,7 @@ impl AdmitPolicy for WoT { _relay_url: &'a RelayUrl, _subscription_id: &'a SubscriptionId, event: &'a Event, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { if self.allowed_public_keys.contains(&event.pubkey) { return Ok(AdmitStatus::success()); diff --git a/sdk/src/authenticator.rs b/sdk/src/authenticator.rs index b7b9b2ae2..5ef2fcbc5 100644 --- a/sdk/src/authenticator.rs +++ b/sdk/src/authenticator.rs @@ -5,11 +5,9 @@ use std::fmt::Debug; use nostr::prelude::*; +use crate::error::Error; use crate::future::BoxedFuture; -/// Authenticator error -pub type AuthenticationError = Box; - /// Authenticator pub trait Authenticator: Any + Debug + Send + Sync { /// Makes a NIP-42 event for authentication @@ -19,7 +17,7 @@ pub trait Authenticator: Any + Debug + Send + Sync { &'a self, relay_url: &'a RelayUrl, challenge: &'a str, - ) -> BoxedFuture<'a, Result>; + ) -> BoxedFuture<'a, Result>; } /// An authenticator that uses a signer that implements [`AsyncGetPublicKey`] and [`AsyncSignEvent`] for creating NIP-42 events. @@ -61,7 +59,7 @@ where &'a self, relay_url: &'a RelayUrl, challenge: &'a str, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { Ok(EventBuilder::auth(challenge, relay_url.clone()) .finalize_async(&self.signer) diff --git a/sdk/src/client/api/add.rs b/sdk/src/client/api/add.rs index 136ff467e..e75fba11c 100644 --- a/sdk/src/client/api/add.rs +++ b/sdk/src/client/api/add.rs @@ -4,7 +4,8 @@ use std::time::Duration; use nostr::types::url::{RelayUrl, RelayUrlArg}; -use crate::client::{Client, Error}; +use crate::client::Client; +use crate::error::Error; use crate::future::BoxedFuture; #[cfg(not(target_arch = "wasm32"))] use crate::proxy::Proxy; @@ -159,11 +160,10 @@ where let url: Cow = self.url.try_into_relay_url()?; // Add relay to the pool - Ok(self - .client + self.client .pool() .add_relay(url, self.capabilities, self.connect, self.opts) - .await?) + .await }) } } @@ -174,7 +174,7 @@ mod tests { use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use super::*; - use crate::policy::{AdmitPolicy, AdmitStatus, PolicyError}; + use crate::policy::{AdmitPolicy, AdmitStatus}; #[derive(Debug)] struct RejectRelayPolicy { @@ -185,7 +185,7 @@ mod tests { fn admit_relay<'a>( &'a self, relay_url: &'a RelayUrl, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { if self.rejected_relays.contains(relay_url) { Ok(AdmitStatus::rejected("relay rejected")) diff --git a/sdk/src/client/api/remove.rs b/sdk/src/client/api/remove.rs index e81991721..0d5fd8e0c 100644 --- a/sdk/src/client/api/remove.rs +++ b/sdk/src/client/api/remove.rs @@ -44,7 +44,7 @@ where let url: Cow = self.url.try_into_relay_url()?; // Remove the relay from the pool - Ok(self.client.pool().remove_relay(url, self.force).await?) + self.client.pool().remove_relay(url, self.force).await }) } } @@ -52,7 +52,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::pool; + use crate::error::ErrorKind; use crate::relay::RelayCapabilities; #[tokio::test] @@ -61,13 +61,12 @@ mod tests { client.add_relay("ws://127.0.0.1:6666").await.unwrap(); - assert!(matches!( - client - .remove_relay("ws://127.0.0.1:7777") - .await - .unwrap_err(), - Error::RelayPool(pool::Error::RelayNotFound(..)) - )); + let err = client + .remove_relay("ws://127.0.0.1:7777") + .await + .unwrap_err(); + assert_eq!(err.kind(), ErrorKind::NotFound); + assert_eq!(err.to_string(), "relay not found"); } #[tokio::test] diff --git a/sdk/src/client/api/send_event.rs b/sdk/src/client/api/send_event.rs index 9201a6d86..e0607df5f 100644 --- a/sdk/src/client/api/send_event.rs +++ b/sdk/src/client/api/send_event.rs @@ -7,7 +7,8 @@ use nostr::{Event, EventId, Kind, PublicKey, RelayUrl, RelayUrlArg}; use nostr_gossip::{BestRelaySelection, GossipListKind}; use crate::client::gossip::Gossip; -use crate::client::{Client, Error, Output}; +use crate::client::{Client, Output}; +use crate::error::Error; use crate::future::BoxedFuture; use crate::relay::{EventSendStatus, RelayCapabilities}; @@ -177,7 +178,7 @@ impl<'client, 'event, 'url> SendEvent<'client, 'event, 'url> { /// - [`SendEvent::broadcast`] /// - [`SendEvent::to_nip65`] /// - /// Returns [`Error::GossipNotConfigured`] if gossip is not configured. + /// Returns an error if gossip is not configured. #[inline] pub fn to_nip17(mut self) -> Self { self.policy = Some(OverwritePolicy::ToNip17); @@ -191,7 +192,7 @@ impl<'client, 'event, 'url> SendEvent<'client, 'event, 'url> { /// - [`SendEvent::broadcast`] /// - [`SendEvent::to_nip17`] /// - /// Returns [`Error::GossipNotConfigured`] if gossip is not configured. + /// Returns an error if gossip is not configured. #[inline] pub fn to_nip65(mut self) -> Self { self.policy = Some(OverwritePolicy::ToNip65); @@ -294,7 +295,9 @@ async fn gossip_prepare_urls( // // if relays.is_empty() { - return Err(Error::PrivateMsgRelaysNotFound); + return Err(Error::not_found( + "Private message relays not found. The user is not ready to receive private messages.", + )); } // Add outbox and inbox relays @@ -401,7 +404,7 @@ where } // Send to gossip, but gossip is not available: error (Some(OverwritePolicy::ToNip17 | OverwritePolicy::ToNip65), None) => { - return Err(Error::GossipNotConfigured); + return Err(Error::gossip_not_configured()); } // Send to specific relays (Some(OverwritePolicy::To(list)), _) => { @@ -421,8 +424,7 @@ where } }; - Ok(self - .client + self.client .pool() .send_event( urls, @@ -431,7 +433,7 @@ where self.wait_for_ok_timeout, self.wait_for_authentication_timeout, ) - .await?) + .await }) } } @@ -444,7 +446,8 @@ mod tests { use nostr_relay_builder::MockRelay; use super::*; - use crate::client::{Error, GossipConfig, GossipRelayLimits}; + use crate::client::{GossipConfig, GossipRelayLimits}; + use crate::error::ErrorKind; #[tokio::test] async fn test_send_event() { @@ -659,7 +662,8 @@ mod tests { // Send event let err = client.send_event(&event).to_nip65().await.unwrap_err(); - assert!(matches!(err, Error::GossipNotConfigured)); + assert_eq!(err.kind(), ErrorKind::State); + assert_eq!(err.to_string(), "gossip not configured"); } #[tokio::test] @@ -746,6 +750,7 @@ mod tests { // Send event let err = client.send_event(&event).to_nip17().await.unwrap_err(); - assert!(matches!(err, Error::GossipNotConfigured)); + assert_eq!(err.kind(), ErrorKind::State); + assert_eq!(err.to_string(), "gossip not configured"); } } diff --git a/sdk/src/client/api/send_msg.rs b/sdk/src/client/api/send_msg.rs index 3fdd6176c..b9888402e 100644 --- a/sdk/src/client/api/send_msg.rs +++ b/sdk/src/client/api/send_msg.rs @@ -100,11 +100,10 @@ where } }; - Ok(self - .client + self.client .pool() .send_msg(urls, self.msg, self.wait_until_sent) - .await?) + .await }) } } diff --git a/sdk/src/client/api/stream_events.rs b/sdk/src/client/api/stream_events.rs index 7018f0078..8c7b01e10 100644 --- a/sdk/src/client/api/stream_events.rs +++ b/sdk/src/client/api/stream_events.rs @@ -9,11 +9,12 @@ use nostr::{Event, Filter, SubscriptionId}; use super::req_target::ReqTarget; use super::util::build_targets; -use crate::client::{Client, Error}; +use crate::client::Client; +use crate::error::Error; use crate::future::BoxedFuture; -use crate::relay::{self, RelayStreamEvent, ReqExitPolicy}; +use crate::relay::{RelayStreamEvent, ReqExitPolicy}; -type EventStream = Pin)> + Send>>; +type EventStream = Pin)> + Send>>; /// Stream events #[must_use = "Does nothing unless you await!"] diff --git a/sdk/src/client/api/subscribe.rs b/sdk/src/client/api/subscribe.rs index f652c72ec..76cefdfdf 100644 --- a/sdk/src/client/api/subscribe.rs +++ b/sdk/src/client/api/subscribe.rs @@ -58,11 +58,10 @@ where let targets: HashMap> = build_targets(self.client, self.target).await?; - Ok(self - .client + self.client .pool() .subscribe(targets, self.id, self.auto_close) - .await?) + .await }) } } diff --git a/sdk/src/client/api/sync.rs b/sdk/src/client/api/sync.rs index f5215b95b..34929d92c 100644 --- a/sdk/src/client/api/sync.rs +++ b/sdk/src/client/api/sync.rs @@ -190,7 +190,7 @@ where } }; - Ok(self.client.pool().sync(targets, self.opts).await?) + self.client.pool().sync(targets, self.opts).await }) } } @@ -200,7 +200,7 @@ mod tests { use nostr::Kind; use super::*; - use crate::pool; + use crate::error::ErrorKind; #[tokio::test] async fn test_sync_with_empty_list_of_relays() { @@ -210,9 +210,8 @@ mod tests { let relays: Vec = Vec::new(); let res = client.sync(filter).with(relays).await; - assert!(matches!( - res.unwrap_err(), - Error::RelayPool(pool::Error::NoRelaysSpecified) - )) + let err = res.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::Invalid); + assert_eq!(err.to_string(), "relay/s not specified"); } } diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs deleted file mode 100644 index ae4e944cc..000000000 --- a/sdk/src/client/error.rs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2022-2023 Yuki Kishimoto -// Copyright (c) 2023-2025 Rust Nostr Developers -// Distributed under the MIT software license - -use std::fmt; - -use nostr::serde_json; -use nostr_gossip::error::GossipError; - -use crate::{pool, relay}; - -/// Client error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Relay error - Relay(relay::Error), - /// Relay Pool error - RelayPool(pool::Error), - /// Database error - Database(nostr_database::error::Error), - /// Gossip error - Gossip(GossipError), - /// Json error - Json(serde_json::Error), - /// Signer not configured - SignerNotConfigured, - /// Gossip is not configured - GossipNotConfigured, - /// Broken down filters for gossip are empty - GossipFiltersEmpty, - /// Private message (NIP17) relays not found - PrivateMsgRelaysNotFound, -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => e.fmt(f), - Self::Relay(e) => e.fmt(f), - Self::RelayPool(e) => e.fmt(f), - Self::Database(e) => e.fmt(f), - Self::Gossip(e) => e.fmt(f), - Self::Json(e) => e.fmt(f), - Self::SignerNotConfigured => f.write_str("signer not configured"), - Self::GossipNotConfigured => f.write_str("gossip not configured"), - Self::GossipFiltersEmpty => { - f.write_str("gossip broken down filters are empty") - } - Self::PrivateMsgRelaysNotFound => f.write_str("Private message relays not found. The user is not ready to receive private messages."), - } - } -} - -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} - -impl From for Error { - fn from(e: relay::Error) -> Self { - Self::Relay(e) - } -} - -impl From for Error { - fn from(e: pool::Error) -> Self { - Self::RelayPool(e) - } -} - -impl From for Error { - fn from(e: nostr_database::error::Error) -> Self { - Self::Database(e) - } -} - -impl From for Error { - fn from(e: GossipError) -> Self { - Self::Gossip(e) - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e) - } -} diff --git a/sdk/src/client/gossip/updater.rs b/sdk/src/client/gossip/updater.rs index 069ca7759..a99303bb5 100644 --- a/sdk/src/client/gossip/updater.rs +++ b/sdk/src/client/gossip/updater.rs @@ -463,7 +463,7 @@ impl Client { } if filters.is_empty() { - return Err(Error::GossipFiltersEmpty); + return Err(Error::state_msg("broken down filters are empty")); } Ok(filters) diff --git a/sdk/src/client/mod.rs b/sdk/src/client/mod.rs index 9173aab6d..f120087a5 100644 --- a/sdk/src/client/mod.rs +++ b/sdk/src/client/mod.rs @@ -17,15 +17,14 @@ use tokio::sync::oneshot; mod api; mod builder; -mod error; mod gossip; mod notification; pub use self::api::*; pub use self::builder::*; -pub use self::error::Error; use self::gossip::*; pub use self::notification::*; +use crate::error::Error; use crate::monitor::Monitor; use crate::pool::{RelayPool, RelayPoolBuilder}; #[cfg(not(target_arch = "wasm32"))] @@ -251,7 +250,7 @@ impl Client { /// /// ``` /// # use nostr_sdk::prelude::*; - /// # async fn example() -> Result<()> { + /// # async fn example() -> Result<(), Box> { /// # let client = Client::default(); /// // Not added yet /// let relay = client.relay("wss://relay.example.com").await?; @@ -433,7 +432,7 @@ impl Client { { let url: RelayUrlArg<'a> = url.into(); let url: Cow = url.try_as_relay_url()?; - Ok(self.pool().connect_relay(&url).await?) + self.pool().connect_relay(&url).await } /// Try to connect to a previously added relay @@ -444,7 +443,7 @@ impl Client { { let url: RelayUrlArg<'a> = url.into(); let url: Cow = url.try_as_relay_url()?; - Ok(self.pool().try_connect_relay(&url, timeout).await?) + self.pool().try_connect_relay(&url, timeout).await } /// Disconnect relay @@ -455,7 +454,7 @@ impl Client { { let url: RelayUrlArg<'a> = url.into(); let url: Cow = url.try_as_relay_url()?; - Ok(self.pool().disconnect_relay(&url).await?) + self.pool().disconnect_relay(&url).await } /// Connect to relays @@ -614,7 +613,7 @@ impl Client { /// /// ```rust,no_run /// # use nostr_sdk::prelude::*; - /// # async fn example() -> Result<()> { + /// # async fn example() -> Result<(), Box> { /// # let client = Client::default(); /// # client.add_relay("wss://relay1.example.com").await?; /// # client.add_relay("wss://relay2.example.com").await?; @@ -648,7 +647,7 @@ impl Client { /// ```rust,no_run /// # use std::collections::HashMap; /// # use nostr_sdk::prelude::*; - /// # async fn example() -> Result<()> { + /// # async fn example() -> Result<(), Box> { /// # let client = Client::default(); /// # client.add_relay("wss://relay1.example.com").await?; /// # client.add_relay("wss://relay2.example.com").await?; @@ -689,7 +688,7 @@ impl Client { /// ```rust,no_run /// # use std::time::Duration; /// # use nostr_sdk::prelude::*; - /// # async fn example() -> Result<()> { + /// # async fn example() -> Result<(), Box> { /// # let client = Client::default(); /// # client.add_relay("wss://relay1.example.com").await?; /// # client.add_relay("wss://relay2.example.com").await?; @@ -1196,8 +1195,8 @@ mod tests { use nostr_gossip_memory::prelude::*; use nostr_relay_builder::MockRelay; - use super::{Error, *}; - use crate::pool; + use super::*; + use crate::error::ErrorKind; use crate::relay::RelayStatus; #[tokio::test] @@ -1223,10 +1222,9 @@ mod tests { // Client must be marked as shutdown assert!(client.is_shutdown()); - assert!(matches!( - client.add_relay(url).await.unwrap_err(), - Error::RelayPool(pool::Error::Shutdown) - )); + let err = client.add_relay(url).await.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::State); + assert_eq!(err.to_string(), "shutdown"); } #[tokio::test] diff --git a/sdk/src/error.rs b/sdk/src/error.rs new file mode 100644 index 000000000..4cac3807e --- /dev/null +++ b/sdk/src/error.rs @@ -0,0 +1,224 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license + +//! SDK error. + +use std::error; + +use nostr::{RelayUrl, serde_json}; + +opaquerr::define_kind! { + /// SDK error kind. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// Transport error. + Transport => "transport error", + /// Database error. + Database => "database error", + /// Gossip error. + Gossip => "gossip error", + /// Policy error. + Policy => "policy error", + /// The operation timed out. + Timeout => "timeout", + /// Required data was not found. + NotFound => "not found", + /// Input is well-formed, but violates an SDK invariant. + Invalid => "input violates an SDK invariant", + /// The operation cannot be completed in the current state. + State => "invalid state", + /// The operation was rejected. + Rejected => "operation rejected", + /// The operation is known but not supported. + Unsupported => "operation not supported", + /// A configured limit was exceeded. + LimitExceeded => "limit exceeded", + /// Anything not covered by the stable categories above. + Other => "other error", + } +} + +opaquerr::define_error! { + /// SDK error. + pub Error(ErrorKind) + + from { + nostr::error::Error => ErrorKind::Protocol, + nostr_database::error::Error => ErrorKind::Database, + nostr_gossip::error::GossipError => ErrorKind::Gossip, + serde_json::Error => ErrorKind::Protocol, + faster_hex::Error => ErrorKind::Protocol, + negentropy::Error => ErrorKind::Protocol, + tokio::sync::oneshot::error::RecvError => ErrorKind::Other, + tokio::sync::broadcast::error::RecvError => ErrorKind::Other, + } +} + +impl Error { + /// Transport error. + #[inline] + pub fn transport(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Transport, error) + } + + /// Policy error. + #[inline] + pub fn policy(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Policy, error) + } + + #[inline] + pub(crate) fn authentication_msg(msg: &'static str) -> Self { + Self::with_static_message(ErrorKind::Rejected, msg) + } + + #[inline] + pub(crate) fn gossip_not_configured() -> Self { + Self::state_msg("gossip not configured") + } + + /// Generic SDK error. + #[inline] + pub fn other(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Other, error) + } + + #[inline] + pub(crate) fn protocol_msg(msg: &'static str) -> Self { + Self::with_static_message(ErrorKind::Protocol, msg) + } + + #[inline] + pub(crate) fn invalid_msg(msg: &'static str) -> Self { + Self::with_static_message(ErrorKind::Invalid, msg) + } + + #[inline] + pub(crate) fn state_msg(msg: &'static str) -> Self { + Self::with_static_message(ErrorKind::State, msg) + } + + #[inline] + pub(crate) fn not_found(msg: &'static str) -> Self { + Self::with_static_message(ErrorKind::NotFound, msg) + } + + #[inline] + pub(crate) fn relay_not_found() -> Self { + Self::with_static_message(ErrorKind::NotFound, "relay not found") + } + + #[inline] + pub(crate) fn relay_not_found_with_url(url: &RelayUrl) -> Self { + Self::new(ErrorKind::NotFound, format!("relay '{url}' not found")) + } + + #[inline] + pub(crate) fn relays_not_specified() -> Self { + Self::with_static_message(ErrorKind::Invalid, "relay/s not specified") + } + + #[inline] + pub(crate) fn limit_exceeded(msg: &'static str) -> Self { + Self::with_static_message(ErrorKind::LimitExceeded, msg) + } + + #[inline] + pub(crate) fn timeout() -> Self { + Self::simple(ErrorKind::Timeout) + } + + #[inline] + pub(crate) fn not_connected() -> Self { + Self::with_static_message(ErrorKind::State, "relay not connected") + } + + #[inline] + pub(crate) fn shutdown() -> Self { + Self::with_static_message(ErrorKind::State, "shutdown") + } + + #[inline] + pub(crate) fn not_ready() -> Self { + Self::with_static_message(ErrorKind::State, "relay is initialized but not ready") + } + + #[inline] + pub(crate) fn banned() -> Self { + Self::with_static_message(ErrorKind::State, "relay banned") + } + + #[inline] + pub(crate) fn sleeping() -> Self { + Self::with_static_message(ErrorKind::State, "relay is sleeping") + } + + #[inline] + pub(crate) fn read_disabled() -> Self { + Self::with_static_message(ErrorKind::Unsupported, "read actions are disabled") + } + + #[inline] + pub(crate) fn write_disabled() -> Self { + Self::with_static_message(ErrorKind::Unsupported, "write actions are disabled") + } + + #[inline] + pub(crate) fn rejected_msg(msg: &'static str) -> Self { + Self::with_static_message(ErrorKind::Rejected, msg) + } + + #[inline] + pub(crate) fn rejected(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Rejected, error) + } + + #[inline] + pub(crate) fn relay_msg(msg: String) -> Self { + Self::new(ErrorKind::Rejected, msg) + } + + #[inline] + pub(crate) fn connection_rejected(reason: Option) -> Self { + match reason { + Some(reason) => Self::new( + ErrorKind::Rejected, + format!("connection rejected: reason={reason}"), + ), + None => Self::with_static_message(ErrorKind::Rejected, "connection rejected"), + } + } + + #[inline] + pub(crate) fn negentropy_not_supported() -> Self { + Self::with_static_message(ErrorKind::Unsupported, "negentropy not supported") + } + + #[inline] + pub(crate) fn unknown_negentropy_error() -> Self { + Self::with_static_message(ErrorKind::Protocol, "unknown negentropy error") + } + + #[inline] + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn pong_not_match(expected: u64, received: u64) -> Self { + Self::new( + ErrorKind::Protocol, + format!("pong not match: expected={expected}, received={received}"), + ) + } +} diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 8b229933c..d1d64c9aa 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -8,6 +8,7 @@ pub mod authenticator; pub mod client; +pub mod error; mod events_tracker; mod future; pub mod monitor; diff --git a/sdk/src/policy.rs b/sdk/src/policy.rs index 36c82c631..2bd3a6e2d 100644 --- a/sdk/src/policy.rs +++ b/sdk/src/policy.rs @@ -8,36 +8,9 @@ use std::fmt; use nostr::{Event, RelayUrl, SubscriptionId}; +use crate::error::Error; use crate::future::BoxedFuture; -/// Policy Error -#[derive(Debug)] -pub enum PolicyError { - /// An error happened in the underlying backend. - Backend(Box), -} - -impl std::error::Error for PolicyError {} - -impl fmt::Display for PolicyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Backend(e) => e.fmt(f), - } - } -} - -impl PolicyError { - /// Create a new backend error. - #[inline] - pub fn backend(error: E) -> Self - where - E: Into>, - { - Self::Backend(error.into()) - } -} - /// Admission status #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum AdmitStatus { @@ -77,7 +50,7 @@ pub trait AdmitPolicy: fmt::Debug + Send + Sync { fn admit_relay<'a>( &'a self, relay_url: &'a RelayUrl, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { let _ = relay_url; Box::pin(async move { Ok(AdmitStatus::Success) }) } @@ -88,7 +61,7 @@ pub trait AdmitPolicy: fmt::Debug + Send + Sync { fn admit_connection<'a>( &'a self, relay_url: &'a RelayUrl, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { let _ = relay_url; Box::pin(async move { Ok(AdmitStatus::Success) }) } @@ -99,7 +72,7 @@ pub trait AdmitPolicy: fmt::Debug + Send + Sync { fn admit_auth<'a>( &'a self, relay_url: &'a RelayUrl, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { let _ = relay_url; Box::pin(async move { Ok(AdmitStatus::Success) }) } @@ -112,7 +85,7 @@ pub trait AdmitPolicy: fmt::Debug + Send + Sync { relay_url: &'a RelayUrl, subscription_id: &'a SubscriptionId, event: &'a Event, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { let _ = (relay_url, subscription_id, event); Box::pin(async move { Ok(AdmitStatus::Success) }) } diff --git a/sdk/src/pool/error.rs b/sdk/src/pool/error.rs deleted file mode 100644 index 34a6433a8..000000000 --- a/sdk/src/pool/error.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::fmt; - -use nostr::RelayUrl; - -use crate::policy::PolicyError; -use crate::relay; - -/// Relay Pool error -#[derive(Debug)] -pub enum Error { - /// Policy error - Policy(PolicyError), - /// Relay error - Relay(relay::Error), - /// Too many relays - TooManyRelays { - /// Max numer allowed - limit: usize, - }, - /// No relays specified - NoRelaysSpecified, - /// Relay not found - RelayNotFound(RelayUrl), - /// Relay Pool is shutdown - Shutdown, -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Policy(e) => e.fmt(f), - Self::Relay(e) => e.fmt(f), - Self::TooManyRelays { .. } => f.write_str("too many relays"), - Self::NoRelaysSpecified => f.write_str("no relays specified"), - Self::RelayNotFound(url) => write!(f, "relay '{}' not found", url), - Self::Shutdown => f.write_str("relay pool is shutdown"), - } - } -} - -impl From for Error { - fn from(e: PolicyError) -> Self { - Self::Policy(e) - } -} - -impl From for Error { - fn from(e: relay::Error) -> Self { - Self::Relay(e) - } -} diff --git a/sdk/src/pool/mod.rs b/sdk/src/pool/mod.rs index 813c85705..df5952e1d 100644 --- a/sdk/src/pool/mod.rs +++ b/sdk/src/pool/mod.rs @@ -17,11 +17,10 @@ use nostr_database::prelude::*; use tokio::sync::{Mutex, RwLock, broadcast, mpsc}; mod builder; -mod error; pub(crate) use self::builder::RelayPoolBuilder; -pub(crate) use self::error::Error; use crate::client::{ClientNotification, InnerAckPolicy, Output, SendEventOutput, SyncSummary}; +use crate::error::Error; use crate::monitor::Monitor; use crate::policy::AdmitStatus; use crate::relay::{ @@ -164,7 +163,7 @@ impl RelayPool { ) -> Result { // Check if the pool has been shutdown if self.is_shutdown() { - return Err(Error::Shutdown); + return Err(Error::shutdown()); } // Check if the relay can be added to the pool, per-policy. @@ -201,7 +200,7 @@ impl RelayPool { // Check number fo relays and limit if let Some(max) = self.max_relays.map(|m| m.get()) { if relays.len() >= max { - return Err(Error::TooManyRelays { limit: max }); + return Err(Error::limit_exceeded("too many relays")); } } @@ -237,7 +236,7 @@ impl RelayPool { // Remove relay let relay: Relay = match relays.remove(&url) { Some(relay) => relay, - None => return Err(Error::RelayNotFound(url.into_owned())), + None => return Err(Error::relay_not_found()), }; // If NOT force, check if it has `GOSSIP` capability @@ -340,9 +339,7 @@ impl RelayPool { let relays = self.relays.read().await; // Get relay - let relay: &Relay = relays - .get(url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + let relay: &Relay = relays.get(url).ok_or_else(Error::relay_not_found)?; // Connect relay.connect(); @@ -359,9 +356,7 @@ impl RelayPool { let relays = self.relays.read().await; // Get relay - let relay: &Relay = relays - .get(url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + let relay: &Relay = relays.get(url).ok_or_else(Error::relay_not_found)?; // Try to connect relay.try_connect().timeout(timeout).await?; @@ -374,9 +369,7 @@ impl RelayPool { let relays = self.relays.read().await; // Get relay - let relay: &Relay = relays - .get(url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + let relay: &Relay = relays.get(url).ok_or_else(Error::relay_not_found)?; // Disconnect relay.disconnect(); @@ -436,7 +429,7 @@ impl RelayPool { ) -> Result, Error> { // Check if urls set is empty if set.is_empty() { - return Err(Error::NoRelaysSpecified); + return Err(Error::relays_not_specified()); } // Lock with read shared access @@ -450,7 +443,7 @@ impl RelayPool { for url in set.into_iter() { let relay: &Relay = relays .get(&url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + .ok_or_else(|| Error::relay_not_found_with_url(&url))?; urls.push(url); futures.push( relay @@ -495,7 +488,7 @@ impl RelayPool { // Check if urls set is empty if set.is_empty() { - return Err(Error::NoRelaysSpecified); + return Err(Error::relays_not_specified()); } // Lock with read shared access @@ -514,7 +507,7 @@ impl RelayPool { // Try to get relay let relay: &Relay = relays .get(&url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + .ok_or_else(|| Error::relay_not_found_with_url(&url))?; // Create the future request futures.push(async move { @@ -557,7 +550,7 @@ impl RelayPool { ) -> Result, Error> { // Check if urls set is empty if filters.is_empty() { - return Err(Error::NoRelaysSpecified); + return Err(Error::relays_not_specified()); } // Lock with read shared access @@ -574,7 +567,7 @@ impl RelayPool { // Get relay let relay: &Relay = relays .get(&url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + .ok_or_else(|| Error::relay_not_found_with_url(&url))?; // Prepare let id: SubscriptionId = id.clone(); @@ -690,7 +683,7 @@ impl RelayPool { ) -> Result, Error> { // Check if urls set is empty if targets.is_empty() { - return Err(Error::NoRelaysSpecified); + return Err(Error::relays_not_specified()); } // Lock with read shared access @@ -706,7 +699,7 @@ impl RelayPool { for (url, (filter, items)) in targets.into_iter() { let relay: &Relay = relays .get(&url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + .ok_or_else(|| Error::relay_not_found_with_url(&url))?; urls.push(url); futures.push( relay @@ -746,7 +739,7 @@ impl RelayPool { ) -> Result, Error> { // Check if `targets` map is empty if filters.is_empty() { - return Err(Error::NoRelaysSpecified); + return Err(Error::relays_not_specified()); } // Lock with read shared access @@ -766,7 +759,7 @@ impl RelayPool { // Try to get the relay let relay: &Relay = relays .get(&url) - .ok_or_else(|| Error::RelayNotFound(url.clone()))?; + .ok_or_else(|| Error::relay_not_found_with_url(&url))?; // Push url urls.push(url); diff --git a/sdk/src/prelude.rs b/sdk/src/prelude.rs index d6960f6e9..42ba84796 100644 --- a/sdk/src/prelude.rs +++ b/sdk/src/prelude.rs @@ -15,6 +15,7 @@ pub use nostr_gossip::prelude::*; pub use crate::authenticator::{self, *}; pub use crate::client::{self, *}; +pub use crate::error::{self, Error, ErrorKind}; pub use crate::monitor::{self, *}; pub use crate::policy::*; #[cfg(not(target_arch = "wasm32"))] diff --git a/sdk/src/relay/api/fetch_events.rs b/sdk/src/relay/api/fetch_events.rs index 2adc1fbd0..80cc1ae71 100644 --- a/sdk/src/relay/api/fetch_events.rs +++ b/sdk/src/relay/api/fetch_events.rs @@ -5,8 +5,9 @@ use futures::StreamExt; use nostr::{Event, Filter}; use nostr_database::Events; +use crate::error::Error; use crate::future::BoxedFuture; -use crate::relay::{Error, Relay, ReqExitPolicy}; +use crate::relay::{Relay, ReqExitPolicy}; /// Fetch events #[must_use = "Does nothing unless you await!"] @@ -99,7 +100,7 @@ mod tests { use super::*; use crate::authenticator::SignerAuthenticator; - use crate::relay::{Error, RelayOptions, RelayStatus}; + use crate::relay::{RelayOptions, RelayStatus}; /// Setup public (without NIP42 auth) relay with N events to test event fetching /// @@ -195,15 +196,10 @@ mod tests { .timeout(Duration::from_secs(5)) .await .unwrap_err(); - match err { - Error::RelayMessage(msg) => { - assert_eq!( - MachineReadablePrefix::parse(&msg).unwrap(), - MachineReadablePrefix::AuthRequired - ); - } - e => panic!("Unexpected error: {e}"), - } + assert_eq!( + MachineReadablePrefix::parse(&err.to_string()).unwrap(), + MachineReadablePrefix::AuthRequired + ); } #[tokio::test] diff --git a/sdk/src/relay/api/send_event.rs b/sdk/src/relay/api/send_event.rs index d74abd0a2..c27091d4d 100644 --- a/sdk/src/relay/api/send_event.rs +++ b/sdk/src/relay/api/send_event.rs @@ -8,8 +8,9 @@ use nostr::message::MachineReadablePrefix; use nostr::{ClientMessage, Event, EventId}; use tokio::sync::broadcast; +use crate::error::Error; use crate::future::BoxedFuture; -use crate::relay::{Error, Relay, RelayNotification}; +use crate::relay::{Relay, RelayNotification}; /// Output returned when sending an event to a single relay. #[derive(Debug, Clone, PartialEq, Eq)] @@ -189,19 +190,19 @@ async fn wait_for_authentication( return Ok(()); } RelayNotification::AuthenticationFailed => { - return Err(Error::AuthenticationFailed); + return Err(Error::authentication_msg("authentication failed")); } RelayNotification::RelayStatus { status } if status.is_disconnected() => { - return Err(Error::NotConnected); + return Err(Error::not_connected()); } _ => (), } } - Err(Error::PrematureExit) + Err(Error::state_msg("premature exit")) }) .await - .ok_or(Error::Timeout)? + .ok_or_else(Error::timeout)? } impl<'relay, 'event> IntoFuture for SendEvent<'relay, 'event> @@ -232,7 +233,7 @@ where return Ok(RelaySendEventOutput::new(self.event.id, status)); } - // If auth required, wait for authentication adn resend it + // If auth required, wait for authentication and resend it if let Some(MachineReadablePrefix::AuthRequired) = MachineReadablePrefix::parse(&message) { @@ -255,12 +256,12 @@ where EventSendStatus::ack(message), )) } else { - Err(Error::RelayMessage(message)) + Err(Error::relay_msg(message)) }; } } - Err(Error::RelayMessage(message)) + Err(Error::relay_msg(message)) }) } } @@ -351,15 +352,11 @@ mod tests { let event = EventBuilder::text_note("Test").finalize(&keys).unwrap(); // Auth disabled, so must fails as is unauthenticated - match relay.send_event(&event).await.unwrap_err() { - crate::relay::Error::RelayMessage(msg) => { - assert_eq!( - MachineReadablePrefix::parse(&msg).unwrap(), - MachineReadablePrefix::AuthRequired - ); - } - e => panic!("Unexpected error: {e}"), - } + let err = relay.send_event(&event).await.unwrap_err(); + assert_eq!( + MachineReadablePrefix::parse(&err.to_string()).unwrap(), + MachineReadablePrefix::AuthRequired + ); } #[tokio::test] diff --git a/sdk/src/relay/api/send_msg.rs b/sdk/src/relay/api/send_msg.rs index f3e155a64..6432bb43f 100644 --- a/sdk/src/relay/api/send_msg.rs +++ b/sdk/src/relay/api/send_msg.rs @@ -3,8 +3,9 @@ use std::time::Duration; use nostr::ClientMessage; +use crate::error::Error; use crate::future::BoxedFuture; -use crate::relay::{Error, Relay}; +use crate::relay::Relay; /// Send the client message #[must_use = "Does nothing unless you await!"] @@ -59,6 +60,7 @@ mod tests { use nostr::{Filter, RelayUrl, SubscriptionId}; use super::*; + use crate::error::ErrorKind; // At the moment, before consider a relay disconnected, are required at least 2 attempts and a success rate < 90% #[tokio::test] @@ -70,7 +72,7 @@ mod tests { // First attempt let res = relay.try_connect().timeout(Duration::from_secs(1)).await; - assert!(matches!(res.unwrap_err(), Error::Transport(_))); + assert_eq!(res.unwrap_err().kind(), ErrorKind::Transport); // Relay is disconnected, but is required another attempt before consider it non-connected let res = relay.send_msg(msg.clone()).await; @@ -78,10 +80,12 @@ mod tests { // Second attempt let res = relay.try_connect().timeout(Duration::from_secs(1)).await; - assert!(matches!(res.unwrap_err(), Error::Transport(_))); + assert_eq!(res.unwrap_err().kind(), ErrorKind::Transport); // Relay is disconnected, we have a 2 attempts and success rate is < 90% let res = relay.send_msg(msg).await; - assert!(matches!(res.unwrap_err(), Error::NotConnected)); + let err = res.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::State); + assert_eq!(err.to_string(), "relay not connected"); } } diff --git a/sdk/src/relay/api/stream_events.rs b/sdk/src/relay/api/stream_events.rs index 8fc2975d8..fe52fb1e2 100644 --- a/sdk/src/relay/api/stream_events.rs +++ b/sdk/src/relay/api/stream_events.rs @@ -8,9 +8,10 @@ use nostr::{Event, Filter, SubscriptionId}; use tokio::sync::{mpsc, oneshot}; use super::subscribe::subscribe_auto_closing; +use crate::error::Error; use crate::future::BoxedFuture; use crate::relay::{ - Error, Relay, ReqExitPolicy, SubscribeAutoCloseOptions, SubscriptionActivity, + Relay, ReqExitPolicy, SubscribeAutoCloseOptions, SubscriptionActivity, SubscriptionAutoClosedReason, }; @@ -159,11 +160,13 @@ impl Stream for SubscriptionActivityEventStream { SubscriptionActivity::Closed(reason) => match reason { SubscriptionAutoClosedReason::AuthenticationFailed => { self.done = true; - Poll::Ready(Some(RelayStreamEvent::Error(Error::AuthenticationFailed))) + Poll::Ready(Some(RelayStreamEvent::Error(Error::authentication_msg( + "authentication failed", + )))) } SubscriptionAutoClosedReason::Closed(message) => { self.done = true; - Poll::Ready(Some(RelayStreamEvent::Error(Error::RelayMessage(message)))) + Poll::Ready(Some(RelayStreamEvent::Error(Error::relay_msg(message)))) } SubscriptionAutoClosedReason::Completed => { self.done = true; diff --git a/sdk/src/relay/api/subscribe.rs b/sdk/src/relay/api/subscribe.rs index d91db7156..de81a1d80 100644 --- a/sdk/src/relay/api/subscribe.rs +++ b/sdk/src/relay/api/subscribe.rs @@ -4,8 +4,9 @@ use std::future::IntoFuture; use nostr::{ClientMessage, Filter, SubscriptionId}; use tokio::sync::{mpsc, oneshot}; +use crate::error::Error; use crate::future::BoxedFuture; -use crate::relay::{Error, Relay, SubscribeAutoCloseOptions, SubscriptionActivity}; +use crate::relay::{Relay, SubscribeAutoCloseOptions, SubscriptionActivity}; /// Subscribe to events #[must_use = "Does nothing unless you await!"] @@ -52,7 +53,7 @@ pub(super) async fn subscribe_auto_closing( ) -> Result<(), Error> { // Check if filters are empty if filters.is_empty() { - return Err(Error::EmptyFilters); + return Err(Error::invalid_msg("filters cannot be empty")); } // Compose REQ message @@ -95,7 +96,7 @@ async fn subscribe_long_lived( ) -> Result<(), Error> { // Check if filters are empty if filters.is_empty() { - return Err(Error::EmptyFilters); + return Err(Error::invalid_msg("filters cannot be empty")); } // Compose REQ message diff --git a/sdk/src/relay/api/sync.rs b/sdk/src/relay/api/sync.rs index 88fa4a4dd..c825a43e7 100644 --- a/sdk/src/relay/api/sync.rs +++ b/sdk/src/relay/api/sync.rs @@ -9,12 +9,13 @@ use negentropy::{Id, Negentropy, NegentropyStorageVector}; use nostr::{ClientMessage, EventId, Filter, RelayMessage, SubscriptionId, Timestamp}; use tokio::sync::broadcast; +use crate::error::Error; use crate::future::BoxedFuture; use crate::relay::constants::{ NEGENTROPY_BATCH_SIZE_DOWN, NEGENTROPY_FRAME_SIZE_LIMIT, NEGENTROPY_HIGH_WATER_UP, NEGENTROPY_LOW_WATER_UP, }; -use crate::relay::{Error, Relay, RelayNotification, SyncOptions}; +use crate::relay::{Relay, RelayNotification, SyncOptions}; /// Relay negentropy reconciliation summary #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -347,10 +348,10 @@ pub(super) async fn sync( loop { let notification = time::timeout(Some(opts.idle_timeout), notifications.recv()) .await - .ok_or(Error::Timeout)??; + .ok_or(Error::timeout())??; if last_relevant_msg.elapsed() > opts.idle_timeout { - return Err(Error::Timeout); + return Err(Error::timeout()); } match notification { @@ -411,7 +412,7 @@ pub(super) async fn sync( } => { #[allow(clippy::collapsible_match)] if subscription_id.as_ref() == &sub_id { - return Err(Error::RelayMessage(message.into_owned())); + return Err(Error::relay_msg(message.into_owned())); } else { // Not relevant to this sync false @@ -496,7 +497,7 @@ pub(super) async fn sync( } } RelayNotification::RelayStatus { status } if status.is_disconnected() => { - return Err(Error::NotConnected); + return Err(Error::not_connected()); } _ => (), }; @@ -557,26 +558,24 @@ async fn check_negentropy_support( subscription_id, message, } if subscription_id.as_ref() == sub_id => { - return Err(Error::RelayMessage(message.into_owned())); + return Err(Error::relay_msg(message.into_owned())); } RelayMessage::Notice(message) => { if message == "ERROR: negentropy error: negentropy query missing elements" { // The NEG-OPEN message is sent with 4 elements instead of 5 // If the relay return this error means that is not support new // negentropy protocol - return Err(Error::Negentropy( - negentropy::Error::UnsupportedProtocolVersion, - )); + return Err(negentropy::Error::UnsupportedProtocolVersion.into()); } else if message.contains("bad msg") && (message.contains("unknown cmd") || message.contains("negentropy") || message.contains("NEG-")) { - return Err(Error::NegentropyNotSupported); + return Err(Error::negentropy_not_supported()); } else if message.contains("bad msg: invalid message") && message.contains("NEG-OPEN") { - return Err(Error::UnknownNegentropyError); + return Err(Error::unknown_negentropy_error()); } } _ => (), @@ -587,7 +586,7 @@ async fn check_negentropy_support( Ok(()) }) .await - .ok_or(Error::Timeout)? + .ok_or_else(Error::timeout)? } impl<'relay> IntoFuture for SyncEvents<'relay> { @@ -601,7 +600,7 @@ impl<'relay> IntoFuture for SyncEvents<'relay> { // Check if relay can read if !self.relay.inner.capabilities.can_read() { - return Err(Error::ReadDisabled); + return Err(Error::read_disabled()); } let items: Vec<(EventId, Timestamp)> = match self.items { @@ -640,7 +639,8 @@ mod tests { use tokio::sync::broadcast; use super::*; - use crate::relay::{Error, SyncDirection, SyncOptions}; + use crate::error::ErrorKind; + use crate::relay::{SyncDirection, SyncOptions}; #[tokio::test] async fn test_check_negentropy_support_times_out() { @@ -652,7 +652,7 @@ mod tests { .await .unwrap_err(); - assert!(matches!(error, Error::Timeout)); + assert_eq!(error.kind(), ErrorKind::Timeout); } #[tokio::test] @@ -667,10 +667,7 @@ mod tests { .await .unwrap_err(); - assert!(matches!( - error, - Error::BroadcastRecv(broadcast::error::RecvError::Closed) - )); + assert_eq!(error.kind(), ErrorKind::Other); } #[tokio::test] diff --git a/sdk/src/relay/api/try_connect.rs b/sdk/src/relay/api/try_connect.rs index 87ed3a8a8..b87c07438 100644 --- a/sdk/src/relay/api/try_connect.rs +++ b/sdk/src/relay/api/try_connect.rs @@ -1,9 +1,10 @@ use std::future::IntoFuture; use std::time::Duration; +use crate::error::Error; use crate::future::BoxedFuture; use crate::policy::AdmitStatus; -use crate::relay::{Error, Relay, RelayStatus}; +use crate::relay::{Relay, RelayStatus}; use crate::transport::websocket::{WebSocketSink, WebSocketStream}; /// Try to connect relay @@ -39,11 +40,11 @@ impl<'relay> IntoFuture for TryConnect<'relay> { let status: RelayStatus = self.relay.status(); if status.is_shutdown() { - return Err(Error::Shutdown); + return Err(Error::shutdown()); } if status.is_banned() { - return Err(Error::Banned); + return Err(Error::banned()); } // Check if relay can't connect @@ -59,7 +60,7 @@ impl<'relay> IntoFuture for TryConnect<'relay> { self.relay.inner.set_status(RelayStatus::Terminated, false); // Return error - return Err(Error::ConnectionRejected { reason }); + return Err(Error::connection_rejected(reason)); } // Try to connect @@ -84,7 +85,8 @@ mod tests { use nostr::RelayUrl; use nostr_relay_builder::prelude::*; - use super::{Error, *}; + use super::*; + use crate::error::ErrorKind; #[tokio::test] async fn test_try_connect() { @@ -118,7 +120,7 @@ mod tests { assert_eq!(relay.status(), RelayStatus::Initialized); let res = relay.try_connect().timeout(Duration::from_secs(2)).await; - assert!(matches!(res.unwrap_err(), Error::Transport(..))); + assert_eq!(res.unwrap_err().kind(), ErrorKind::Transport); assert_eq!(relay.status(), RelayStatus::Terminated); diff --git a/sdk/src/relay/api/unsubscribe.rs b/sdk/src/relay/api/unsubscribe.rs index 36d9ef858..acfcca414 100644 --- a/sdk/src/relay/api/unsubscribe.rs +++ b/sdk/src/relay/api/unsubscribe.rs @@ -2,8 +2,9 @@ use std::future::IntoFuture; use nostr::SubscriptionId; +use crate::error::Error; use crate::future::BoxedFuture; -use crate::relay::{Error, Relay}; +use crate::relay::Relay; /// Unsubscribe from a REQ #[must_use = "Does nothing unless you await!"] diff --git a/sdk/src/relay/api/unsubscribe_all.rs b/sdk/src/relay/api/unsubscribe_all.rs index 62ebaa09a..ab93c42be 100644 --- a/sdk/src/relay/api/unsubscribe_all.rs +++ b/sdk/src/relay/api/unsubscribe_all.rs @@ -1,7 +1,8 @@ use std::future::IntoFuture; +use crate::error::Error; use crate::future::BoxedFuture; -use crate::relay::{Error, Relay}; +use crate::relay::Relay; /// Unsubscribe from all REQs #[must_use = "Does nothing unless you await!"] diff --git a/sdk/src/relay/error.rs b/sdk/src/relay/error.rs deleted file mode 100644 index b18c716b4..000000000 --- a/sdk/src/relay/error.rs +++ /dev/null @@ -1,262 +0,0 @@ -use std::fmt; -use std::time::Duration; - -use nostr_gossip::error::GossipError; -use tokio::sync::{broadcast, oneshot}; - -use crate::policy::PolicyError; -use crate::transport::error::TransportError; - -/// Relay error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Any error - Any(Box), - /// Transport error - Transport(TransportError), - /// Policy error - Policy(PolicyError), - /// Database error - Database(nostr_database::error::Error), - /// Gossip error - Gossip(GossipError), - /// Hex error - Hex(faster_hex::Error), - /// Negentropy error - Negentropy(negentropy::Error), - /// Oneshot recv error - OneshotRecv(oneshot::error::RecvError), - /// Broadcast recv error - BroadcastRecv(broadcast::error::RecvError), - /// Generic timeout - Timeout, - /// Not replied to ping - NotRepliedToPing, - /// Can't parse pong - CantParsePong, - /// Pong not match - PongNotMatch { - /// Expected nonce - expected: u64, - /// Received nonce - received: u64, - }, - /// Can't send message to the transport dispatcher - CantSendMessageToDispatcher, - /// Relay not ready - NotReady, - /// Relay not connected - NotConnected, - /// Relay is sleeping - Sleeping, - /// Relay subscription id not exist - SubscriptionNotFound, - /// Event doesn't match the subscription filter - EventNotMatchFilter, - /// Received too many events for the subscription - TooManyEvents, - /// Relay banned - Banned, - /// Relay shutdown - Shutdown, - /// Connection rejected - ConnectionRejected { - /// Reason - reason: Option, - }, - /// Received termination request - TerminationRequest, - /// Relay message - RelayMessage(String), - /// Read actions disabled - ReadDisabled, - /// Write actions disabled - WriteDisabled, - /// Negentropy not supported - NegentropyNotSupported, - /// Unknown negentropy error - UnknownNegentropyError, - /// Relay message too large - RelayMessageTooLarge { - /// Message size - size: usize, - /// Max message size - max_size: usize, - }, - /// Event too large - EventTooLarge { - /// Event size - size: usize, - /// Max event size - max_size: usize, - }, - /// Too many tags - TooManyTags { - /// Tags num - size: usize, - /// Max tags num - max_size: usize, - }, - /// Event expired - EventExpired, - /// Max latency exceeded - MaximumLatencyExceeded { - /// Max - max: Duration, - /// Current - current: Duration, - }, - /// Auth failed - AuthenticationFailed, - /// Auth not admitted - AuthenticationNotAdmitted { - /// Rejection reason - reason: Option, - }, - /// Premature exit - PrematureExit, - /// An empty list of filters has been provided - EmptyFilters, - /// The authenticator is not configured - AuthenticatorNotConfigured, - /// The authentication event is invalid - AuthenticationEventInvalid, -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => e.fmt(f), - Self::Any(e) => e.fmt(f), - Self::Transport(e) => write!(f, "transport: {e}"), - Self::Policy(e) => write!(f, "policy: {e}"), - Self::Database(e) => write!(f, "database: {e}"), - Self::Gossip(e) => write!(f, "gossip: {e}"), - Self::Hex(e) => e.fmt(f), - Self::Negentropy(e) => e.fmt(f), - Self::OneshotRecv(e) => e.fmt(f), - Self::BroadcastRecv(e) => e.fmt(f), - Self::Timeout => f.write_str("timeout"), - Self::NotRepliedToPing => f.write_str("not replied to ping"), - Self::CantParsePong => f.write_str("can't parse pong"), - Self::PongNotMatch { expected, received } => write!( - f, - "pong not match: expected={expected}, received={received}" - ), - Self::CantSendMessageToDispatcher => { - f.write_str("can't send message to the transport dispatcher") - } - Self::NotReady => f.write_str("relay is initialized but not ready"), - Self::NotConnected => f.write_str("relay not connected"), - Self::SubscriptionNotFound => f.write_str("subscription not found"), - Self::EventNotMatchFilter => f.write_str("event doesn't match the subscription filter"), - Self::TooManyEvents => f.write_str("received too many events for the subscription"), - Self::Sleeping => f.write_str("relay is sleeping"), - Self::Banned => f.write_str("relay banned"), - Self::Shutdown => f.write_str("relay shutdown"), - Self::ConnectionRejected { reason } => { - let reason: &str = reason.as_deref().unwrap_or("unknown"); - write!(f, "connection rejected: reason={reason}") - } - Self::TerminationRequest => f.write_str("received termination request"), - Self::RelayMessage(message) => f.write_str(message), - Self::ReadDisabled => f.write_str("read actions are disabled"), - Self::WriteDisabled => f.write_str("write actions are disabled"), - Self::NegentropyNotSupported => f.write_str("negentropy not supported"), - Self::UnknownNegentropyError => f.write_str("unknown negentropy error"), - Self::RelayMessageTooLarge { size, max_size } => write!( - f, - "Received message too large: size={size}, max_size={max_size}" - ), - Self::EventTooLarge { size, max_size } => write!( - f, - "Received event too large: size={size}, max_size={max_size}" - ), - Self::TooManyTags { size, max_size } => write!( - f, - "Received event with too many tags: tags={size}, max_tags={max_size}" - ), - Self::EventExpired => f.write_str("event expired"), - Self::MaximumLatencyExceeded { max, current } => write!( - f, - "Maximum latency exceeded: max={}ms, current={}ms", - max.as_millis(), - current.as_millis() - ), - Self::AuthenticationFailed => f.write_str("authentication failed"), - Self::AuthenticationNotAdmitted { reason } => match reason { - Some(reason) => write!(f, "authentication not admitted: {reason}"), - None => f.write_str("authentication not admitted"), - }, - Self::PrematureExit => f.write_str("premature exit"), - Self::EmptyFilters => f.write_str("empty filters"), - Self::AuthenticatorNotConfigured => f.write_str("authenticator is not configured"), - Self::AuthenticationEventInvalid => f.write_str("authentication event is invalid"), - } - } -} - -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} - -impl From> for Error { - #[inline] - fn from(e: Box) -> Self { - Self::Any(e) - } -} - -impl From for Error { - fn from(e: TransportError) -> Self { - Self::Transport(e) - } -} - -impl From for Error { - fn from(e: PolicyError) -> Self { - Self::Policy(e) - } -} - -impl From for Error { - fn from(e: nostr_database::error::Error) -> Self { - Self::Database(e) - } -} - -impl From for Error { - fn from(e: GossipError) -> Self { - Self::Gossip(e) - } -} - -impl From for Error { - fn from(e: faster_hex::Error) -> Self { - Self::Hex(e) - } -} - -impl From for Error { - fn from(e: negentropy::Error) -> Self { - Self::Negentropy(e) - } -} - -impl From for Error { - fn from(e: oneshot::error::RecvError) -> Self { - Self::OneshotRecv(e) - } -} - -impl From for Error { - fn from(e: broadcast::error::RecvError) -> Self { - Self::BroadcastRecv(e) - } -} diff --git a/sdk/src/relay/inner.rs b/sdk/src/relay/inner.rs index 1ff7768a7..6f2400e1e 100644 --- a/sdk/src/relay/inner.rs +++ b/sdk/src/relay/inner.rs @@ -27,14 +27,12 @@ use super::constants::{ use super::options::{RelayOptions, ReqExitPolicy, SubscribeAutoCloseOptions}; use super::ping::PingTracker; use super::stats::RelayConnectionStats; -use super::{ - Error, RelayNotification, RelayStatus, SubscriptionActivity, SubscriptionAutoClosedReason, -}; +use super::{RelayNotification, RelayStatus, SubscriptionActivity, SubscriptionAutoClosedReason}; use crate::client::ClientNotification; +use crate::error::Error; use crate::policy::AdmitStatus; use crate::relay::status::AtomicRelayStatus; use crate::shared::SharedState; -use crate::transport::error::TransportError; use crate::transport::websocket::{WebSocketSink, WebSocketStream}; type ClientMessageJson = String; @@ -84,7 +82,7 @@ impl RelayChannels { self.nostr .0 .try_send(msg) - .map_err(|_| Error::CantSendMessageToDispatcher) + .map_err(|_| Error::state_msg("can't send message to the transport dispatcher")) } #[inline] @@ -240,17 +238,17 @@ impl InnerRelay { // Relay is not ready (never called connect method) if status.is_initialized() { - return Err(Error::NotReady); + return Err(Error::not_ready()); } // The relay has been banned if status.is_banned() { - return Err(Error::Banned); + return Err(Error::banned()); } // Sanity-check, to ensure that the relay is not sleeping. if status.is_sleeping() { - return Err(Error::Sleeping); + return Err(Error::sleeping()); } // This is needed to allow giving the time to the relay to connect, @@ -266,7 +264,7 @@ impl InnerRelay { && self.stats.success_rate() < MIN_SUCCESS_RATE && self.stats.woke_up_at() + self.opts.connect_timeout < Timestamp::now() { - return Err(Error::NotConnected); + return Err(Error::not_connected()); } // Check avg. latency @@ -277,7 +275,7 @@ impl InnerRelay { // ONLY LATER get the latency, to avoid unnecessary calculation if let Some(current) = self.stats.latency() { if current > max { - return Err(Error::MaximumLatencyExceeded { max, current }); + return Err(Error::limit_exceeded("maximum latency exceeded")); } } } @@ -687,18 +685,18 @@ impl InnerRelay { self.set_status(status_on_failure, false); // Return error - Err(Error::Transport(e)) + Err(Error::transport(e)) } None => { // Update status self.set_status(status_on_failure, false); // Return error - Err(Error::Transport(TransportError::timeout())) + Err(Error::timeout()) } }, // Handle termination notification - _ = self.handle_terminate() => Err(Error::TerminationRequest), + _ = self.handle_terminate() => Err(Error::rejected_msg("received termination request")), } } @@ -839,7 +837,7 @@ impl InnerRelay { // If the last nonce is NOT 0, check if relay replied. // Return error if relay not replied if ping.last_nonce() != 0 && !ping.replied() { - return Err(Error::NotRepliedToPing); + return Err(Error::timeout()); } // Generate and save nonce @@ -895,10 +893,7 @@ impl InnerRelay { // Check if last nonce not matches the received one if last_nonce != nonce { - return Err(Error::PongNotMatch { - expected: last_nonce, - received: nonce, - }); + return Err(Error::pong_not_match(last_nonce, nonce)); } // Set ping as replied @@ -909,7 +904,7 @@ impl InnerRelay { self.stats.save_latency(sent_at.elapsed()); } Err(..) => { - return Err(Error::CantParsePong); + return Err(Error::protocol_msg("can't parse pong")); } } } @@ -1105,7 +1100,7 @@ impl InnerRelay { if let Some(max_size) = self.opts.limits.messages.max_size { let max_size: usize = max_size as usize; if size > max_size { - return Err(Error::RelayMessageTooLarge { size, max_size }); + return Err(Error::limit_exceeded("relay message too large")); } } @@ -1132,7 +1127,7 @@ impl InnerRelay { let size: usize = event.as_json().len(); let max_size: usize = max_size as usize; if size > max_size { - return Err(Error::EventTooLarge { size, max_size }); + return Err(Error::limit_exceeded("event is too large")); } } @@ -1141,10 +1136,7 @@ impl InnerRelay { let size: usize = event.tags.len(); let max_num_tags: usize = max_num_tags as usize; if size > max_num_tags { - return Err(Error::TooManyTags { - size, - max_size: max_num_tags, - }); + return Err(Error::limit_exceeded("too many tags")); } } @@ -1163,7 +1155,7 @@ impl InnerRelay { .. } = subscriptions .get(&subscription_id) - .ok_or(Error::SubscriptionNotFound)?; + .ok_or_else(|| Error::not_found("subscription not found"))?; // Check filter limit ONLY if EOSE is not received yet and if there is only ONE filter. // We can't ensure that limit is respected if there is more than one filter. @@ -1184,7 +1176,7 @@ impl InnerRelay { self.ban(); } - return Err(Error::TooManyEvents); + return Err(Error::limit_exceeded("too many events")); } } } @@ -1201,13 +1193,15 @@ impl InnerRelay { self.ban(); } - return Err(Error::EventNotMatchFilter); + return Err(Error::protocol_msg( + "event doesn't match the subscription filter", + )); } } // Check if the event is expired if event.is_expired() { - return Err(Error::EventExpired); + return Err(Error::invalid_msg("event expired")); } // Check event admission policy @@ -1339,12 +1333,12 @@ impl InnerRelay { // If it can't write, check if there are "write" messages if !self.capabilities.can_write() && msg.is_event() { - return Err(Error::WriteDisabled); + return Err(Error::write_disabled()); } // If it can't read, check if there are "read" messages if !self.capabilities.can_read() && (msg.is_req() || msg.is_close()) { - return Err(Error::ReadDisabled); + return Err(Error::read_disabled()); } match wait_until_sent { @@ -1361,7 +1355,7 @@ impl InnerRelay { // Wait for confirmation Ok(time::timeout(Some(timeout), rx) .await - .ok_or(Error::Timeout)??) + .ok_or_else(Error::timeout)??) } None => self.atomic.channels.send_client_msg(JsonMessageItem { json: msg.as_json(), @@ -1374,12 +1368,17 @@ impl InnerRelay { // Check if the relay can authenticate if let Some(policy) = &self.state.admit_policy { if let AdmitStatus::Rejected { reason } = policy.admit_auth(&self.url).await? { - return Err(Error::AuthenticationNotAdmitted { reason }); + return match reason { + Some(reason) => Err(Error::rejected(format!( + "authentication rejected: {reason}" + ))), + None => Err(Error::authentication_msg("authentication rejected")), + }; } } let Some(authenticator) = &self.state.authenticator else { - return Err(Error::AuthenticatorNotConfigured); + return Err(Error::state_msg("no authenticator available")); }; // Create the NIP-42 auth event @@ -1387,7 +1386,7 @@ impl InnerRelay { // Ensure event is valid if !nip42::is_valid_auth_event(&event, &self.url, &challenge) { - return Err(Error::AuthenticationEventInvalid); + return Err(Error::invalid_msg("invalid auth event")); } // Subscribe to notifications @@ -1407,7 +1406,7 @@ impl InnerRelay { if status { Ok(()) } else { - Err(Error::RelayMessage(message)) + Err(Error::relay_msg(message)) } } @@ -1434,16 +1433,16 @@ impl InnerRelay { } } RelayNotification::RelayStatus { status } if status.is_disconnected() => { - return Err(Error::NotConnected); + return Err(Error::not_connected()); } _ => (), } } - Err(Error::PrematureExit) + Err(Error::state_msg("premature exit")) }) .await - .ok_or(Error::Timeout)? + .ok_or_else(Error::timeout)? } pub async fn resubscribe(&self) -> Result<(), Error> { @@ -1779,7 +1778,7 @@ impl InnerRelay { async fn send_ws_msg(tx: &mut WebSocketSink, msg: Message) -> Result<(), Error> { match time::timeout(Some(WEBSOCKET_TX_TIMEOUT), tx.send(msg)).await { Some(res) => Ok(res?), - None => Err(Error::Timeout), + None => Err(Error::timeout()), } } @@ -1788,7 +1787,7 @@ async fn close_ws(tx: &mut WebSocketSink) -> Result<(), Error> { // TODO: remove timeout from here? match time::timeout(Some(WEBSOCKET_TX_TIMEOUT), tx.close()).await { Some(res) => Ok(res?), - None => Err(Error::Timeout), + None => Err(Error::timeout()), } } diff --git a/sdk/src/relay/mod.rs b/sdk/src/relay/mod.rs index ff40ae691..245fe2a07 100644 --- a/sdk/src/relay/mod.rs +++ b/sdk/src/relay/mod.rs @@ -19,7 +19,6 @@ mod api; mod builder; mod capabilities; mod constants; -mod error; mod inner; mod limits; mod notification; @@ -31,7 +30,6 @@ mod status; pub use self::api::*; pub use self::builder::*; pub use self::capabilities::*; -pub use self::error::Error; use self::inner::InnerRelay; pub use self::limits::*; pub use self::notification::*; @@ -39,6 +37,7 @@ pub use self::options::*; pub use self::stats::*; pub use self::status::*; use crate::client::ClientNotification; +use crate::error::Error; use crate::shared::SharedState; use crate::stream::NotificationStream; @@ -440,7 +439,7 @@ impl Relay { } }) .await - .ok_or(Error::Timeout)?; + .ok_or_else(Error::timeout)?; // Unsubscribe self.send_msg(ClientMessage::close(id)).await?; @@ -463,8 +462,9 @@ mod tests { use async_utility::time; use nostr_relay_builder::prelude::*; - use super::{Error, *}; - use crate::policy::{AdmitPolicy, AdmitStatus, PolicyError}; + use super::*; + use crate::error::{Error, ErrorKind}; + use crate::policy::{AdmitPolicy, AdmitStatus}; #[derive(Debug)] struct CustomTestPolicy { @@ -475,7 +475,7 @@ mod tests { fn admit_connection<'a>( &'a self, relay_url: &'a RelayUrl, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { if self.banned_relays.contains(relay_url) { Ok(AdmitStatus::rejected("banned")) @@ -741,7 +741,9 @@ mod tests { }); let res = relay.try_connect().timeout(Duration::from_secs(7)).await; - assert!(matches!(res.unwrap_err(), Error::TerminationRequest)); + let err = res.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::Rejected); + assert_eq!(err.to_string(), "received termination request"); assert_eq!(relay.status(), RelayStatus::Terminated); @@ -773,7 +775,9 @@ mod tests { // Retry to connect let res = relay.try_connect().timeout(Duration::from_secs(2)).await; - assert!(matches!(res.unwrap_err(), Error::Banned)); + let err = res.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::State); + assert_eq!(err.to_string(), "relay banned"); assert_eq!(relay.status(), RelayStatus::Banned); @@ -784,7 +788,9 @@ mod tests { // Health check let res = relay.inner.ensure_operational(); - assert!(matches!(res.unwrap_err(), Error::Banned)); + let err = res.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::State); + assert_eq!(err.to_string(), "relay banned"); } #[tokio::test] @@ -815,7 +821,9 @@ mod tests { // Attempt to reconnect: must fail let res = relay.try_connect().timeout(Duration::from_secs(3)).await; - assert!(matches!(res.unwrap_err(), Error::Shutdown)); + let err = res.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::State); + assert_eq!(err.to_string(), "shutdown"); } #[tokio::test] @@ -934,7 +942,9 @@ mod tests { // Retry to connect let res = relay.try_connect().timeout(Duration::from_secs(2)).await; - assert!(matches!(res.unwrap_err(), Error::ConnectionRejected { .. })); + let err = res.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::Rejected); + assert_eq!(err.to_string(), "connection rejected: reason=banned"); assert_eq!(relay.status(), RelayStatus::Terminated); assert!(!relay.inner.is_running()); diff --git a/sdk/src/transport/error.rs b/sdk/src/transport/error.rs deleted file mode 100644 index 908b270f8..000000000 --- a/sdk/src/transport/error.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Transport error - -use std::fmt; -use std::io::{self, ErrorKind}; - -/// Transport Error -#[derive(Debug)] -pub enum TransportError { - /// I/O error - IO(io::Error), - /// An error happened in the underlying backend. - Backend(Box), -} - -impl std::error::Error for TransportError {} - -impl fmt::Display for TransportError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::IO(e) => e.fmt(f), - Self::Backend(e) => e.fmt(f), - } - } -} - -impl From for TransportError { - fn from(e: io::Error) -> Self { - Self::IO(e) - } -} - -impl TransportError { - /// Timeout error - #[inline] - pub fn timeout() -> Self { - Self::IO(io::Error::new(ErrorKind::TimedOut, "timeout")) - } - - /// Create a new backend error. - #[inline] - pub fn backend(error: E) -> Self - where - E: Into>, - { - Self::Backend(error.into()) - } -} diff --git a/sdk/src/transport/mod.rs b/sdk/src/transport/mod.rs index 778436108..da306f854 100644 --- a/sdk/src/transport/mod.rs +++ b/sdk/src/transport/mod.rs @@ -4,5 +4,4 @@ //! Nostr transports -pub mod error; pub mod websocket; diff --git a/sdk/src/transport/websocket.rs b/sdk/src/transport/websocket.rs index 3485d04ab..70b09d017 100644 --- a/sdk/src/transport/websocket.rs +++ b/sdk/src/transport/websocket.rs @@ -15,21 +15,21 @@ use futures::stream::SplitSink; use futures::{Sink, SinkExt, Stream, StreamExt, TryStreamExt}; use nostr::Url; -use super::error::TransportError; +use crate::error::Error; use crate::future::BoxedFuture; /// WebSocket transport sink #[cfg(not(target_arch = "wasm32"))] -pub type WebSocketSink = Pin + Send>>; +pub type WebSocketSink = Pin + Send>>; /// WebSocket transport sink #[cfg(target_arch = "wasm32")] -pub type WebSocketSink = Pin>>; +pub type WebSocketSink = Pin>>; /// WebSocket transport stream #[cfg(not(target_arch = "wasm32"))] -pub type WebSocketStream = Pin> + Send>>; +pub type WebSocketStream = Pin> + Send>>; /// WebSocket transport stream #[cfg(target_arch = "wasm32")] -pub type WebSocketStream = Pin>>>; +pub type WebSocketStream = Pin>>>; #[doc(hidden)] pub trait IntoWebSocketTransport { @@ -70,7 +70,7 @@ pub trait WebSocketTransport: fmt::Debug + Send + Sync { &'a self, url: &'a Url, proxy: Option, - ) -> BoxedFuture<'a, Result<(WebSocketSink, WebSocketStream), TransportError>>; + ) -> BoxedFuture<'a, Result<(WebSocketSink, WebSocketStream), Error>>; } /// Default websocket transport @@ -86,7 +86,7 @@ impl WebSocketTransport for DefaultWebsocketTransport { &'a self, url: &'a Url, proxy: Option, - ) -> BoxedFuture<'a, Result<(WebSocketSink, WebSocketStream), TransportError>> { + ) -> BoxedFuture<'a, Result<(WebSocketSink, WebSocketStream), Error>> { Box::pin(async move { let mode: ConnectionMode = match proxy { #[cfg(not(target_arch = "wasm32"))] @@ -99,7 +99,7 @@ impl WebSocketTransport for DefaultWebsocketTransport { // Connect let socket: WebSocket = WebSocket::connect(url, &mode) .await - .map_err(TransportError::backend)?; + .map_err(Error::transport)?; // Split sink and stream let (tx, rx) = socket.split(); @@ -107,8 +107,7 @@ impl WebSocketTransport for DefaultWebsocketTransport { // NOTE: don't use sink_map_err here, as it may cause panics! // Issue: https://github.com/rust-nostr/nostr/issues/984 let sink: WebSocketSink = Box::pin(TransportSink(tx)) as WebSocketSink; - let stream: WebSocketStream = - Box::pin(rx.map_err(TransportError::backend)) as WebSocketStream; + let stream: WebSocketStream = Box::pin(rx.map_err(Error::transport)) as WebSocketStream; Ok((sink, stream)) }) @@ -118,29 +117,29 @@ impl WebSocketTransport for DefaultWebsocketTransport { struct TransportSink(SplitSink); impl Sink for TransportSink { - type Error = TransportError; + type Error = Error; fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0) .poll_ready_unpin(cx) - .map_err(TransportError::backend) + .map_err(Error::transport) } fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { Pin::new(&mut self.0) .start_send_unpin(item) - .map_err(TransportError::backend) + .map_err(Error::transport) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0) .poll_flush_unpin(cx) - .map_err(TransportError::backend) + .map_err(Error::transport) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0) .poll_close_unpin(cx) - .map_err(TransportError::backend) + .map_err(Error::transport) } } diff --git a/signer/nostr-connect/src/error.rs b/signer/nostr-connect/src/error.rs index 88119dbe3..d87f62de5 100644 --- a/signer/nostr-connect/src/error.rs +++ b/signer/nostr-connect/src/error.rs @@ -7,7 +7,6 @@ use std::fmt; use nostr::PublicKey; -use nostr_sdk::client; use tokio::sync::SetError; /// Nostr Connect error @@ -15,8 +14,8 @@ use tokio::sync::SetError; pub enum Error { /// Nostr protocol error Protocol(nostr::error::Error), - /// Client - Client(client::Error), + /// Sdk + Sdk(nostr_sdk::error::Error), /// Set user public key error SetUserPublicKey(SetError), /// Invalid response from remote signer @@ -41,7 +40,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Protocol(e) => e.fmt(f), - Self::Client(e) => e.fmt(f), + Self::Sdk(e) => e.fmt(f), Self::SetUserPublicKey(e) => e.fmt(f), Self::InvalidResponse(e) => e.fmt(f), Self::Response(e) => e.fmt(f), @@ -60,9 +59,9 @@ impl From for Error { } } -impl From for Error { - fn from(e: client::Error) -> Self { - Self::Client(e) +impl From for Error { + fn from(e: nostr_sdk::error::Error) -> Self { + Self::Sdk(e) } } From 55bcf2ff381280fa32e4b4b1be05a6e885a48697 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Sat, 20 Jun 2026 11:04:37 +0200 Subject: [PATCH 08/11] gossip: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + gossip/nostr-gossip-memory/src/store.rs | 12 ++-- gossip/nostr-gossip-sqlite/src/error.rs | 51 +++++++------- gossip/nostr-gossip-sqlite/src/migration.rs | 10 +-- gossip/nostr-gossip-sqlite/src/pool.rs | 16 ++--- gossip/nostr-gossip-sqlite/src/prelude.rs | 2 +- gossip/nostr-gossip-sqlite/src/store.rs | 73 +++++++++++---------- gossip/nostr-gossip/Cargo.toml | 1 + gossip/nostr-gossip/src/error.rs | 58 ++++++++++------ gossip/nostr-gossip/src/lib.rs | 12 ++-- gossip/nostr-gossip/src/prelude.rs | 2 +- sdk/src/error.rs | 2 +- 12 files changed, 129 insertions(+), 111 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d2d68805..600af1913 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1937,6 +1937,7 @@ name = "nostr-gossip" version = "0.45.0-alpha.1" dependencies = [ "nostr", + "opaquerr", ] [[package]] diff --git a/gossip/nostr-gossip-memory/src/store.rs b/gossip/nostr-gossip-memory/src/store.rs index 367949645..0aad415e1 100644 --- a/gossip/nostr-gossip-memory/src/store.rs +++ b/gossip/nostr-gossip-memory/src/store.rs @@ -11,7 +11,7 @@ use nostr::nips::nip17; use nostr::nips::nip65::{self, RelayMetadata}; use nostr::util::BoxedFuture; use nostr::{Event, Kind, PublicKey, RelayUrl, Timestamp}; -use nostr_gossip::error::GossipError; +use nostr_gossip::error::Error; use nostr_gossip::flags::GossipFlags; use nostr_gossip::{ BestRelaySelection, GossipAllowedRelays, GossipListKind, GossipPublicKeyStatus, NostrGossip, @@ -395,7 +395,7 @@ impl NostrGossip for NostrGossipMemory { &'a self, event: &'a Event, relay_url: Option<&'a RelayUrl>, - ) -> BoxedFuture<'a, Result<(), GossipError>> { + ) -> BoxedFuture<'a, Result<(), Error>> { Box::pin(async move { self.process_event(event, relay_url).await; Ok(()) @@ -406,7 +406,7 @@ impl NostrGossip for NostrGossipMemory { &'a self, public_key: &'a PublicKey, list: GossipListKind, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { Ok(self.get_status(public_key, list).await) }) } @@ -414,7 +414,7 @@ impl NostrGossip for NostrGossipMemory { &'a self, public_key: &'a PublicKey, list: GossipListKind, - ) -> BoxedFuture<'a, Result<(), GossipError>> { + ) -> BoxedFuture<'a, Result<(), Error>> { Box::pin(async move { self._update_fetch_attempt(public_key, list).await; Ok(()) @@ -425,7 +425,7 @@ impl NostrGossip for NostrGossipMemory { &self, list: GossipListKind, limit: NonZeroUsize, - ) -> BoxedFuture<'_, Result, GossipError>> { + ) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { Ok(self.collect_outdated_public_keys(list, limit).await) }) } @@ -434,7 +434,7 @@ impl NostrGossip for NostrGossipMemory { public_key: &'a PublicKey, selection: BestRelaySelection, allowed: GossipAllowedRelays, - ) -> BoxedFuture<'a, Result, GossipError>> { + ) -> BoxedFuture<'a, Result, Error>> { Box::pin(async move { Ok(self._get_best_relays(public_key, selection, allowed).await) }) } } diff --git a/gossip/nostr-gossip-sqlite/src/error.rs b/gossip/nostr-gossip-sqlite/src/error.rs index 0250608ed..c563e0d08 100644 --- a/gossip/nostr-gossip-sqlite/src/error.rs +++ b/gossip/nostr-gossip-sqlite/src/error.rs @@ -3,14 +3,12 @@ use std::fmt; use std::num::TryFromIntError; -use tokio::sync::AcquireError; +pub use nostr_gossip::error::{Error, ErrorKind}; use tokio::task::JoinError; /// Migration error #[derive(Debug, Clone, PartialEq, Eq)] -pub enum MigrationError { - /// Migration is in a dirty state - Dirty(i64), +pub(crate) enum MigrationError { /// Database version is newer than supported one NewerVersion { /// Current database version @@ -25,7 +23,6 @@ impl std::error::Error for MigrationError {} impl fmt::Display for MigrationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Dirty(version) => write!(f, "migration {version} is partially applied"), Self::NewerVersion { current, supported } => write!( f, "database version {current} is newer than supported version {supported}" @@ -34,61 +31,57 @@ impl fmt::Display for MigrationError { } } -/// Gossip SQLite error #[derive(Debug)] -pub enum Error { - /// TryFromInt error +pub(crate) enum StoreError { TryFromInt(TryFromIntError), - /// Rusqlite error Rusqlite(rusqlite::Error), - /// Migration error Migration(MigrationError), - /// Thread error Thread(JoinError), - /// Failed to acquire semaphore - Acquire(AcquireError), } -impl std::error::Error for Error {} +impl std::error::Error for StoreError {} -impl fmt::Display for Error { +impl fmt::Display for StoreError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::TryFromInt(e) => e.fmt(f), - Self::Rusqlite(e) => e.fmt(f), - Self::Migration(e) => e.fmt(f), - Self::Thread(e) => e.fmt(f), - Self::Acquire(e) => e.fmt(f), + Self::TryFromInt(e) => write!(f, "{e}"), + Self::Rusqlite(e) => write!(f, "{e}"), + Self::Migration(e) => write!(f, "Migration error: {e}"), + Self::Thread(e) => write!(f, "{e}"), } } } -impl From for Error { - fn from(err: TryFromIntError) -> Self { - Self::TryFromInt(err) +impl From for StoreError { + fn from(e: TryFromIntError) -> Self { + Self::TryFromInt(e) } } -impl From for Error { +impl From for StoreError { fn from(e: rusqlite::Error) -> Self { Self::Rusqlite(e) } } -impl From for Error { +impl From for StoreError { fn from(e: MigrationError) -> Self { Self::Migration(e) } } -impl From for Error { +impl From for StoreError { fn from(e: JoinError) -> Self { Self::Thread(e) } } -impl From for Error { - fn from(err: AcquireError) -> Self { - Self::Acquire(err) +impl From for Error { + fn from(error: StoreError) -> Self { + match error { + StoreError::Migration(e) => Self::migration(e), + StoreError::Thread(e) => Self::new(ErrorKind::IO, e), + e => Self::storage(e), + } } } diff --git a/gossip/nostr-gossip-sqlite/src/migration.rs b/gossip/nostr-gossip-sqlite/src/migration.rs index 5477d9fc7..9ea196dc9 100644 --- a/gossip/nostr-gossip-sqlite/src/migration.rs +++ b/gossip/nostr-gossip-sqlite/src/migration.rs @@ -2,11 +2,11 @@ use std::cmp::Ordering; use rusqlite::Transaction; -use crate::error::{Error, MigrationError}; +use crate::error::{MigrationError, StoreError}; const DB_VERSION: i64 = 1; -pub(super) fn run(tx: &Transaction<'_>) -> Result<(), Error> { +pub(super) fn run(tx: &Transaction<'_>) -> Result<(), StoreError> { // Get the current version let mut curr_version: i64 = curr_db_version(tx)?; @@ -35,17 +35,17 @@ pub(super) fn run(tx: &Transaction<'_>) -> Result<(), Error> { Ok(()) } -fn curr_db_version(tx: &Transaction<'_>) -> Result { +fn curr_db_version(tx: &Transaction<'_>) -> Result { let version: i64 = tx.query_row("PRAGMA user_version", [], |row| row.get(0))?; Ok(version) } -fn set_db_version(tx: &Transaction<'_>, version: i64) -> Result<(), Error> { +fn set_db_version(tx: &Transaction<'_>, version: i64) -> Result<(), StoreError> { tx.pragma_update(None, "user_version", version)?; Ok(()) } -fn mig_init(tx: &Transaction<'_>) -> Result { +fn mig_init(tx: &Transaction<'_>) -> Result { tx.execute_batch(include_str!("../migrations/001_init.sql"))?; set_db_version(tx, 1)?; Ok(1) diff --git a/gossip/nostr-gossip-sqlite/src/pool.rs b/gossip/nostr-gossip-sqlite/src/pool.rs index a36fafa54..615e7111b 100644 --- a/gossip/nostr-gossip-sqlite/src/pool.rs +++ b/gossip/nostr-gossip-sqlite/src/pool.rs @@ -8,7 +8,7 @@ use async_utility::task; use rusqlite::{Connection, OpenFlags}; use tokio::sync::Mutex; -use crate::error::Error; +use crate::error::StoreError; #[derive(Debug, Clone)] pub(crate) struct Pool { @@ -22,19 +22,19 @@ impl Pool { } } - pub(crate) fn open_in_memory() -> Result { + pub(crate) fn open_in_memory() -> Result { let conn: Connection = Connection::open_in_memory()?; Ok(Self::new(conn)) } #[inline] #[cfg(not(target_arch = "wasm32"))] - pub(crate) async fn open_with_path(path: PathBuf) -> Result { + pub(crate) async fn open_with_path(path: PathBuf) -> Result { let conn: Connection = task::spawn_blocking(move || Connection::open(path)).await??; Ok(Self::new(conn)) } - pub(crate) async fn open_with_vfs

(path: P, vfs: &str) -> Result + pub(crate) async fn open_with_vfs

(path: P, vfs: &str) -> Result where P: AsRef, { @@ -44,9 +44,9 @@ impl Pool { } #[cfg(not(target_arch = "wasm32"))] - pub async fn interact(&self, f: F) -> Result + pub async fn interact(&self, f: F) -> Result where - F: FnOnce(&mut Connection) -> Result + Send + 'static, + F: FnOnce(&mut Connection) -> Result + Send + 'static, R: Send + 'static, { let arc: Arc> = self.conn.clone(); @@ -55,9 +55,9 @@ impl Pool { } #[cfg(target_arch = "wasm32")] - pub async fn interact(&self, f: F) -> Result + pub async fn interact(&self, f: F) -> Result where - F: FnOnce(&mut Connection) -> Result + 'static, + F: FnOnce(&mut Connection) -> Result + 'static, R: 'static, { let mut conn = self.conn.lock().await; diff --git a/gossip/nostr-gossip-sqlite/src/prelude.rs b/gossip/nostr-gossip-sqlite/src/prelude.rs index 4f72afe7b..a14a70319 100644 --- a/gossip/nostr-gossip-sqlite/src/prelude.rs +++ b/gossip/nostr-gossip-sqlite/src/prelude.rs @@ -10,5 +10,5 @@ pub use nostr::prelude::*; -pub use crate::error::*; +pub use crate::error::{Error, ErrorKind}; pub use crate::store::*; diff --git a/gossip/nostr-gossip-sqlite/src/store.rs b/gossip/nostr-gossip-sqlite/src/store.rs index d4e94e6ac..2a4c98a5f 100644 --- a/gossip/nostr-gossip-sqlite/src/store.rs +++ b/gossip/nostr-gossip-sqlite/src/store.rs @@ -10,7 +10,7 @@ use nostr::nips::nip17; use nostr::nips::nip65::{self, RelayMetadata}; use nostr::util::BoxedFuture; use nostr::{Event, Kind, PublicKey, RelayUrl, Timestamp}; -use nostr_gossip::error::GossipError; +use nostr_gossip::error::Error; use nostr_gossip::flags::GossipFlags; use nostr_gossip::{ BestRelaySelection, GossipAllowedRelays, GossipListKind, GossipPublicKeyStatus, NostrGossip, @@ -19,7 +19,7 @@ use nostr_gossip::{ use rusqlite::{OptionalExtension, Transaction, params}; use crate::constant::{READ_WRITE_FLAGS, RELAYS_QUERY_LIMIT, TTL_OUTDATED}; -use crate::error::Error; +use crate::error::StoreError; use crate::migration; use crate::model::ListRow; use crate::pool::Pool; @@ -84,7 +84,7 @@ impl NostrGossipSqlite { &self, event: &Event, relay_url: Option<&RelayUrl>, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { let event = event.clone(); let relay_url = relay_url.cloned(); @@ -125,7 +125,7 @@ impl NostrGossipSqlite { &self, public_key: &PublicKey, list: GossipListKind, - ) -> Result { + ) -> Result { let public_key = *public_key; self.pool @@ -174,7 +174,7 @@ impl NostrGossipSqlite { &self, public_key: PublicKey, list: GossipListKind, - ) -> Result<(), Error> { + ) -> Result<(), StoreError> { self.pool .interact(move |conn| { let tx = conn.transaction()?; @@ -202,7 +202,7 @@ impl NostrGossipSqlite { &self, list: GossipListKind, limit: NonZeroUsize, - ) -> Result, Error> { + ) -> Result, StoreError> { let now: i64 = Timestamp::now().as_secs() as i64; let threshold: i64 = now.saturating_sub(TTL_OUTDATED.as_secs() as i64); self.pool @@ -255,7 +255,7 @@ impl NostrGossipSqlite { public_key: PublicKey, selection: BestRelaySelection, allowed: GossipAllowedRelays, - ) -> Result, Error> { + ) -> Result, StoreError> { let mut relays: HashSet = HashSet::new(); match selection { @@ -340,7 +340,7 @@ impl NostrGossipSqlite { flag: GossipFlags, allowed: GossipAllowedRelays, limit: u8, - ) -> Result, Error> { + ) -> Result, StoreError> { self.pool .interact(move |conn| { let query = r#" @@ -387,7 +387,7 @@ impl NostrGossipSqlite { } } -fn get_or_save_public_key(tx: &Transaction<'_>, public_key: &PublicKey) -> Result { +fn get_or_save_public_key(tx: &Transaction<'_>, public_key: &PublicKey) -> Result { match get_id_by_public_key(tx, public_key)? { Some(id) => Ok(id), None => save_public_key(tx, public_key), @@ -397,7 +397,7 @@ fn get_or_save_public_key(tx: &Transaction<'_>, public_key: &PublicKey) -> Resul fn get_id_by_public_key( tx: &Transaction<'_>, public_key: &PublicKey, -) -> Result, Error> { +) -> Result, StoreError> { let pk_id: Option = tx .query_row( "SELECT id FROM public_keys WHERE public_key = ?1", @@ -408,7 +408,7 @@ fn get_id_by_public_key( Ok(pk_id) } -fn save_public_key(tx: &Transaction<'_>, public_key: &PublicKey) -> Result { +fn save_public_key(tx: &Transaction<'_>, public_key: &PublicKey) -> Result { tx.execute( "INSERT INTO public_keys (public_key) VALUES (?1) ON CONFLICT (public_key) DO NOTHING", params![public_key.as_bytes().as_slice()], @@ -421,14 +421,17 @@ fn save_public_key(tx: &Transaction<'_>, public_key: &PublicKey) -> Result, relay_url: &RelayUrl) -> Result { +fn get_or_save_relay_url(tx: &Transaction<'_>, relay_url: &RelayUrl) -> Result { match get_id_by_relay_url(tx, relay_url)? { Some(id) => Ok(id), None => save_relay_url(tx, relay_url), } } -fn get_id_by_relay_url(tx: &Transaction<'_>, relay_url: &RelayUrl) -> Result, Error> { +fn get_id_by_relay_url( + tx: &Transaction<'_>, + relay_url: &RelayUrl, +) -> Result, StoreError> { let relay_id: Option = tx .query_row( "SELECT id FROM relays WHERE url = ?1", @@ -439,7 +442,7 @@ fn get_id_by_relay_url(tx: &Transaction<'_>, relay_url: &RelayUrl) -> Result, relay_url: &RelayUrl) -> Result { +fn save_relay_url(tx: &Transaction<'_>, relay_url: &RelayUrl) -> Result { tx.execute( "INSERT INTO relays (url) VALUES (?1) ON CONFLICT (url) DO NOTHING", params![relay_url.as_str_without_trailing_slash()], @@ -456,7 +459,7 @@ fn remove_flag_from_user_relays( tx: &Transaction<'_>, public_key_id: i32, flags_to_remove: GossipFlags, -) -> Result<(), Error> { +) -> Result<(), StoreError> { tx.execute( "UPDATE relays_per_user SET bitflags = (bitflags & ~?1) WHERE public_key_id = ?2", params![flags_to_remove.as_u32(), public_key_id], @@ -470,7 +473,7 @@ fn update_relay_per_user( public_key_id: i32, relay_url: &RelayUrl, flags: GossipFlags, -) -> Result<(), Error> { +) -> Result<(), StoreError> { let relay_id: i32 = get_or_save_relay_url(tx, relay_url)?; let now: u64 = Timestamp::now().as_secs(); @@ -491,7 +494,11 @@ fn update_relay_per_user( Ok(()) } -fn update_nip65_relays(tx: &Transaction<'_>, public_key_id: i32, iter: I) -> Result<(), Error> +fn update_nip65_relays( + tx: &Transaction<'_>, + public_key_id: i32, + iter: I, +) -> Result<(), StoreError> where I: IntoIterator)>, { @@ -526,7 +533,11 @@ where Ok(()) } -fn update_nip17_relays(tx: &Transaction<'_>, public_key_id: i32, iter: I) -> Result<(), Error> +fn update_nip17_relays( + tx: &Transaction<'_>, + public_key_id: i32, + iter: I, +) -> Result<(), StoreError> where I: IntoIterator, { @@ -556,7 +567,7 @@ where Ok(()) } -fn update_hints(tx: &Transaction<'_>, event: &Event) -> Result<(), Error> { +fn update_hints(tx: &Transaction<'_>, event: &Event) -> Result<(), StoreError> { for tag in event.tags.iter().filter_map(|t| { if t.kind() != "p" { return None; @@ -582,11 +593,11 @@ impl NostrGossip for NostrGossipSqlite { &'a self, event: &'a Event, relay_url: Option<&'a RelayUrl>, - ) -> BoxedFuture<'a, Result<(), GossipError>> { + ) -> BoxedFuture<'a, Result<(), Error>> { Box::pin(async move { self.process_event(event, relay_url) .await - .map_err(GossipError::backend) + .map_err(Into::into) }) } @@ -594,23 +605,19 @@ impl NostrGossip for NostrGossipSqlite { &'a self, public_key: &'a PublicKey, list: GossipListKind, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - self.get_status(public_key, list) - .await - .map_err(GossipError::backend) - }) + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { self.get_status(public_key, list).await.map_err(Into::into) }) } fn update_fetch_attempt<'a>( &'a self, public_key: &'a PublicKey, list: GossipListKind, - ) -> BoxedFuture<'a, Result<(), GossipError>> { + ) -> BoxedFuture<'a, Result<(), Error>> { Box::pin(async move { self._update_fetch_attempt(*public_key, list) .await - .map_err(GossipError::backend) + .map_err(Into::into) }) } @@ -618,11 +625,11 @@ impl NostrGossip for NostrGossipSqlite { &self, list: GossipListKind, limit: NonZeroUsize, - ) -> BoxedFuture<'_, Result, GossipError>> { + ) -> BoxedFuture<'_, Result, Error>> { Box::pin(async move { self.get_outdated_public_keys(list, limit) .await - .map_err(GossipError::backend) + .map_err(Into::into) }) } @@ -631,11 +638,11 @@ impl NostrGossip for NostrGossipSqlite { public_key: &'a PublicKey, selection: BestRelaySelection, allowed: GossipAllowedRelays, - ) -> BoxedFuture<'a, Result, GossipError>> { + ) -> BoxedFuture<'a, Result, Error>> { Box::pin(async move { self._get_best_relays(*public_key, selection, allowed) .await - .map_err(GossipError::backend) + .map_err(Into::into) }) } } diff --git a/gossip/nostr-gossip/Cargo.toml b/gossip/nostr-gossip/Cargo.toml index 0e450497d..abe964900 100644 --- a/gossip/nostr-gossip/Cargo.toml +++ b/gossip/nostr-gossip/Cargo.toml @@ -13,3 +13,4 @@ keywords = ["nostr", "gossip"] [dependencies] nostr = { workspace = true, features = ["std"] } +opaquerr.workspace = true diff --git a/gossip/nostr-gossip/src/error.rs b/gossip/nostr-gossip/src/error.rs index 8989221f9..906f55660 100644 --- a/gossip/nostr-gossip/src/error.rs +++ b/gossip/nostr-gossip/src/error.rs @@ -4,34 +4,50 @@ //! Nostr Gossip error -use std::fmt; - -/// Gossip Error -#[derive(Debug)] -pub enum GossipError { - /// An error happened in the underlying database backend. - Backend(Box), +opaquerr::define_kind! { + /// Nostr gossip error kind. + pub ErrorKind { + /// I/O error. + IO => "I/O error", + /// Storage error + Storage => "storage error", + /// Database migration error. + Migration => "migration error", + /// The operation is known but not supported. + Unsupported => "the operation is known but not supported", + /// Anything not covered by the stable categories above. + Other => "other error", + } } -impl std::error::Error for GossipError {} +opaquerr::define_error! { + /// Nostr gossip error. + pub Error(ErrorKind) -impl fmt::Display for GossipError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Backend(e) => e.fmt(f), - } + from { + std::io::Error => ErrorKind::IO, } } -impl GossipError { - /// Create a new backend error - /// - /// Shorthand for `Error::Backend(Box::new(error))`. - #[inline] - pub fn backend(error: E) -> Self +impl Error { + /// Storage error + pub fn storage(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Storage, error) + } + + /// Migration error + pub fn migration(error: E) -> Self where - E: std::error::Error + Send + Sync + 'static, + E: Into>, { - Self::Backend(Box::new(error)) + Self::new(ErrorKind::Migration, error) + } + + /// unsupported feature + pub const fn unsupported(message: &'static str) -> Self { + Self::with_static_message(ErrorKind::Unsupported, message) } } diff --git a/gossip/nostr-gossip/src/lib.rs b/gossip/nostr-gossip/src/lib.rs index adc1369c2..c3f0b3b47 100644 --- a/gossip/nostr-gossip/src/lib.rs +++ b/gossip/nostr-gossip/src/lib.rs @@ -22,7 +22,7 @@ pub mod error; pub mod flags; pub mod prelude; -use self::error::GossipError; +use self::error::Error; /// Gossip list kind #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -203,28 +203,28 @@ pub trait NostrGossip: Any + Debug + Send + Sync { &'a self, event: &'a Event, relay_url: Option<&'a RelayUrl>, - ) -> BoxedFuture<'a, Result<(), GossipError>>; + ) -> BoxedFuture<'a, Result<(), Error>>; /// Check the [`PublicKey`] status fn status<'a>( &'a self, public_key: &'a PublicKey, list: GossipListKind, - ) -> BoxedFuture<'a, Result>; + ) -> BoxedFuture<'a, Result>; /// Update the last check timestamp for an [`PublicKey`]. fn update_fetch_attempt<'a>( &'a self, public_key: &'a PublicKey, list: GossipListKind, - ) -> BoxedFuture<'a, Result<(), GossipError>>; + ) -> BoxedFuture<'a, Result<(), Error>>; /// Get up to `limit` outdated public keys for the specified list kind. fn outdated_public_keys( &self, list: GossipListKind, limit: NonZeroUsize, - ) -> BoxedFuture<'_, Result, GossipError>>; + ) -> BoxedFuture<'_, Result, Error>>; /// Get the best relays for a [`PublicKey`]. fn get_best_relays<'a>( @@ -232,7 +232,7 @@ pub trait NostrGossip: Any + Debug + Send + Sync { public_key: &'a PublicKey, selection: BestRelaySelection, allowed: GossipAllowedRelays, - ) -> BoxedFuture<'a, Result, GossipError>>; + ) -> BoxedFuture<'a, Result, Error>>; } #[doc(hidden)] diff --git a/gossip/nostr-gossip/src/prelude.rs b/gossip/nostr-gossip/src/prelude.rs index 6f00f0bf4..edcc703a3 100644 --- a/gossip/nostr-gossip/src/prelude.rs +++ b/gossip/nostr-gossip/src/prelude.rs @@ -10,6 +10,6 @@ pub use nostr::prelude::*; -pub use crate::error::*; +pub use crate::error::{Error, ErrorKind}; pub use crate::flags::*; pub use crate::*; diff --git a/sdk/src/error.rs b/sdk/src/error.rs index 4cac3807e..b3f7030ef 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -47,7 +47,7 @@ opaquerr::define_error! { from { nostr::error::Error => ErrorKind::Protocol, nostr_database::error::Error => ErrorKind::Database, - nostr_gossip::error::GossipError => ErrorKind::Gossip, + nostr_gossip::error::Error => ErrorKind::Gossip, serde_json::Error => ErrorKind::Protocol, faster_hex::Error => ErrorKind::Protocol, negentropy::Error => ErrorKind::Protocol, From e6f7de10975cade4c656e7f017213c2dff5aa010 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Sat, 20 Jun 2026 11:29:00 +0200 Subject: [PATCH 09/11] connect: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + signer/nostr-connect/Cargo.toml | 1 + signer/nostr-connect/src/client.rs | 20 ++--- signer/nostr-connect/src/error.rs | 118 ++++++++++++++++------------ signer/nostr-connect/src/prelude.rs | 3 +- signer/nostr-connect/src/signer.rs | 4 +- 6 files changed, 82 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 600af1913..6043d2772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,6 +1907,7 @@ dependencies = [ "nostr", "nostr-relay-builder", "nostr-sdk", + "opaquerr", "tokio", "tracing", "tracing-subscriber", diff --git a/signer/nostr-connect/Cargo.toml b/signer/nostr-connect/Cargo.toml index 7670b3934..373117555 100644 --- a/signer/nostr-connect/Cargo.toml +++ b/signer/nostr-connect/Cargo.toml @@ -29,6 +29,7 @@ async-utility.workspace = true futures-core = "0.3" nostr = { workspace = true, features = ["std", "rand", "nip04", "nip44", "nip46"] } nostr-sdk.workspace = true +opaquerr.workspace = true tokio = { workspace = true, features = ["sync"] } tracing.workspace = true diff --git a/signer/nostr-connect/src/client.rs b/signer/nostr-connect/src/client.rs index 8bc828d83..959ea44be 100644 --- a/signer/nostr-connect/src/client.rs +++ b/signer/nostr-connect/src/client.rs @@ -43,7 +43,7 @@ impl NostrConnect { // Check app keys if let NostrConnectUri::Client { public_key, .. } = &uri { if public_key != &client_keys.public_key { - return Err(Error::PublicKeyNotMatchAppKeys); + return Err(Error::public_key_not_match_app_keys()); } } @@ -73,16 +73,16 @@ impl NostrConnect { /// struct MyAuthUrlHandler; /// /// impl AuthUrlHandler for MyAuthUrlHandler { - /// fn on_auth_url(&self, auth_url: Url) -> BoxedFuture<'_, Result<()>> { + /// fn on_auth_url(&self, auth_url: Url) -> BoxedFuture<'_, Result<(), Error>> { /// Box::pin(async move { - /// webbrowser::open(auth_url.as_str())?; + /// webbrowser::open(auth_url.as_str()).map_err(Error::other)?; /// Ok(()) /// }) /// } /// } /// /// #[tokio::main] - /// async fn main() -> Result<()> { + /// async fn main() -> Result<(), Box> { /// let uri = NostrConnectUri::parse("bunker://79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3?relay=wss://relay.nsec.app")?; /// let client_keys = Keys::generate(); /// let timeout = Duration::from_secs(60); @@ -264,7 +264,7 @@ impl NostrConnect { } } else { if let Some(error) = response.error { - return Err(Error::Response(error)); + return Err(Error::response(error)); } if let Some(result) = response.result { @@ -278,10 +278,10 @@ impl NostrConnect { } } - Err(Error::Timeout) + Err(Error::timeout()) }) .await - .ok_or(Error::Timeout)? + .ok_or_else(Error::timeout)? } /// Connect bunker @@ -302,7 +302,7 @@ impl NostrConnect { return Ok(()); } - Err(Error::InvalidResponse(res.to_string())) + Err(Error::invalid_response(res.to_string())) } async fn _get_public_key(&self) -> Result<&PublicKey, Error> { @@ -430,10 +430,10 @@ async fn get_remote_signer_public_key( } } - Err(Error::SignerPublicKeyNotFound) + Err(Error::signer_public_key_not_found()) }) .await - .ok_or(Error::Timeout)? + .ok_or_else(Error::timeout)? } fn is_valid_connect_response(response: &ResponseResult, expected_secret: Option<&str>) -> bool { diff --git a/signer/nostr-connect/src/error.rs b/signer/nostr-connect/src/error.rs index d87f62de5..d1f5c4fa1 100644 --- a/signer/nostr-connect/src/error.rs +++ b/signer/nostr-connect/src/error.rs @@ -4,69 +4,85 @@ //! Nostr Connect error -use std::fmt; - use nostr::PublicKey; use tokio::sync::SetError; -/// Nostr Connect error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Sdk - Sdk(nostr_sdk::error::Error), - /// Set user public key error - SetUserPublicKey(SetError), - /// Invalid response from remote signer - InvalidResponse(String), - /// NIP46 response error - Response(String), - /// Signer public key not found - SignerPublicKeyNotFound, - /// Request timeout - Timeout, - /// Unexpected URI - UnexpectedUri, - /// Public key not match - PublicKeyNotMatchAppKeys, - /// Nostr connect client without a secret - NoClientSecret, +opaquerr::define_kind! { + /// Nostr Connect error kind. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// SDK error. + Sdk => "SDK error", + /// Input is well-formed, but violates a signer invariant. + Invalid => "input violates a signer invariant", + /// The operation was rejected by the remote signer. + Rejected => "operation rejected", + /// The operation timed out. + Timeout => "timeout", + /// Required data was not found. + NotFound => "not found", + /// The operation cannot be completed in the current state. + State => "invalid state", + /// Anything not covered by the stable categories above. + Other => "other error", + } } -impl std::error::Error for Error {} +opaquerr::define_error! { + /// Nostr Connect error. + pub Error(ErrorKind) -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => e.fmt(f), - Self::Sdk(e) => e.fmt(f), - Self::SetUserPublicKey(e) => e.fmt(f), - Self::InvalidResponse(e) => e.fmt(f), - Self::Response(e) => e.fmt(f), - Self::SignerPublicKeyNotFound => f.write_str("signer public key not found"), - Self::Timeout => f.write_str("timeout"), - Self::UnexpectedUri => f.write_str("unexpected URI"), - Self::PublicKeyNotMatchAppKeys => f.write_str("public key not match app keys"), - Self::NoClientSecret => f.write_str("missing client secret"), - } + from { + nostr::error::Error => ErrorKind::Protocol, + nostr_sdk::error::Error => ErrorKind::Sdk, + SetError => ErrorKind::State, } } -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) +impl Error { + /// Creates a new Nostr error from a known kind of error as well as an arbitrary error payload. + /// + /// It is a shortcut for [`Error::new`] with [`ErrorKind::Other`]. + #[inline] + pub fn other(error: E) -> Self + where + E: Into>, + { + Self::new(ErrorKind::Other, error) } -} -impl From for Error { - fn from(e: nostr_sdk::error::Error) -> Self { - Self::Sdk(e) + pub(crate) fn invalid_response(response: S) -> Self + where + S: Into, + { + Self::new(ErrorKind::Invalid, response.into()) + } + + pub(crate) fn response(message: S) -> Self + where + S: Into, + { + Self::new(ErrorKind::Rejected, message.into()) + } + + pub(crate) fn signer_public_key_not_found() -> Self { + Self::with_static_message(ErrorKind::NotFound, "signer public key not found") + } + + pub(crate) fn timeout() -> Self { + Self::simple(ErrorKind::Timeout) + } + + pub(crate) fn unexpected_uri() -> Self { + Self::with_static_message(ErrorKind::Invalid, "unexpected URI") + } + + pub(crate) fn public_key_not_match_app_keys() -> Self { + Self::with_static_message(ErrorKind::Invalid, "public key not match app keys") } -} -impl From> for Error { - fn from(e: SetError) -> Self { - Self::SetUserPublicKey(e) + pub(crate) fn no_client_secret() -> Self { + Self::with_static_message(ErrorKind::State, "missing client secret") } } diff --git a/signer/nostr-connect/src/prelude.rs b/signer/nostr-connect/src/prelude.rs index 8e8b2a282..9d8041e5a 100644 --- a/signer/nostr-connect/src/prelude.rs +++ b/signer/nostr-connect/src/prelude.rs @@ -11,6 +11,5 @@ pub use nostr::prelude::*; pub use crate::client::*; -#[allow(unused_imports)] -pub use crate::error::*; +pub use crate::error::{Error, ErrorKind}; pub use crate::signer::*; diff --git a/signer/nostr-connect/src/signer.rs b/signer/nostr-connect/src/signer.rs index 39cc62ff3..ec95acaec 100644 --- a/signer/nostr-connect/src/signer.rs +++ b/signer/nostr-connect/src/signer.rs @@ -94,7 +94,7 @@ impl NostrConnectRemoteSigner { signer.nostr_connect_client_public_key = Some(public_key); Ok(signer) } - NostrConnectUri::Bunker { .. } => Err(Error::UnexpectedUri), + NostrConnectUri::Bunker { .. } => Err(Error::unexpected_uri()), } } @@ -114,7 +114,7 @@ impl NostrConnectRemoteSigner { async fn send_connect_response(&self, public_key: PublicKey) -> Result<(), Error> { let Some(secret) = self.secret.clone() else { - return Err(Error::NoClientSecret); + return Err(Error::no_client_secret()); }; // TODO: Fix the request id, should we? From 3b11efbf7d3beee474067449e302f3e34462df79 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Sat, 20 Jun 2026 11:29:52 +0200 Subject: [PATCH 10/11] browser-signer-proxy: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + signer/nostr-browser-signer-proxy/Cargo.toml | 1 + .../nostr-browser-signer-proxy/src/error.rs | 92 ++++++++----------- signer/nostr-browser-signer-proxy/src/lib.rs | 6 +- .../nostr-browser-signer-proxy/src/prelude.rs | 3 +- 5 files changed, 42 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6043d2772..4ffe71427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1889,6 +1889,7 @@ dependencies = [ "hyper", "hyper-util", "nostr", + "opaquerr", "serde", "serde_json", "tokio", diff --git a/signer/nostr-browser-signer-proxy/Cargo.toml b/signer/nostr-browser-signer-proxy/Cargo.toml index 3b614d191..c65d40d08 100644 --- a/signer/nostr-browser-signer-proxy/Cargo.toml +++ b/signer/nostr-browser-signer-proxy/Cargo.toml @@ -18,6 +18,7 @@ http-body-util = "0.1" hyper = { version = "1.9", features = ["server", "http1"] } hyper-util = { version = "0.1", features = ["tokio"] } nostr = { workspace = true, features = ["std"] } +opaquerr.workspace = true serde = { workspace = true, features = ["std", "derive"] } serde_json = { workspace = true, features = ["std"] } tokio = { workspace = true, features = ["macros", "net", "rt", "rt-multi-thread", "sync", "time"] } diff --git a/signer/nostr-browser-signer-proxy/src/error.rs b/signer/nostr-browser-signer-proxy/src/error.rs index ec8988c94..2d0ff9eac 100644 --- a/signer/nostr-browser-signer-proxy/src/error.rs +++ b/signer/nostr-browser-signer-proxy/src/error.rs @@ -4,75 +4,55 @@ //! Error -use std::{fmt, io}; - use hyper::http; use tokio::sync::oneshot::error::RecvError; -/// Error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// I/O error - Io(io::Error), - /// HTTP error - Http(http::Error), - /// Json error - Json(serde_json::Error), - /// Oneshot channel receive error - OneShotRecv(RecvError), - /// Generic error - Generic(String), - /// Timeout - Timeout, - /// The server is shutdown - Shutdown, -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => write!(f, "{e}"), - Self::Io(e) => write!(f, "{e}"), - Self::Http(e) => write!(f, "{e}"), - Self::Json(e) => write!(f, "{e}"), - Self::OneShotRecv(e) => write!(f, "{e}"), - Self::Generic(e) => write!(f, "{e}"), - Self::Timeout => write!(f, "timeout"), - Self::Shutdown => write!(f, "server is shutdown"), - } +opaquerr::define_kind! { + /// Nostr browser signer proxy error kind. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// I/O error. + IO => "I/O error", + /// HTTP error. + Http => "HTTP error", + /// JSON error. + Json => "JSON error", + /// The operation timed out. + Timeout => "timeout", + /// The operation cannot be completed in the current state. + State => "invalid state", + /// Anything not covered by the stable categories above. + Other => "other error", } } -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} +opaquerr::define_error! { + /// Nostr browser signer proxy error. + pub Error(ErrorKind) -impl From for Error { - fn from(e: io::Error) -> Self { - Self::Io(e) + from { + nostr::error::Error => ErrorKind::Protocol, + std::io::Error => ErrorKind::IO, + http::Error => ErrorKind::Http, + serde_json::Error => ErrorKind::Json, + RecvError => ErrorKind::Other, } } -impl From for Error { - fn from(e: http::Error) -> Self { - Self::Http(e) +impl Error { + pub(crate) fn generic(message: S) -> Self + where + S: Into, + { + Self::new(ErrorKind::Other, message.into()) } -} -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e) + pub(crate) fn timeout() -> Self { + Self::simple(ErrorKind::Timeout) } -} -impl From for Error { - fn from(e: RecvError) -> Self { - Self::OneShotRecv(e) + pub(crate) fn shutdown() -> Self { + Self::with_static_message(ErrorKind::State, "server is shutdown") } } diff --git a/signer/nostr-browser-signer-proxy/src/lib.rs b/signer/nostr-browser-signer-proxy/src/lib.rs index 22c9cc2fb..1c29249ed 100644 --- a/signer/nostr-browser-signer-proxy/src/lib.rs +++ b/signer/nostr-browser-signer-proxy/src/lib.rs @@ -299,7 +299,7 @@ impl BrowserSignerProxy { pub async fn start(&self) -> Result<(), Error> { // Ensure is not shutdown if self.inner.is_shutdown() { - return Err(Error::Shutdown); + return Err(Error::shutdown()); } // Mark the proxy as started and check if was already started @@ -407,10 +407,10 @@ impl BrowserSignerProxy { // Wait for response match time::timeout(self.inner.options.timeout, rx) .await - .map_err(|_| Error::Timeout)?? + .map_err(|_| Error::timeout())?? { Ok(res) => Ok(serde_json::from_value(res)?), - Err(error) => Err(Error::Generic(error)), + Err(error) => Err(Error::generic(error)), } } diff --git a/signer/nostr-browser-signer-proxy/src/prelude.rs b/signer/nostr-browser-signer-proxy/src/prelude.rs index 560eeb9cb..94f74237e 100644 --- a/signer/nostr-browser-signer-proxy/src/prelude.rs +++ b/signer/nostr-browser-signer-proxy/src/prelude.rs @@ -10,6 +10,5 @@ pub use nostr::prelude::*; -#[allow(unused_imports)] -pub use crate::error::*; +pub use crate::error::{Error, ErrorKind}; pub use crate::*; From 3c259832103e91309667fbbcbe9d95dfcd9bf5da Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Sat, 20 Jun 2026 11:39:28 +0200 Subject: [PATCH 11/11] browser-signer: refactor error Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + signer/nostr-browser-signer/Cargo.toml | 1 + signer/nostr-browser-signer/src/error.rs | 54 ++++++++++ signer/nostr-browser-signer/src/lib.rs | 118 ++++----------------- signer/nostr-browser-signer/src/prelude.rs | 14 +++ 5 files changed, 91 insertions(+), 97 deletions(-) create mode 100644 signer/nostr-browser-signer/src/error.rs create mode 100644 signer/nostr-browser-signer/src/prelude.rs diff --git a/Cargo.lock b/Cargo.lock index 4ffe71427..e8ef1e3ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1874,6 +1874,7 @@ version = "0.45.0-alpha.1" dependencies = [ "js-sys", "nostr", + "opaquerr", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/signer/nostr-browser-signer/Cargo.toml b/signer/nostr-browser-signer/Cargo.toml index 8420a9b3a..d32c5f3de 100644 --- a/signer/nostr-browser-signer/Cargo.toml +++ b/signer/nostr-browser-signer/Cargo.toml @@ -20,6 +20,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] js-sys = { version = "0.3", default-features = false, features = ["std"] } nostr = { workspace = true, default-features = false, features = ["std"] } +opaquerr.workspace = true wasm-bindgen = { version = "0.2", default-features = false, features = ["std"] } wasm-bindgen-futures = { version = "0.4", default-features = false } web-sys = { version = "0.3", default-features = false, features = ["std", "Window"] } diff --git a/signer/nostr-browser-signer/src/error.rs b/signer/nostr-browser-signer/src/error.rs new file mode 100644 index 000000000..5363f717e --- /dev/null +++ b/signer/nostr-browser-signer/src/error.rs @@ -0,0 +1,54 @@ +//! Error + +use wasm_bindgen::JsValue; + +opaquerr::define_kind! { + /// NIP-07 error kind. + pub ErrorKind { + /// Nostr protocol error. + Protocol => "nostr protocol error", + /// Browser/Extension-related error. + Extension => "browser extension error", + /// Anything not covered by the stable categories above. + Other => "other error", + } +} + +opaquerr::define_error! { + /// NIP-07 error. + pub Error(ErrorKind) + + from { + nostr::error::Error => ErrorKind::Protocol, + } +} + +impl From for Error { + fn from(e: JsValue) -> Self { + Self::new(ErrorKind::Extension, format!("{e:?}")) + } +} + +impl Error { + pub(super) fn no_global_window_object() -> Self { + Self::with_static_message(ErrorKind::Extension, "no global `window` object") + } + + pub(super) fn namespace_not_found(namespace: &str) -> Self { + Self::new( + ErrorKind::Extension, + format!("namespace `{namespace}` not found"), + ) + } + + pub(super) fn object_key_not_found(key: &str) -> Self { + Self::new( + ErrorKind::Extension, + format!("key `{key}` not found in object"), + ) + } + + pub(super) fn type_mismatch() -> Self { + Self::new(ErrorKind::Extension, "type mismatch") + } +} diff --git a/signer/nostr-browser-signer/src/lib.rs b/signer/nostr-browser-signer/src/lib.rs index e359901be..b0a91fc49 100644 --- a/signer/nostr-browser-signer/src/lib.rs +++ b/signer/nostr-browser-signer/src/lib.rs @@ -15,7 +15,6 @@ // Crate available only for WASM #![cfg(target_family = "wasm")] -use std::fmt; use std::str::FromStr; use js_sys::{Array, Function, JsString, Object, Promise, Reflect}; @@ -25,6 +24,11 @@ use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use web_sys::Window; +pub mod error; +pub mod prelude; + +use self::error::Error; + const GET_PUBLIC_KEY: &str = "getPublicKey"; const SIGN_EVENT: &str = "signEvent"; const NIP04: &str = "nip04"; @@ -38,79 +42,6 @@ enum CallFunc<'a> { Call2(&'a JsValue, &'a JsValue), } -/// NIP07 browser/extension error -#[derive(Debug)] -pub enum ExtensionError { - /// Generic WASM error - Wasm(String), - /// Impossible to get window - NoGlobalWindowObject, - /// Namespace not found - NamespaceNotFound(String), - /// Object key not found - ObjectKeyNotFound(String), - /// Invalid type - TypeMismatch, -} - -impl std::error::Error for ExtensionError {} - -impl fmt::Display for ExtensionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Wasm(e) => write!(f, "{e}"), - Self::NoGlobalWindowObject => write!(f, "No global `window` object"), - Self::NamespaceNotFound(n) => write!(f, "`{n}` namespace not found"), - Self::ObjectKeyNotFound(n) => write!(f, "Key `{n}` not found in object"), - Self::TypeMismatch => write!(f, "Type mismatch"), - } - } -} - -impl From for ExtensionError { - fn from(e: JsValue) -> Self { - Self::Wasm(format!("{e:?}")) - } -} - -/// NIP-07 error -#[derive(Debug)] -pub enum Error { - /// Nostr protocol error - Protocol(nostr::error::Error), - /// Browser/Extension-related errors - Extension(ExtensionError), -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Protocol(e) => write!(f, "{e}"), - Self::Extension(e) => write!(f, "{e}"), - } - } -} - -impl From for Error { - fn from(e: nostr::error::Error) -> Self { - Self::Protocol(e) - } -} - -impl From for Error { - fn from(e: ExtensionError) -> Self { - Self::Extension(e) - } -} - -impl From for Error { - fn from(e: JsValue) -> Self { - Self::Extension(ExtensionError::from(e)) - } -} - /// Signer for interaction with browser extensions (ex. Alby) /// /// Browser extensions: @@ -128,44 +59,37 @@ unsafe impl Sync for BrowserSigner {} impl BrowserSigner { /// Compose new NIP07 Signer - pub fn new() -> Result { - let window: Window = web_sys::window().ok_or(ExtensionError::NoGlobalWindowObject)?; + pub fn new() -> Result { + let window: Window = web_sys::window().ok_or_else(Error::no_global_window_object)?; let namespace: JsValue = Reflect::get(&window, &JsValue::from_str("nostr")) - .map_err(|_| ExtensionError::NamespaceNotFound(String::from("nostr")))?; + .map_err(|_| Error::namespace_not_found("nostr"))?; let nostr_obj: Object = namespace .dyn_into() - .map_err(|_| ExtensionError::NamespaceNotFound(String::from("nostr")))?; + .map_err(|_| Error::namespace_not_found("nostr"))?; Ok(Self { nostr_obj }) } - fn get_func(&self, obj: &Object, name: &str) -> Result { + fn get_func(&self, obj: &Object, name: &str) -> Result { let val: JsValue = Reflect::get(obj, &JsValue::from_str(name)) - .map_err(|_| ExtensionError::NamespaceNotFound(name.to_string()))?; - val.dyn_into() - .map_err(|_| ExtensionError::NamespaceNotFound(name.to_string())) + .map_err(|_| Error::namespace_not_found(name))?; + val.dyn_into().map_err(|_| Error::namespace_not_found(name)) } - fn get_sub_obj(&self, super_obj: &Object, name: &str) -> Result { + fn get_sub_obj(&self, super_obj: &Object, name: &str) -> Result { let namespace: JsValue = Reflect::get(super_obj, &JsValue::from_str(name)) - .map_err(|_| ExtensionError::NamespaceNotFound(String::from(name)))?; + .map_err(|_| Error::namespace_not_found(name))?; namespace .dyn_into() - .map_err(|_| ExtensionError::NamespaceNotFound(String::from(name))) + .map_err(|_| Error::namespace_not_found(name)) } /// Get value from object key #[inline] - fn get_value_by_key(&self, obj: &Object, key: &str) -> Result { - Reflect::get(obj, &JsValue::from_str(key)) - .map_err(|_| ExtensionError::ObjectKeyNotFound(key.to_string())) + fn get_value_by_key(&self, obj: &Object, key: &str) -> Result { + Reflect::get(obj, &JsValue::from_str(key)).map_err(|_| Error::object_key_not_found(key)) } - async fn call_func( - &self, - obj: &Object, - name: &str, - args: CallFunc<'_>, - ) -> Result + async fn call_func(&self, obj: &Object, name: &str, args: CallFunc<'_>) -> Result where T: JsCast, { @@ -178,7 +102,7 @@ impl BrowserSigner { let promise: Promise = Promise::resolve(&temp); let result: JsValue = JsFuture::from(promise).await?; - result.dyn_into().map_err(|_| ExtensionError::TypeMismatch) + result.dyn_into().map_err(|_| Error::type_mismatch()) } /// Get Public Key @@ -242,9 +166,9 @@ impl BrowserSigner { let sig: String = self .get_value_by_key(&event_obj, "sig")? .as_string() - .ok_or(ExtensionError::TypeMismatch)?; + .ok_or_else(Error::type_mismatch)?; let sig: Signature = Signature::from_str(&sig).map_err(|e| { - Error::Protocol(nostr::error::Error::new( + Error::from(nostr::error::Error::new( nostr::error::ErrorKind::Malformed, e, )) diff --git a/signer/nostr-browser-signer/src/prelude.rs b/signer/nostr-browser-signer/src/prelude.rs new file mode 100644 index 000000000..94f74237e --- /dev/null +++ b/signer/nostr-browser-signer/src/prelude.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license + +//! Prelude + +#![allow(unknown_lints)] +#![allow(ambiguous_glob_reexports)] +#![doc(hidden)] + +pub use nostr::prelude::*; + +pub use crate::error::{Error, ErrorKind}; +pub use crate::*;