use alloc::string::{String, ToString};
use alloc::vec::Vec;
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,
};
use crate::event::tag::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions};
use crate::event::{self};
use crate::types::url;
use crate::{EventId, PublicKey, RelayUrl, key};
const EVENT: &str = "e";
#[derive(Debug, PartialEq)]
pub enum Error {
Keys(key::Error),
Event(event::Error),
Url(url::Error),
Codec(TagCodecError),
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<key::Error> for Error {
fn from(e: key::Error) -> Self {
Self::Keys(e)
}
}
impl From<event::Error> for Error {
fn from(e: event::Error) -> Self {
Self::Event(e)
}
}
impl From<url::Error> for Error {
fn from(e: url::Error) -> Self {
Self::Url(e)
}
}
impl From<TagCodecError> for Error {
fn from(e: TagCodecError) -> Self {
Self::Codec(e)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Marker {
Root,
Reply,
}
impl fmt::Display for Marker {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Root => f.write_str("root"),
Self::Reply => f.write_str("reply"),
}
}
}
impl FromStr for Marker {
type Err = Error;
fn from_str(marker: &str) -> Result<Self, Self::Err> {
match marker {
"root" => Ok(Self::Root),
"reply" => Ok(Self::Reply),
_ => Err(Error::InvalidMarker),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Nip10Tag {
Event {
id: EventId,
relay_hint: Option<RelayUrl>,
marker: Option<Marker>,
public_key: Option<PublicKey>,
},
}
impl Nip10Tag {
#[inline]
pub fn is_root(&self) -> bool {
matches!(
self,
Self::Event {
marker: Some(Marker::Root),
..
}
)
}
#[inline]
pub fn is_reply(&self) -> bool {
matches!(
self,
Self::Event {
marker: Some(Marker::Reply),
..
}
)
}
}
impl TagCodec for Nip10Tag {
type Error = Error;
fn parse<I, S>(tag: I) -> Result<Self, Self::Error>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut iter = tag.into_iter();
let kind: S = iter.next().ok_or(TagCodecError::missing_tag_kind())?;
match kind.as_ref() {
EVENT => {
let (id, relay_hint, marker, public_key) = parse_e_tag(iter)?;
Ok(Self::Event {
id,
relay_hint,
marker,
public_key,
})
}
_ => Err(TagCodecError::Unknown.into()),
}
}
fn to_tag(&self) -> Tag {
match self {
Self::Event {
id,
relay_hint,
marker,
public_key,
} => {
let mut tag: Vec<String> = Vec::with_capacity(
2 + relay_hint.is_some() as usize
+ marker.is_some() as usize
+ public_key.is_some() as usize,
);
tag.push(String::from(EVENT));
tag.push(id.to_hex());
match (relay_hint, marker.is_some() || public_key.is_some()) {
(Some(relay_hint), ..) => tag.push(relay_hint.to_string()),
(None, true) => tag.push(String::new()),
(None, false) => {}
}
match (marker, public_key.is_some()) {
(Some(marker), _) => tag.push(marker.to_string()),
(None, true) => tag.push(String::new()),
(None, false) => {}
}
if let Some(public_key) = public_key {
tag.push(public_key.to_hex());
}
Tag::new(tag)
}
}
}
}
impl_tag_codec_conversions!(Nip10Tag);
#[allow(clippy::type_complexity)]
fn parse_e_tag<T, S>(
mut iter: T,
) -> Result<(EventId, Option<RelayUrl>, Option<Marker>, Option<PublicKey>), Error>
where
T: Iterator<Item = S>,
S: AsRef<str>,
{
let id: EventId = take_event_id::<_, _, Error>(&mut iter)?;
let relay_hint: Option<RelayUrl> = take_and_parse_optional_relay_url(&mut iter)?;
let marker: Option<Marker> = match iter.next() {
Some(marker) => {
let marker: &str = marker.as_ref();
if !marker.is_empty() && marker != "mention" {
Some(Marker::from_str(marker)?)
} else {
None
}
}
_ => None,
};
let public_key: Option<PublicKey> = take_and_parse_optional_public_key(&mut iter)?;
Ok((id, relay_hint, marker, public_key))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::key::PublicKey;
#[test]
fn test_parse_empty_tag() {
let tag: Vec<String> = Vec::new();
let err = Nip10Tag::parse(&tag).unwrap_err();
assert_eq!(err, Error::Codec(TagCodecError::missing_tag_kind()));
}
#[test]
fn test_non_existing_tag() {
let tag = vec!["p"];
let err = Nip10Tag::parse(&tag).unwrap_err();
assert_eq!(err, Error::Codec(TagCodecError::Unknown));
}
#[test]
fn test_standardized_e_tag() {
let relay_hint = RelayUrl::parse("wss://relay.example.com").unwrap();
let public_key =
PublicKey::from_hex("aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4")
.unwrap();
let tag = vec![
String::from("e"),
EventId::all_zeros().to_hex(),
relay_hint.to_string(),
String::from("root"),
public_key.to_hex(),
];
let parsed = Nip10Tag::parse(&tag).unwrap();
assert_eq!(
parsed,
Nip10Tag::Event {
id: EventId::all_zeros(),
relay_hint: Some(relay_hint),
marker: Some(Marker::Root),
public_key: Some(public_key),
}
);
assert!(parsed.is_root());
assert!(!parsed.is_reply());
assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap());
}
#[test]
fn test_positional_e_tag() {
let tag = vec![String::from("e"), EventId::all_zeros().to_hex()];
let parsed = Nip10Tag::parse(&tag).unwrap();
assert_eq!(
parsed,
Nip10Tag::Event {
id: EventId::all_zeros(),
relay_hint: None,
marker: None,
public_key: None,
}
);
assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap());
}
#[test]
fn test_standardized_e_tag_with_empty_marker() {
let public_key =
PublicKey::from_hex("aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4")
.unwrap();
let tag = vec![
String::from("e"),
EventId::all_zeros().to_hex(),
String::from("wss://relay.example.com"),
String::new(),
public_key.to_hex(),
];
let parsed = Nip10Tag::parse(&tag).unwrap();
assert_eq!(
parsed,
Nip10Tag::Event {
id: EventId::all_zeros(),
relay_hint: Some(RelayUrl::parse("wss://relay.example.com").unwrap()),
marker: None,
public_key: Some(public_key),
}
);
assert_eq!(parsed.to_tag(), Tag::parse(tag).unwrap());
}
#[test]
fn test_standardized_e_tag_without_marker_is_invalid() {
let public_key =
PublicKey::from_hex("aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4")
.unwrap();
let tag = vec![
String::from("e"),
EventId::all_zeros().to_hex(),
String::from("wss://relay.example.com"),
public_key.to_hex(),
];
let err = Nip10Tag::parse(&tag).unwrap_err();
assert_eq!(err, Error::InvalidMarker);
}
#[test]
fn test_e_tag_with_mention_marker() {
let hex = "19bb195b83fd26db217b6feebb444de4808d90eb4375c31c75ba5bb5c5c10cfc";
let id = EventId::from_hex(hex).unwrap();
let result = Nip10Tag::parse(["e", hex, "", "mention"]);
assert_eq!(
result,
Ok(Nip10Tag::Event {
id,
relay_hint: None,
marker: None,
public_key: None,
})
);
}
}