diff --git a/crates/nostr/README.md b/crates/nostr/README.md index 4647e5e0f..28ec99766 100644 --- a/crates/nostr/README.md +++ b/crates/nostr/README.md @@ -42,14 +42,14 @@ fn main() -> Result<()> { .lud16("pay@yukikishimoto.com") .custom_field("custom_field", "my value"); - let event: Event = EventBuilder::metadata(&metadata).finalize(&keys)?; + let event: Event = metadata.finalize(&keys)?; // New text note let event: Event = EventBuilder::text_note("Hello from rust-nostr").finalize(&keys)?; // New POW text note let difficulty: NonZeroU8 = NonZeroU8::new(16).unwrap(); - let unsigned: UnsignedEvent = EventBuilder::text_note("POW text note from rust-nostr").finalize_unsigned(keys.public_key); + let unsigned: UnsignedEvent = EventBuilder::text_note("POW text note from rust-nostr").finalize_unsigned(keys.public_key)?; let unsigned: UnsignedEvent = unsigned.mine(&SingleThreadPow, difficulty)?; let event: Event = unsigned.finalize(&keys)?; diff --git a/crates/nostr/examples/nip09.rs b/crates/nostr/examples/nip09.rs index 2772157e8..1d1ac6224 100644 --- a/crates/nostr/examples/nip09.rs +++ b/crates/nostr/examples/nip09.rs @@ -14,7 +14,7 @@ fn main() -> Result<()> { .id(event_id) .reason("these posts were published by accident"); - let event: Event = EventBuilder::delete(request).finalize(&keys)?; + let event: Event = request.finalize(&keys)?; println!("{}", event.as_json()); Ok(()) diff --git a/crates/nostr/examples/nip13.rs b/crates/nostr/examples/nip13.rs index 406853678..6a2c353cd 100644 --- a/crates/nostr/examples/nip13.rs +++ b/crates/nostr/examples/nip13.rs @@ -14,7 +14,7 @@ fn main() -> Result<()> { // Build unsigned event let unsigned: UnsignedEvent = - EventBuilder::text_note(msg_content).finalize_unsigned(keys.public_key); + EventBuilder::text_note(msg_content).finalize_unsigned(keys.public_key)?; #[cfg(not(feature = "pow-multi-thread"))] let adapter = SingleThreadPow; diff --git a/crates/nostr/src/event/builder.rs b/crates/nostr/src/event/builder.rs index 559f6daba..9d5228096 100644 --- a/crates/nostr/src/event/builder.rs +++ b/crates/nostr/src/event/builder.rs @@ -7,6 +7,7 @@ use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec::Vec; +use core::convert::Infallible; use core::fmt; use core::ops::Range; @@ -37,18 +38,23 @@ impl fmt::Display for WrongKindError { /// Template that can be converted into a generic [`EventBuilder`]. pub trait EventBuilderTemplate: Sized { + /// Error type + type Error: core::error::Error; + /// Convert into the generic event builder. - fn build(self) -> EventBuilder; + fn build(self) -> Result; } impl FinalizeUnsignedEvent for B where B: EventBuilderTemplate, { + type Error = B::Error; + #[inline] - fn finalize_unsigned(self, public_key: PublicKey) -> UnsignedEvent { - let builder: EventBuilder = self.build(); - builder.finalize_unsigned(public_key) + fn finalize_unsigned(self, public_key: PublicKey) -> Result { + let builder: EventBuilder = self.build()?; + Ok(builder.finalize_unsigned(public_key).unwrap_infallible()) } } @@ -60,15 +66,18 @@ where type Error = SignerError; fn finalize(self, signer: &S) -> Result { - let builder: EventBuilder = self.build(); + let builder: EventBuilder = self.build().map_err(SignerError::backend)?; builder.finalize(signer) } } /// Template that can asynchronously be converted into a generic [`EventBuilder`]. pub trait EventBuilderTemplateAsync { + /// Error type + type Error: core::error::Error; + /// Convert this typed builder into the generic event builder. - fn build_async<'a>(self) -> BoxedFuture<'a, EventBuilder> + fn build_async<'a>(self) -> BoxedFuture<'a, Result> where Self: 'a; } @@ -77,8 +86,10 @@ impl EventBuilderTemplateAsync for B where B: EventBuilderTemplate + Send, { + type Error = B::Error; + #[inline] - fn build_async<'a>(self) -> BoxedFuture<'a, EventBuilder> + fn build_async<'a>(self) -> BoxedFuture<'a, Result> where Self: 'a, { @@ -90,14 +101,22 @@ impl FinalizeUnsignedEventAsync for B where B: EventBuilderTemplateAsync + Send, { + type Error = B::Error; + #[inline] - fn finalize_unsigned_async<'a>(self, public_key: PublicKey) -> BoxedFuture<'a, UnsignedEvent> + fn finalize_unsigned_async<'a>( + self, + public_key: PublicKey, + ) -> BoxedFuture<'a, Result> where Self: 'a, { Box::pin(async move { - let builder: EventBuilder = self.build_async().await; - builder.finalize_unsigned_async(public_key).await + let builder: EventBuilder = self.build_async().await?; + Ok(builder + .finalize_unsigned_async(public_key) + .await + .unwrap_infallible()) }) } } @@ -115,7 +134,7 @@ where S: 'a, { Box::pin(async move { - let builder: EventBuilder = self.build_async().await; + let builder: EventBuilder = self.build_async().await.map_err(SignerError::backend)?; builder.finalize_async(signer).await }) } @@ -183,29 +202,6 @@ impl EventBuilder { self } - /// Profile metadata - /// - /// - /// - /// # Example - /// ```rust,no_run - /// use nostr::prelude::*; - /// - /// let metadata = Metadata::new() - /// .name("username") - /// .display_name("My Username") - /// .about("Description") - /// .picture(Url::parse("https://example.com/avatar.png").unwrap()) - /// .nip05("username@example.com") - /// .lud16("pay@yukikishimoto.com"); - /// - /// let builder = EventBuilder::metadata(&metadata); - /// ``` - #[inline] - pub fn metadata(metadata: &Metadata) -> Self { - Self::new(Kind::Metadata, metadata.as_json()) - } - /// Relay list metadata /// /// @@ -416,14 +412,6 @@ impl EventBuilder { } } - /// Event deletion request - /// - /// - #[inline] - pub fn delete(request: EventDeletionRequest) -> Self { - request.to_event_builder() - } - /// Request to vanish /// /// @@ -1245,88 +1233,6 @@ impl EventBuilder { Self::new(Kind::UserStatus, content).tags(tags) } - /// Code Snippets - /// - /// - #[inline] - pub fn code_snippet(snippet: CodeSnippet) -> Self { - snippet.to_event_builder() - } - - /// Git Repository Announcement - /// - /// - #[inline] - pub fn git_repository_announcement(announcement: GitRepositoryAnnouncement) -> Self { - announcement.to_event_builder() - } - - /// Git Issue - /// - /// - #[inline] - pub fn git_issue(issue: GitIssue) -> Result { - issue.to_event_builder() - } - - /// Git Patch - /// - /// - #[inline] - pub fn git_patch(patch: GitPatch) -> Result { - patch.to_event_builder() - } - - /// Git Pull Request - /// - /// - #[inline] - pub fn git_pull_request(pull_request: GitPullRequest) -> Result { - pull_request.to_event_builder() - } - - /// Git Pull Request Update - /// - /// - #[inline] - pub fn git_pull_request_update(update: GitPullRequestUpdate) -> Result { - update.to_event_builder() - } - - /// Git User Grasp List - /// - /// - #[inline] - pub fn git_user_grasp_list(grasp_list: GitUserGraspList) -> Self { - grasp_list.to_event_builder() - } - - /// Torrent metadata - /// - /// - #[inline] - pub fn torrent(metadata: Torrent) -> Self { - metadata.to_event_builder() - } - - // TODO: add `torrent_comment` - - /// Create a poll - /// - /// - #[inline] - pub fn poll(poll: Poll) -> Self { - poll.to_event_builder() - } - - /// Create a poll response - /// - /// - #[inline] - pub fn poll_response(response: PollResponse) -> Self { - response.to_event_builder() - } - /// Chat message /// /// @@ -1411,14 +1317,6 @@ impl EventBuilder { Self::new(Kind::Comment, content).tags(tags) } - - /// Web Bookmark - /// - /// - #[inline] - pub fn web_bookmark(web_bookmark: WebBookmark) -> Self { - web_bookmark.to_event_builder() - } } fn has_nostr_event_uri(content: &str, event_id: &EventId) -> bool { @@ -1438,9 +1336,11 @@ fn has_nostr_event_uri(content: &str, event_id: &EventId) -> bool { } impl FinalizeUnsignedEvent for EventBuilder { + type Error = Infallible; + #[inline] - fn finalize_unsigned(self, public_key: PublicKey) -> UnsignedEvent { - UnsignedEvent { + fn finalize_unsigned(self, public_key: PublicKey) -> Result { + Ok(UnsignedEvent { // Not compute event ID, as the user may want POW, so would be an unnecessary computation. id: None, pubkey: public_key, @@ -1448,13 +1348,18 @@ impl FinalizeUnsignedEvent for EventBuilder { kind: self.kind, tags: self.tags, content: self.content, - } + }) } } impl FinalizeUnsignedEventAsync for EventBuilder { + type Error = Infallible; + #[inline] - fn finalize_unsigned_async<'a>(self, public_key: PublicKey) -> BoxedFuture<'a, UnsignedEvent> + fn finalize_unsigned_async<'a>( + self, + public_key: PublicKey, + ) -> BoxedFuture<'a, Result> where Self: 'a, { @@ -1470,7 +1375,7 @@ where fn finalize(self, signer: &S) -> Result { let public_key: PublicKey = signer.get_public_key().map_err(SignerError::backend)?; - let unsigned: UnsignedEvent = self.finalize_unsigned(public_key); + let unsigned: UnsignedEvent = self.finalize_unsigned(public_key).unwrap_infallible(); signer.sign_event(unsigned).map_err(SignerError::backend) } } @@ -1491,7 +1396,7 @@ where .get_public_key_async() .await .map_err(SignerError::backend)?; - let unsigned: UnsignedEvent = self.finalize_unsigned(public_key); + let unsigned: UnsignedEvent = self.finalize_unsigned(public_key).unwrap_infallible(); signer .sign_event_async(unsigned) .await diff --git a/crates/nostr/src/event/unsigned.rs b/crates/nostr/src/event/unsigned.rs index 97ba3700d..df7491f5f 100644 --- a/crates/nostr/src/event/unsigned.rs +++ b/crates/nostr/src/event/unsigned.rs @@ -219,14 +219,23 @@ impl From for UnsignedEvent { /// Finalize a builder into an unsigned event. pub trait FinalizeUnsignedEvent: Sized { + /// Error type + type Error: core::error::Error; + /// Build the unsigned event with the supplied public key. - fn finalize_unsigned(self, public_key: PublicKey) -> UnsignedEvent; + fn finalize_unsigned(self, public_key: PublicKey) -> Result; } /// Finalize a builder into an unsigned event asynchronously. pub trait FinalizeUnsignedEventAsync: Sized { + /// Error type + type Error: core::error::Error; + /// Build the unsigned event with the supplied public key. - fn finalize_unsigned_async<'a>(self, public_key: PublicKey) -> BoxedFuture<'a, UnsignedEvent> + fn finalize_unsigned_async<'a>( + self, + public_key: PublicKey, + ) -> BoxedFuture<'a, Result> where Self: 'a; } diff --git a/crates/nostr/src/nips/nip01/mod.rs b/crates/nostr/src/nips/nip01/mod.rs index f9cd9b241..87e92a236 100644 --- a/crates/nostr/src/nips/nip01/mod.rs +++ b/crates/nostr/src/nips/nip01/mod.rs @@ -8,6 +8,7 @@ use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; +use core::convert::Infallible; use core::fmt; use core::num::ParseIntError; use core::str::FromStr; @@ -22,10 +23,10 @@ mod tags; pub use self::tags::*; use super::nip19::{self, FromBech32, Nip19Coordinate, ToBech32}; use super::nip21::{FromNostrUri, ToNostrUri}; -use crate::event::TagCodecError; +use crate::event::{EventBuilderTemplate, TagCodecError}; use crate::types::url::{self, Url}; use crate::util::impl_json_methods; -use crate::{Filter, Kind, PublicKey, Tag, event, key}; +use crate::{EventBuilder, Filter, Kind, PublicKey, Tag, event, key}; /// NIP-01 error #[derive(Debug, PartialEq)] @@ -322,6 +323,30 @@ impl ToBech32 for CoordinateBorrow<'_> { impl ToNostrUri for CoordinateBorrow<'_> {} /// Metadata +/// +/// # Examples +/// +/// ## Building a user metadata event +/// +/// ```rust,no_run +/// # use nostr::prelude::*; +/// # #[cfg(all(feature = "std", feature = "os-rng"))] +/// # fn run() -> Result<(), Box> { +/// let metadata = Metadata::new() +/// .name("username") +/// .display_name("My Username") +/// .about("Description") +/// .picture(Url::parse("https://example.com/avatar.png")?) +/// .nip05("username@example.com") +/// .lud16("pay@yukikishimoto.com"); +/// +/// let keys = Keys::parse("nsec...")?; +/// let event: Event = metadata.finalize(&keys)?; +/// # _ = event; +/// # Ok(()) +/// # } +/// # fn main() {} +/// ``` #[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Metadata { /// Name @@ -524,6 +549,24 @@ where deserializer.deserialize_map(GenericTagsVisitor) } +impl EventBuilderTemplate for &Metadata { + type Error = Infallible; + + #[inline] + fn build(self) -> Result { + Ok(EventBuilder::new(Kind::Metadata, self.as_json())) + } +} + +impl EventBuilderTemplate for Metadata { + type Error = Infallible; + + #[inline] + fn build(self) -> Result { + (&self).build() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/nostr/src/nips/nip02.rs b/crates/nostr/src/nips/nip02.rs index 44a015322..42e5499ab 100644 --- a/crates/nostr/src/nips/nip02.rs +++ b/crates/nostr/src/nips/nip02.rs @@ -8,6 +8,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; +use core::convert::Infallible; use core::fmt; use super::util::{take_and_parse_optional_relay_url, take_optional_string, take_public_key}; @@ -202,7 +203,9 @@ impl ContactListBuilder { } impl EventBuilderTemplate for ContactListBuilder { - fn build(self) -> EventBuilder { + type Error = Infallible; + + fn build(self) -> Result { let tags = self.contacts.into_iter().map(|contact| { Nip02Tag::PublicKey { public_key: contact.public_key, @@ -211,7 +214,7 @@ impl EventBuilderTemplate for ContactListBuilder { } .to_tag() }); - EventBuilder::new(Kind::ContactList, "").tags(tags) + Ok(EventBuilder::new(Kind::ContactList, "").tags(tags)) } } diff --git a/crates/nostr/src/nips/nip09.rs b/crates/nostr/src/nips/nip09.rs index 65c4f5fff..961b0907b 100644 --- a/crates/nostr/src/nips/nip09.rs +++ b/crates/nostr/src/nips/nip09.rs @@ -2,15 +2,16 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -//! NIP09: Event Deletion Request +//! NIP-09: Event Deletion Request //! //! use alloc::string::String; use alloc::vec::Vec; +use core::convert::Infallible; use super::nip01::Coordinate; -use crate::event::{EventBuilder, EventId, Kind, Tag}; +use crate::event::{EventBuilder, EventBuilderTemplate, EventId, Kind, Tag}; /// Event deletion request #[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -73,9 +74,12 @@ impl EventDeletionRequest { self.reason = Some(reason.into()); self } +} + +impl EventBuilderTemplate for EventDeletionRequest { + type Error = Infallible; - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_event_builder(self) -> EventBuilder { + fn build(self) -> Result { let mut tags: Vec = Vec::with_capacity(self.ids.len() + self.coordinates.len()); for id in self.ids.into_iter() { @@ -86,7 +90,7 @@ impl EventDeletionRequest { tags.push(Tag::coordinate(coordinate, None)); } - EventBuilder::new(Kind::EventDeletion, self.reason.unwrap_or_default()).tags(tags) + Ok(EventBuilder::new(Kind::EventDeletion, self.reason.unwrap_or_default()).tags(tags)) } } @@ -113,7 +117,7 @@ mod tests { .coordinate(coordinate) .reason("these posts were published by accident"); - let event: Event = request.to_event_builder().finalize(&keys).unwrap(); + let event: Event = request.finalize(&keys).unwrap(); assert_eq!(event.kind, Kind::EventDeletion); assert_eq!(event.content, "these posts were published by accident"); @@ -134,7 +138,7 @@ mod tests { // Event ID without reason let request = EventDeletionRequest::new().id(event_id); - let event: Event = request.to_event_builder().finalize(&keys).unwrap(); + let event: Event = request.finalize(&keys).unwrap(); assert_eq!(event.kind, Kind::EventDeletion); assert!(event.content.is_empty()); diff --git a/crates/nostr/src/nips/nip13/mod.rs b/crates/nostr/src/nips/nip13/mod.rs index b2c35be9e..e9b23e0f0 100644 --- a/crates/nostr/src/nips/nip13/mod.rs +++ b/crates/nostr/src/nips/nip13/mod.rs @@ -627,7 +627,8 @@ pub mod tests { let unsigned = EventBuilder::text_note( "Why must I find leading zero bits? Is there no beauty in the ones?", ) - .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()); + .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()) + .unwrap_infallible(); let unsigned = unsigned .mine(&TestAdapter, NonZeroU8::new(2).unwrap()) @@ -671,7 +672,8 @@ pub mod tests { let unsigned = EventBuilder::text_note( "Why must I find leading zero bits? Is there no beauty in the ones?", ) - .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()); + .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()) + .unwrap_infallible(); let unsigned = unsigned .mine_async(&AsyncTestAdapter, NonZeroU8::new(2).unwrap()) diff --git a/crates/nostr/src/nips/nip13/multi_thread.rs b/crates/nostr/src/nips/nip13/multi_thread.rs index 275d0c1fe..63cbd6d4c 100644 --- a/crates/nostr/src/nips/nip13/multi_thread.rs +++ b/crates/nostr/src/nips/nip13/multi_thread.rs @@ -137,7 +137,8 @@ pub mod tests { #[test] fn threaded_adapter() { let unsigned = EventBuilder::text_note("Wait, you guys are getting paid to find nonces? I'm just doing it for the leading zeros") - .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()); + .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()) + .unwrap_infallible(); let unsigned = unsigned .mine(&MultiThreadPow, NonZeroU8::new(2).unwrap()) @@ -156,7 +157,8 @@ pub mod tests { fn multi_thread_mining_can_be_cancelled() { let cancel: Arc = Arc::new(AtomicBool::new(false)); let unsigned = EventBuilder::text_note("multi thread cancellation test") - .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()); + .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()) + .unwrap_infallible(); let worker_cancel: Arc = cancel.clone(); let handle = thread::spawn(move || mine(unsigned, u8::MAX, worker_cancel)); diff --git a/crates/nostr/src/nips/nip13/single_thread.rs b/crates/nostr/src/nips/nip13/single_thread.rs index 0c4bf90f6..b295716d1 100644 --- a/crates/nostr/src/nips/nip13/single_thread.rs +++ b/crates/nostr/src/nips/nip13/single_thread.rs @@ -94,7 +94,8 @@ pub mod tests { let unsigned = EventBuilder::text_note( "Proof of Work: The only workout my CPU gets since I stopped gaming", ) - .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()); + .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()) + .unwrap_infallible(); // Mine the event let unsigned = unsigned @@ -114,7 +115,8 @@ pub mod tests { fn single_thread_mining_can_be_cancelled() { let cancel: Arc = Arc::new(AtomicBool::new(false)); let unsigned = EventBuilder::text_note("single thread cancellation test") - .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()); + .finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap()) + .unwrap_infallible(); let worker_cancel: Arc = cancel.clone(); let handle = thread::spawn(move || mine(unsigned, u8::MAX, Some(worker_cancel.as_ref()))); diff --git a/crates/nostr/src/nips/nip17.rs b/crates/nostr/src/nips/nip17.rs index 0cfbbe5f1..3e62054e8 100644 --- a/crates/nostr/src/nips/nip17.rs +++ b/crates/nostr/src/nips/nip17.rs @@ -31,7 +31,7 @@ use crate::key::{AsyncGetPublicKey, GetPublicKey}; use crate::signer::SignerError; use crate::types::url::{self, RelayUrl}; #[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))] -use crate::util::BoxedFuture; +use crate::util::{BoxedFuture, UnwrapInfallible}; const RELAY: &str = "relay"; @@ -222,6 +222,7 @@ fn make_rumor( .tag(Tag::public_key(receiver)) .tags(extra_tags) .finalize_unsigned(sender) + .unwrap_infallible() } /// Standardized NIP-17 tags diff --git a/crates/nostr/src/nips/nip34.rs b/crates/nostr/src/nips/nip34.rs index 94e0a693f..700abb5a6 100644 --- a/crates/nostr/src/nips/nip34.rs +++ b/crates/nostr/src/nips/nip34.rs @@ -10,6 +10,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; +use core::convert::Infallible; use core::fmt; use core::num::ParseIntError; use core::str::FromStr; @@ -20,7 +21,9 @@ use hashes::sha1::Hash as Sha1Hash; use super::nip01::{self, 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::event::{ + EventBuilder, EventBuilderTemplate, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, +}; use crate::types::url::{self, Url}; use crate::{EventId, Kind, PublicKey, RelayUrl, Timestamp, key}; @@ -498,8 +501,10 @@ pub struct GitRepositoryAnnouncement { pub maintainers: Vec, } -impl GitRepositoryAnnouncement { - pub(crate) fn to_event_builder(self) -> EventBuilder { +impl EventBuilderTemplate for GitRepositoryAnnouncement { + type Error = Infallible; + + fn build(self) -> Result { let mut tags: Vec = Vec::with_capacity(1); // Add repo ID @@ -541,7 +546,7 @@ impl GitRepositoryAnnouncement { } // Build - EventBuilder::new(Kind::GitRepoAnnouncement, "").tags(tags) + Ok(EventBuilder::new(Kind::GitRepoAnnouncement, "").tags(tags)) } } @@ -558,9 +563,10 @@ pub struct GitIssue { pub labels: Vec, } -impl GitIssue { - /// Based on revision. - pub(crate) fn to_event_builder(self) -> Result { +impl EventBuilderTemplate for GitIssue { + type Error = Error; + + fn build(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { return Err(Error::UnexpectedKind); @@ -666,8 +672,10 @@ pub struct GitPatch { pub labels: Vec, } -impl GitPatch { - pub(crate) fn to_event_builder(self) -> Result { +impl EventBuilderTemplate for GitPatch { + type Error = Error; + + fn build(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { return Err(Error::UnexpectedKind); @@ -753,8 +761,10 @@ pub struct GitPullRequest { pub merge_base: Option, } -impl GitPullRequest { - pub(crate) fn to_event_builder(self) -> Result { +impl EventBuilderTemplate for GitPullRequest { + type Error = Error; + + fn build(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { return Err(Error::UnexpectedKind); @@ -828,8 +838,10 @@ pub struct GitPullRequestUpdate { pub merge_base: Option, } -impl GitPullRequestUpdate { - pub(crate) fn to_event_builder(self) -> Result { +impl EventBuilderTemplate for GitPullRequestUpdate { + type Error = Error; + + fn build(self) -> Result { // Check if repository address kind is wrong if self.repository.kind != Kind::GitRepoAnnouncement { return Err(Error::UnexpectedKind); @@ -895,15 +907,17 @@ pub struct GitUserGraspList { pub grasp_servers: Vec, } -impl GitUserGraspList { - pub(crate) fn to_event_builder(self) -> EventBuilder { +impl EventBuilderTemplate for GitUserGraspList { + type Error = Infallible; + + fn build(self) -> Result { let tags: Vec = self .grasp_servers .into_iter() .map(|url| Nip34Tag::Grasp(url).to_tag()) .collect(); - EventBuilder::new(Kind::GitUserGraspList, "").tags(tags) + Ok(EventBuilder::new(Kind::GitUserGraspList, "").tags(tags)) } } @@ -935,7 +949,7 @@ mod tests { }; let keys = Keys::generate(); - let event: Event = repo.to_event_builder().finalize(&keys).unwrap(); + let event: Event = repo.finalize(&keys).unwrap(); assert_eq!(event.kind, Kind::GitRepoAnnouncement); assert!(event.content.is_empty()); @@ -995,7 +1009,7 @@ mod tests { }; let keys = Keys::generate(); - let event: Event = repo.to_event_builder().unwrap().finalize(&keys).unwrap(); + let event: Event = repo.finalize(&keys).unwrap(); assert_eq!(event.kind, Kind::GitIssue); assert_eq!(event.content, "My issue content"); @@ -1043,7 +1057,7 @@ mod tests { }; let keys = Keys::generate(); - let event: Event = repo.to_event_builder().unwrap().finalize(&keys).unwrap(); + let event: Event = repo.finalize(&keys).unwrap(); assert_eq!(event.kind, Kind::GitPatch); assert_eq!(event.content, ""); @@ -1100,7 +1114,7 @@ mod tests { }; let keys = Keys::generate(); - let event: Event = update.to_event_builder().unwrap().finalize(&keys).unwrap(); + let event: Event = update.finalize(&keys).unwrap(); assert_eq!(event.kind, Kind::GitPullRequestUpdate); assert!(event.content.is_empty()); diff --git a/crates/nostr/src/nips/nip35.rs b/crates/nostr/src/nips/nip35.rs index 1bba49aad..9e778d230 100644 --- a/crates/nostr/src/nips/nip35.rs +++ b/crates/nostr/src/nips/nip35.rs @@ -11,9 +11,11 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; +use core::convert::Infallible; use hashes::sha1::Hash as Sha1Hash; +use crate::event::EventBuilderTemplate; use crate::types::url::Url; use crate::{EventBuilder, Kind, Tag}; @@ -45,9 +47,10 @@ pub struct Torrent { pub hashtags: Vec, } -impl Torrent { - /// Converts the torrent metadata into an [`EventBuilder`]. - pub fn to_event_builder(self) -> EventBuilder { +impl EventBuilderTemplate for Torrent { + type Error = Infallible; + + fn build(self) -> Result { let mut tags: Vec = Vec::with_capacity( 2 + self.files.len() + self.trackers.len() @@ -75,6 +78,6 @@ impl Torrent { tags.push(Tag::hashtag(tag)); } - EventBuilder::new(Kind::Torrent, self.description).tags(tags) + Ok(EventBuilder::new(Kind::Torrent, self.description).tags(tags)) } } diff --git a/crates/nostr/src/nips/nip59.rs b/crates/nostr/src/nips/nip59.rs index c7213f066..069b69f9b 100644 --- a/crates/nostr/src/nips/nip59.rs +++ b/crates/nostr/src/nips/nip59.rs @@ -442,8 +442,9 @@ mod tests { .unwrap(); // Compose Gift Wrap event - let rumor: UnsignedEvent = - EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key); + let rumor: UnsignedEvent = EventBuilder::text_note("Test") + .finalize_unsigned(sender_keys.public_key) + .unwrap_infallible(); let event: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor.clone()) .finalize(&sender_keys) .unwrap(); @@ -470,8 +471,9 @@ mod tests { Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e") .unwrap(); - let rumor: UnsignedEvent = - EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key); + let rumor: UnsignedEvent = EventBuilder::text_note("Test") + .finalize_unsigned(sender_keys.public_key) + .unwrap_infallible(); let event: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor) .finalize(&sender_keys) .unwrap(); @@ -491,8 +493,9 @@ mod tests { Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e") .unwrap(); - let rumor: UnsignedEvent = - EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key); + let rumor: UnsignedEvent = EventBuilder::text_note("Test") + .finalize_unsigned(sender_keys.public_key) + .unwrap_infallible(); let seal = GiftWrapSealBuilder::new(rumor, receiver_keys.public_key()) .finalize(&sender_keys) .unwrap(); @@ -510,8 +513,9 @@ mod tests { Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e") .unwrap(); - let rumor: UnsignedEvent = - EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key); + let rumor: UnsignedEvent = EventBuilder::text_note("Test") + .finalize_unsigned(sender_keys.public_key) + .unwrap_infallible(); let seal = GiftWrapSealBuilder::new(rumor, receiver_keys.public_key()) .finalize_async(&sender_keys) .await @@ -535,8 +539,9 @@ mod tests { // Construct a rumor that lies about its pubkey but is still wrapped/signed // by `sender_keys`. This mimics a spoofing attempt the recipient must reject. - let rumor: UnsignedEvent = - EventBuilder::text_note("spoofed").finalize_unsigned(impersonated_keys.public_key()); + let rumor: UnsignedEvent = EventBuilder::text_note("spoofed") + .finalize_unsigned(impersonated_keys.public_key()) + .unwrap_infallible(); let gift_wrap: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor) .finalize(&sender_keys) diff --git a/crates/nostr/src/nips/nip62.rs b/crates/nostr/src/nips/nip62.rs index dcd8f2ff0..004c5c65a 100644 --- a/crates/nostr/src/nips/nip62.rs +++ b/crates/nostr/src/nips/nip62.rs @@ -195,7 +195,8 @@ mod tests { "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", ) .unwrap(), - ); + ) + .unwrap_infallible(); assert!(is_valid_vanish_request_for_relay( all_relays.tags.as_slice(), @@ -212,7 +213,8 @@ mod tests { "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", ) .unwrap(), - ); + ) + .unwrap_infallible(); assert!(is_valid_vanish_request_for_relay( single_relay.tags.as_slice(), @@ -223,10 +225,14 @@ mod tests { Some(&relay_b) )); - let other_kind = EventBuilder::text_note("hello").finalize_unsigned( - PublicKey::from_hex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") + let other_kind = EventBuilder::text_note("hello") + .finalize_unsigned( + PublicKey::from_hex( + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ) .unwrap(), - ); + ) + .unwrap_infallible(); assert!(!is_valid_vanish_request_for_relay( other_kind.tags.as_slice(), diff --git a/crates/nostr/src/nips/nip88.rs b/crates/nostr/src/nips/nip88.rs index cf026c474..57eb2dcfc 100644 --- a/crates/nostr/src/nips/nip88.rs +++ b/crates/nostr/src/nips/nip88.rs @@ -8,12 +8,15 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; +use core::convert::Infallible; 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 crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::event::{ + EventBuilderTemplate, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, +}; use crate::types::url; use crate::{Event, EventBuilder, EventId, Kind, RelayUrl, Timestamp}; @@ -231,9 +234,12 @@ impl Poll { ends_at, }) } +} + +impl EventBuilderTemplate for Poll { + type Error = Infallible; - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_event_builder(self) -> EventBuilder { + fn build(self) -> Result { let mut tags: Vec = Vec::with_capacity(1 + self.options.len() + self.relays.len()); tags.push(Nip88Tag::PollType(self.r#type).to_tag()); @@ -250,7 +256,7 @@ impl Poll { tags.push(Nip88Tag::PollEndsAt(timestamp).to_tag()); } - EventBuilder::new(Kind::Poll, self.title).tags(tags) + Ok(EventBuilder::new(Kind::Poll, self.title).tags(tags)) } } @@ -272,10 +278,10 @@ pub enum PollResponse { responses: Vec, }, } +impl EventBuilderTemplate for PollResponse { + type Error = Infallible; -impl PollResponse { - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_event_builder(self) -> EventBuilder { + fn build(self) -> Result { let tags: Vec = match self { Self::SingleChoice { poll_id, response } => { vec![ @@ -296,7 +302,7 @@ impl PollResponse { } }; - EventBuilder::new(Kind::PollResponse, "").tags(tags) + Ok(EventBuilder::new(Kind::PollResponse, "").tags(tags)) } } diff --git a/crates/nostr/src/nips/nipb0.rs b/crates/nostr/src/nips/nipb0.rs index b65307cae..dd655a5a2 100644 --- a/crates/nostr/src/nips/nipb0.rs +++ b/crates/nostr/src/nips/nipb0.rs @@ -9,11 +9,14 @@ use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; +use core::convert::Infallible; use core::fmt; use core::num::ParseIntError; use super::util::{take_string, take_timestamp}; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::event::{ + EventBuilderTemplate, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, +}; use crate::{EventBuilder, Kind, Timestamp}; const URL: &str = "d"; @@ -167,10 +170,12 @@ impl WebBookmark { } self } +} + +impl EventBuilderTemplate for WebBookmark { + type Error = Infallible; - /// Convert the web bookmark to an event builder - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_event_builder(self) -> EventBuilder { + fn build(self) -> Result { let mut tags: Vec = vec![NipB0Tag::Url(self.url).into()]; let mut add_if_some = |tag: Option| { @@ -186,7 +191,7 @@ impl WebBookmark { tags.push(NipB0Tag::Hashtag(hashtag).into()); } - EventBuilder::new(Kind::WebBookmark, self.description).tags(tags) + Ok(EventBuilder::new(Kind::WebBookmark, self.description).tags(tags)) } } diff --git a/crates/nostr/src/nips/nipc0.rs b/crates/nostr/src/nips/nipc0.rs index 7e9d62f71..acbda09e8 100644 --- a/crates/nostr/src/nips/nipc0.rs +++ b/crates/nostr/src/nips/nipc0.rs @@ -9,10 +9,13 @@ use alloc::string::String; use alloc::vec; use alloc::vec::Vec; +use core::convert::Infallible; use core::fmt; use super::util::take_string; -use crate::event::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions}; +use crate::event::{ + EventBuilderTemplate, Tag, TagCodec, TagCodecError, impl_tag_codec_conversions, +}; use crate::{EventBuilder, Kind}; const LANGUAGE: &str = "l"; @@ -245,10 +248,12 @@ impl CodeSnippet { self.repo = Some(repo.into()); self } +} + +impl EventBuilderTemplate for CodeSnippet { + type Error = Infallible; - /// Convert the code snippet to an event builder - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_event_builder(self) -> EventBuilder { + fn build(self) -> Result { let mut tags: Vec = Vec::new(); let mut add_if_some = |tag: Option| { @@ -269,7 +274,7 @@ impl CodeSnippet { tags.push(NipC0Tag::Dependency(dep).into()); } - EventBuilder::new(Kind::CodeSnippet, self.snippet).tags(tags) + Ok(EventBuilder::new(Kind::CodeSnippet, self.snippet).tags(tags)) } } diff --git a/database/nostr-database-test-suite/src/lib.rs b/database/nostr-database-test-suite/src/lib.rs index cf76c8ee4..db2d75a3a 100644 --- a/database/nostr-database-test-suite/src/lib.rs +++ b/database/nostr-database-test-suite/src/lib.rs @@ -39,7 +39,10 @@ macro_rules! database_unit_tests { .collect() } - fn build_event(keys: &Keys, builder: EventBuilder) -> Event { + fn build_event(keys: &Keys, builder: T) -> Event + where + T: FinalizeEvent + { builder.finalize(keys).expect("Failed to build and finalize event") } @@ -51,12 +54,8 @@ macro_rules! database_unit_tests { let events = vec![ build_event(&keys_a, EventBuilder::text_note("Text Note A")), build_event(&keys_b, EventBuilder::text_note("Text Note B")), - build_event(&keys_a, EventBuilder::metadata( - &Metadata::new().name("account-a").display_name("Account A"), - )), - build_event(&keys_b, EventBuilder::metadata( - &Metadata::new().name("account-b").display_name("Account B"), - )), + build_event(&keys_a, Metadata::new().name("account-a").display_name("Account A")), + build_event(&keys_b, Metadata::new().name("account-b").display_name("Account B")), build_event(&keys_a, EventBuilder::new(Kind::Custom(33_333), "") .tag(Tag::identifier("my-id-a"))), build_event(&keys_b, EventBuilder::new(Kind::Custom(33_333), "") @@ -277,7 +276,9 @@ macro_rules! database_unit_tests { // Create first replaceable event (kind 0 - metadata) let metadata1 = Metadata::new().name("First"); - let event1 = EventBuilder::metadata(&metadata1) + let event1 = metadata1 + .build() + .unwrap() .custom_created_at(Timestamp::from_secs(1000)) .finalize(&keys) .expect("Failed to finalize"); @@ -286,7 +287,9 @@ macro_rules! database_unit_tests { // Create newer replaceable event with later timestamp let metadata2 = Metadata::new().name("Second"); - let event2 = EventBuilder::metadata(&metadata2) + let event2 = metadata2 + .build() + .unwrap() .custom_created_at(Timestamp::from_secs(2000)) .finalize(&keys) .expect("Failed to finalize"); @@ -358,7 +361,7 @@ macro_rules! database_unit_tests { // Create deletion event let deletion = - EventBuilder::delete(EventDeletionRequest::new().id(event1.id).id(event2.id)) + EventDeletionRequest::new().id(event1.id).id(event2.id) .finalize(&keys) .expect("Failed to finalize"); @@ -403,7 +406,7 @@ macro_rules! database_unit_tests { let req = EventDeletionRequest::new() .coordinate(event.coordinate().unwrap()); let deletion = - EventBuilder::delete(req) + req .finalize(&keys) .expect("Failed to finalize"); @@ -504,7 +507,7 @@ macro_rules! database_unit_tests { let (keys, expected_event) = add_event( &store, - EventBuilder::metadata(&metadata).custom_created_at(now - Duration::from_secs(120)), + (&metadata).build().unwrap().custom_created_at(now - Duration::from_secs(120)), ) .await; @@ -525,7 +528,7 @@ macro_rules! database_unit_tests { // Replace previous event let (new_expected_event, status) = add_event_with_keys( &store, - EventBuilder::metadata(&metadata).custom_created_at(now), + metadata.build().unwrap().custom_created_at(now), &keys, ) .await; diff --git a/rfs/nostr-blossom/src/bud01.rs b/rfs/nostr-blossom/src/bud01.rs index a06d24c21..3b1e4ef0a 100644 --- a/rfs/nostr-blossom/src/bud01.rs +++ b/rfs/nostr-blossom/src/bud01.rs @@ -1,5 +1,6 @@ //! Implements data structures specific to BUD-01 +use std::convert::Infallible; use std::fmt; use nostr::event::{EventBuilderTemplate, TagCodec}; @@ -82,10 +83,12 @@ impl BlossomAuthorizationVerb { } impl EventBuilderTemplate for BlossomAuthorization { + type Error = Infallible; + /// Blossom authorization event /// /// - fn build(self) -> EventBuilder { + fn build(self) -> Result { let mut tags: Vec = Vec::new(); match self.scope { @@ -106,6 +109,6 @@ impl EventBuilderTemplate for BlossomAuthorization { // Add the 't' tag to say what this auth is for tags.push(Tag::hashtag(self.action.to_string())); - EventBuilder::new(Kind::BlossomAuth, self.content).tags(tags) + Ok(EventBuilder::new(Kind::BlossomAuth, self.content).tags(tags)) } } diff --git a/sdk/examples/client.rs b/sdk/examples/client.rs index 79ee644ab..63e679ba7 100644 --- a/sdk/examples/client.rs +++ b/sdk/examples/client.rs @@ -28,8 +28,8 @@ async fn main() -> Result<()> { println!("Not sent to: {:?}", output.failed); // Create a text note POW event to relays - let unsigned = - EventBuilder::text_note("POW text note from rust-nostr").finalize_unsigned(keys.public_key); + let unsigned = EventBuilder::text_note("POW text note from rust-nostr") + .finalize_unsigned(keys.public_key)?; let unsigned = unsigned .mine_async(&SingleThreadPow, NonZeroU8::new(20).unwrap()) .await?; diff --git a/sdk/examples/code_snippet.rs b/sdk/examples/code_snippet.rs index 0811eacc7..7cc2fc205 100644 --- a/sdk/examples/code_snippet.rs +++ b/sdk/examples/code_snippet.rs @@ -28,7 +28,7 @@ async fn main() -> Result<()> { .extension("rs") .license("MIT"); - let event = EventBuilder::code_snippet(snippet).finalize(&keys)?; + let event = snippet.finalize(&keys)?; let output = client.send_event(&event).await?; diff --git a/sdk/src/relay/api/fetch_events.rs b/sdk/src/relay/api/fetch_events.rs index c194047c7..2adc1fbd0 100644 --- a/sdk/src/relay/api/fetch_events.rs +++ b/sdk/src/relay/api/fetch_events.rs @@ -281,9 +281,7 @@ mod tests { let keys = Keys::generate(); // Build and send event - let event = EventBuilder::metadata(&Metadata::new().name("Test")) - .finalize(&keys) - .unwrap(); + let event = Metadata::new().name("Test").finalize(&keys).unwrap(); r.send_event(&event).await.unwrap(); }); 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..8d30068d2 100644 --- a/signer/nostr-browser-signer-proxy/examples/browser-signer-proxy.rs +++ b/signer/nostr-browser-signer-proxy/examples/browser-signer-proxy.rs @@ -38,7 +38,7 @@ async fn main() -> Result<()> { // Build a gift wrap let receiver = PublicKey::parse("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")?; - let rumor = EventBuilder::new(Kind::Custom(123), "test").finalize_unsigned(public_key); + let rumor = EventBuilder::new(Kind::Custom(123), "test").finalize_unsigned(public_key)?; let gift_wrap = GiftWrapBuilder::new(receiver, rumor) .finalize_async(&proxy) .await?; diff --git a/signer/nostr-browser-signer-proxy/examples/custom_html.rs b/signer/nostr-browser-signer-proxy/examples/custom_html.rs index 71bf7468a..48f07da45 100644 --- a/signer/nostr-browser-signer-proxy/examples/custom_html.rs +++ b/signer/nostr-browser-signer-proxy/examples/custom_html.rs @@ -56,7 +56,7 @@ async fn main() -> Result<()> { // Build a gift wrap let receiver = PublicKey::parse("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")?; - let rumor = EventBuilder::new(Kind::Custom(123), "test").finalize_unsigned(public_key); + let rumor = EventBuilder::new(Kind::Custom(123), "test").finalize_unsigned(public_key)?; let gift_wrap = GiftWrapBuilder::new(receiver, rumor) .finalize_async(&proxy) .await?;