use alloc::string::{String, ToString};
use alloc::vec;
use core::fmt;
use core::str::FromStr;
use super::util::take_string;
use crate::event::tag::{Tag, TagCodec, TagCodecError, impl_tag_codec_conversions};
const IDENTITY: &str = "i";
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
Codec(TagCodecError),
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<TagCodecError> for Error {
fn from(e: TagCodecError) -> Self {
Self::Codec(e)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ExternalIdentity {
GitHub,
Twitter,
Mastodon,
Telegram,
}
impl fmt::Display for ExternalIdentity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl ExternalIdentity {
pub fn as_str(&self) -> &str {
match self {
Self::GitHub => "github",
Self::Twitter => "twitter",
Self::Mastodon => "mastodon",
Self::Telegram => "telegram",
}
}
}
impl FromStr for ExternalIdentity {
type Err = Error;
fn from_str(identity: &str) -> Result<Self, Self::Err> {
match identity {
"github" => Ok(Self::GitHub),
"twitter" => Ok(Self::Twitter),
"mastodon" => Ok(Self::Mastodon),
"telegram" => Ok(Self::Telegram),
_ => Err(Error::InvalidIdentity),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Identity {
pub platform: ExternalIdentity,
pub ident: String,
pub proof: String,
}
impl Identity {
pub fn new<S1, S2>(platform_iden: S1, proof: S2) -> Result<Self, Error>
where
S1: AsRef<str>,
S2: Into<String>,
{
let i: &str = platform_iden.as_ref();
let (platform, ident) = i.rsplit_once(':').ok_or(Error::InvalidIdentity)?;
Ok(Self {
platform: ExternalIdentity::from_str(platform)?,
ident: ident.to_string(),
proof: proof.into(),
})
}
#[inline]
fn tag_platform_identity(&self) -> String {
format!("{}:{}", self.platform, self.ident)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Nip39Tag {
Identity(Identity),
}
impl TagCodec for Nip39Tag {
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() {
IDENTITY => {
let platform_ident: String = take_string(&mut iter, "identity")?;
let proof: String = take_string(&mut iter, "proof")?;
Ok(Self::Identity(Identity::new(platform_ident, proof)?))
}
_ => Err(TagCodecError::Unknown.into()),
}
}
fn to_tag(&self) -> Tag {
match self {
Self::Identity(identity) => Tag::new(vec![
String::from(IDENTITY),
identity.tag_platform_identity(),
identity.proof.clone(),
]),
}
}
}
impl_tag_codec_conversions!(Nip39Tag);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_tag() {
let parsed =
Nip39Tag::parse(["i", "github:semisol", "9721ce4ee4fceb91c9711ca2a6c9a5ab"]).unwrap();
assert_eq!(
parsed,
Nip39Tag::Identity(Identity {
platform: ExternalIdentity::GitHub,
ident: String::from("semisol"),
proof: String::from("9721ce4ee4fceb91c9711ca2a6c9a5ab"),
})
);
assert_eq!(
parsed.to_tag(),
Tag::parse(["i", "github:semisol", "9721ce4ee4fceb91c9711ca2a6c9a5ab",]).unwrap()
);
}
#[test]
fn test_identity_tag_with_extra_values() {
let parsed = Nip39Tag::parse([
"i",
"twitter:semisol_public",
"1619358434134196225",
"extra",
])
.unwrap();
assert_eq!(
parsed,
Nip39Tag::Identity(Identity {
platform: ExternalIdentity::Twitter,
ident: String::from("semisol_public"),
proof: String::from("1619358434134196225"),
})
);
}
#[test]
fn test_identity_tag_missing_proof() {
let err = Nip39Tag::parse(["i", "github:semisol"]).unwrap_err();
assert_eq!(err, Error::Codec(TagCodecError::Missing("proof")));
}
}