use thiserror::Error;
use crate::event::{Event, EventBuilder, Kind, Tag, TagKind};
pub const KIND_THREAD: Kind = Kind::THREAD;
const TITLE_TAG: &str = "title";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Thread {
pub content: String,
pub title: Option<String>,
pub extra_tags: Vec<Tag>,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ThreadError {
#[error("unexpected kind for NIP-7D thread: {}", .0.as_u16())]
WrongKind(Kind),
}
impl Thread {
#[must_use]
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
title: None,
extra_tags: Vec::new(),
}
}
#[must_use]
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn from_event(event: &Event) -> Result<Self, ThreadError> {
if event.kind != KIND_THREAD {
return Err(ThreadError::WrongKind(event.kind));
}
let mut title: Option<String> = None;
let mut extra_tags: Vec<Tag> = Vec::new();
for tag in &event.tags {
if tag.name() == TITLE_TAG && title.is_none() {
title = tag.get(1).map(str::to_owned);
} else {
extra_tags.push(tag.clone());
}
}
Ok(Self {
content: event.content.clone(),
title,
extra_tags,
})
}
}
impl EventBuilder {
#[must_use]
pub fn thread(thread: &Thread) -> Self {
let mut builder = Self::new(KIND_THREAD, thread.content.clone());
if let Some(title) = &thread.title {
builder = builder.tag(Tag::with(&TagKind::from_wire(TITLE_TAG), [title.clone()]));
}
for tag in &thread.extra_tags {
builder = builder.tag(tag.clone());
}
builder
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Keys;
fn keys() -> Keys {
Keys::parse("0000000000000000000000000000000000000000000000000000000000000003").unwrap()
}
#[test]
fn thread_round_trip() {
let thread = Thread::new("Good morning").title("GM");
let event = EventBuilder::thread(&thread)
.sign_with_keys(&keys())
.unwrap();
let parsed = Thread::from_event(&event).unwrap();
assert_eq!(parsed, thread);
}
#[test]
fn thread_without_title() {
let thread = Thread::new("orphan");
let event = EventBuilder::thread(&thread)
.sign_with_keys(&keys())
.unwrap();
let parsed = Thread::from_event(&event).unwrap();
assert!(parsed.title.is_none());
}
#[test]
fn wrong_kind_is_rejected() {
let event = EventBuilder::text_note("nope")
.sign_with_keys(&keys())
.unwrap();
assert!(matches!(
Thread::from_event(&event),
Err(ThreadError::WrongKind(_))
));
}
}