use crate::error::Result;
use crate::types::IdentifierKind;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Recipient {
Address(String),
InboxId(String),
Ens(String),
}
impl Recipient {
#[must_use]
pub fn parse(input: &str) -> Self {
let s = input.trim();
if s.len() == 42
&& s.starts_with("0x")
&& s.as_bytes()
.get(2..)
.is_some_and(|b| b.iter().all(u8::is_ascii_hexdigit))
{
Self::Address(s.to_lowercase())
} else if s.contains('.') {
Self::Ens(s.to_owned())
} else {
Self::InboxId(s.to_owned())
}
}
}
impl From<&str> for Recipient {
fn from(s: &str) -> Self {
Self::parse(s)
}
}
impl From<String> for Recipient {
fn from(s: String) -> Self {
Self::parse(&s)
}
}
impl From<crate::types::AccountIdentifier> for Recipient {
fn from(id: crate::types::AccountIdentifier) -> Self {
match id.kind {
IdentifierKind::Ethereum => Self::Address(id.address),
IdentifierKind::Passkey => Self::InboxId(id.address),
}
}
}
impl std::fmt::Display for Recipient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Address(a) => f.write_str(a),
Self::InboxId(id) => f.write_str(id),
Self::Ens(name) => f.write_str(name),
}
}
}
pub trait Resolver: Send + Sync {
fn resolve(&self, name: &str) -> Result<String>;
fn reverse_resolve(&self, _address: &str) -> Result<Option<String>> {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_eth_address_lowercase() {
let addr = "0x1234567890abcdef1234567890abcdef12345678";
assert_eq!(Recipient::parse(addr), Recipient::Address(addr.into()));
}
#[test]
fn parse_eth_address_normalizes_case() {
let mixed = "0xABCDef1234567890abcdef1234567890ABCDEF12";
assert_eq!(
Recipient::parse(mixed),
Recipient::Address(mixed.to_lowercase())
);
}
#[test]
fn parse_trims_whitespace() {
let padded = " 0x1234567890abcdef1234567890abcdef12345678 ";
assert!(matches!(Recipient::parse(padded), Recipient::Address(_)));
}
#[test]
fn parse_42_char_non_hex_is_not_address() {
let bad = "0xZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
assert_eq!(Recipient::parse(bad), Recipient::InboxId(bad.into()));
}
#[test]
fn parse_short_0x_is_inbox_id() {
assert_eq!(
Recipient::parse("0x1234"),
Recipient::InboxId("0x1234".into())
);
}
#[test]
fn parse_empty_string() {
assert_eq!(Recipient::parse(""), Recipient::InboxId(String::new()));
}
#[test]
fn parse_ens_name() {
assert_eq!(
Recipient::parse("vitalik.eth"),
Recipient::Ens("vitalik.eth".into())
);
}
#[test]
fn parse_plain_string_is_inbox_id() {
assert_eq!(
Recipient::parse("abc123deadbeef"),
Recipient::InboxId("abc123deadbeef".into())
);
}
#[test]
fn from_account_identifier_ethereum() {
use crate::types::{AccountIdentifier, IdentifierKind};
let id = AccountIdentifier {
address: "0xabc".into(),
kind: IdentifierKind::Ethereum,
};
assert_eq!(Recipient::from(id), Recipient::Address("0xabc".into()));
}
#[test]
fn from_account_identifier_passkey() {
use crate::types::{AccountIdentifier, IdentifierKind};
let id = AccountIdentifier {
address: "pk_123".into(),
kind: IdentifierKind::Passkey,
};
assert_eq!(Recipient::from(id), Recipient::InboxId("pk_123".into()));
}
}