use super::event::Event;
use super::kind::Kind;
use super::tag::{Tag, Tags};
use super::unsigned::{UnsignedEvent, UnsignedEventError};
use crate::key::{Keys, PublicKey};
use crate::types::{Timestamp, TimestampError};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum EventBuilderError {
#[error("could not read the system clock: {0}")]
Clock(#[from] TimestampError),
#[error(transparent)]
Signer(#[from] UnsignedEventError),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct EventBuilder {
kind: Kind,
content: String,
tags: Tags,
created_at: Option<Timestamp>,
}
impl EventBuilder {
#[must_use]
pub fn new<S>(kind: Kind, content: S) -> Self
where
S: Into<String>,
{
Self {
kind,
content: content.into(),
tags: Tags::new(),
created_at: None,
}
}
#[must_use]
pub fn text_note<S>(content: S) -> Self
where
S: Into<String>,
{
Self::new(Kind::TEXT_NOTE, content)
}
#[must_use]
pub const fn current_kind(&self) -> Kind {
self.kind
}
#[must_use]
pub fn current_content(&self) -> &str {
&self.content
}
#[must_use]
pub const fn current_tags(&self) -> &Tags {
&self.tags
}
#[must_use]
pub const fn current_created_at(&self) -> Option<Timestamp> {
self.created_at
}
#[must_use]
pub const fn tags_mut(&mut self) -> &mut Tags {
&mut self.tags
}
#[must_use]
pub const fn kind(mut self, kind: Kind) -> Self {
self.kind = kind;
self
}
#[must_use]
pub fn content<S>(mut self, content: S) -> Self
where
S: Into<String>,
{
self.content = content.into();
self
}
#[must_use]
pub fn tag(mut self, tag: Tag) -> Self {
self.tags.push(tag);
self
}
#[must_use]
pub fn tags<I>(mut self, tags: I) -> Self
where
I: IntoIterator<Item = Tag>,
{
self.tags.extend(tags);
self
}
#[must_use]
pub const fn created_at(mut self, ts: Timestamp) -> Self {
self.created_at = Some(ts);
self
}
pub fn build_unsigned(self, pubkey: PublicKey) -> Result<UnsignedEvent, EventBuilderError> {
let created_at = match self.created_at {
Some(ts) => ts,
None => Timestamp::now()?,
};
Ok(UnsignedEvent::new(
pubkey,
created_at,
self.kind,
self.tags,
self.content,
))
}
pub fn sign_with_keys(self, keys: &Keys) -> Result<Event, EventBuilderError> {
let unsigned = self.build_unsigned(*keys.public_key())?;
let event = unsigned.sign_with_keys(keys)?;
Ok(event)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture_keys() -> Keys {
Keys::parse("0000000000000000000000000000000000000000000000000000000000000003").unwrap()
}
#[test]
fn new_text_note_signs_and_verifies() {
let keys = fixture_keys();
let event = EventBuilder::text_note("hello")
.sign_with_keys(&keys)
.unwrap();
assert_eq!(event.kind, Kind::TEXT_NOTE);
assert_eq!(event.content, "hello");
event.verify().unwrap();
}
#[test]
fn pin_created_at_round_trip() {
let keys = fixture_keys();
let ts = Timestamp::from_secs(1_700_000_000);
let event = EventBuilder::text_note("pinned")
.created_at(ts)
.sign_with_keys(&keys)
.unwrap();
assert_eq!(event.created_at, ts);
event.verify().unwrap();
}
#[test]
fn tags_and_kind_are_applied() {
let keys = fixture_keys();
let event = EventBuilder::new(Kind::REACTION, "+")
.tag(Tag::new(["e", "abc"]).unwrap())
.tag(Tag::new(["p", "def"]).unwrap())
.sign_with_keys(&keys)
.unwrap();
assert_eq!(event.kind, Kind::REACTION);
assert_eq!(event.tags.len(), 2);
event.verify().unwrap();
}
#[test]
fn build_unsigned_does_not_require_keys() {
let keys = fixture_keys();
let unsigned = EventBuilder::text_note("draft")
.build_unsigned(*keys.public_key())
.unwrap();
assert_eq!(unsigned.pubkey, *keys.public_key());
let event = unsigned.sign_with_keys(&keys).unwrap();
event.verify().unwrap();
}
#[test]
fn extend_tags_in_one_call() {
let keys = fixture_keys();
let event = EventBuilder::text_note("multi")
.tags([
Tag::new(["e", "id-1"]).unwrap(),
Tag::new(["p", "pk-1"]).unwrap(),
])
.sign_with_keys(&keys)
.unwrap();
assert_eq!(event.tags.len(), 2);
event.verify().unwrap();
}
}