use std::fmt;
use std::str;
use std::hash::{Hash, Hasher};
use std::cell::RefCell;
use quickcheck::{Arbitrary, Gen};
use rfc2822::{NameAddr, AddrSpec};
use Result;
use packet;
use Packet;
pub struct UserID {
pub(crate) common: packet::Common,
value: Vec<u8>,
parsed: RefCell<Option<(Option<String>, Option<String>, Option<String>)>>,
}
impl From<Vec<u8>> for UserID {
fn from(u: Vec<u8>) -> Self {
UserID {
common: Default::default(),
value: u,
parsed: RefCell::new(None),
}
}
}
impl<'a> From<&'a str> for UserID {
fn from(u: &'a str) -> Self {
let b = u.as_bytes();
let mut v = Vec::with_capacity(b.len());
v.extend_from_slice(b);
v.into()
}
}
impl fmt::Display for UserID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let userid = String::from_utf8_lossy(&self.value[..]);
write!(f, "{}", userid)
}
}
impl fmt::Debug for UserID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let userid = String::from_utf8_lossy(&self.value[..]);
f.debug_struct("UserID")
.field("value", &userid)
.finish()
}
}
impl PartialEq for UserID {
fn eq(&self, other: &UserID) -> bool {
self.common == other.common
&& self.value == other.value
}
}
impl Eq for UserID {
}
impl Hash for UserID {
fn hash<H: Hasher>(&self, state: &mut H) {
self.common.hash(state);
self.value.hash(state);
}
}
impl Clone for UserID {
fn clone(&self) -> Self {
UserID {
common: self.common.clone(),
value: self.value.clone(),
parsed: RefCell::new(None),
}
}
}
impl UserID {
pub fn value(&self) -> &[u8] {
self.value.as_slice()
}
fn do_parse(&self) -> Result<()> {
if self.parsed.borrow().is_none() {
let s = str::from_utf8(&self.value)?;
*self.parsed.borrow_mut() = Some(match NameAddr::parse(s) {
Ok(na) => (na.name().map(|s| s.to_string()),
na.comment().map(|s| s.to_string()),
na.address().map(|s| s.to_string())),
Err(err) => {
if let Ok(a) = AddrSpec::parse(s) {
(None, None, Some(a.address().to_string()))
} else {
return Err(err.into());
}
}
});
}
Ok(())
}
pub fn name(&self) -> Result<Option<String>> {
self.do_parse()?;
match *self.parsed.borrow() {
Some((ref name, ref _comment, ref _address)) =>
Ok(name.as_ref().map(|s| s.clone())),
None => unreachable!(),
}
}
pub fn comment(&self) -> Result<Option<String>> {
self.do_parse()?;
match *self.parsed.borrow() {
Some((ref _name, ref comment, ref _address)) =>
Ok(comment.as_ref().map(|s| s.clone())),
None => unreachable!(),
}
}
pub fn address(&self) -> Result<Option<String>> {
self.do_parse()?;
match *self.parsed.borrow() {
Some((ref _name, ref _comment, ref address)) =>
Ok(address.as_ref().map(|s| s.clone())),
None => unreachable!(),
}
}
}
impl From<UserID> for Packet {
fn from(s: UserID) -> Self {
Packet::UserID(s)
}
}
impl Arbitrary for UserID {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
Vec::<u8>::arbitrary(g).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use parse::Parse;
use serialize::SerializeInto;
quickcheck! {
fn roundtrip(p: UserID) -> bool {
let q = UserID::from_bytes(&p.to_vec().unwrap()).unwrap();
assert_eq!(p, q);
true
}
}
#[test]
fn name_addr() {
fn c(value: &str, ok: bool,
name: Option<&str>, comment: Option<&str>, address: Option<&str>)
{
let name = name.map(|s| s.to_string());
let comment = comment.map(|s| s.to_string());
let address = address.map(|s| s.to_string());
let u = UserID::from(value);
for _ in 0..2 {
match u.name() {
Ok(ref v) if ok =>
assert_eq!(v, &name),
Ok(_) if !ok =>
panic!("Expected parse to fail."),
Err(ref err) if ok =>
panic!("Expected parse to succeed: {:?}", err),
Err(_) if !ok =>
(),
_ => unreachable!(),
};
match u.comment() {
Ok(ref v) if ok =>
assert_eq!(v, &comment),
Ok(_) if !ok =>
panic!("Expected parse to fail."),
Err(ref err) if ok =>
panic!("Expected parse to succeed: {:?}", err),
Err(_) if !ok =>
(),
_ => unreachable!(),
};
match u.address() {
Ok(ref v) if ok =>
assert_eq!(v, &address),
Ok(_) if !ok =>
panic!("Expected parse to fail."),
Err(ref err) if ok =>
panic!("Expected parse to succeed: {:?}", err),
Err(_) if !ok =>
(),
_ => unreachable!(),
};
}
}
c("Henry Ford (CEO) <henry@ford.com>", true,
Some("Henry Ford"), Some("CEO"), Some("henry@ford.com"));
c("Thomas \"Tomakin\" (DHC) <thomas@clh.co.uk>", true,
Some("Thomas Tomakin"), Some("DHC"), Some("thomas@clh.co.uk"));
c("Aldous L. Huxley <huxley@old-world.org>", true,
Some("Aldous L. Huxley"), None, Some("huxley@old-world.org"));
c("huxley@old-world.org", true,
None, None, Some("huxley@old-world.org"));
c("\"<loki@bar.com>\" <foo@bar.com>", true,
Some("<loki@bar.com>"), None, Some("foo@bar.com"));
c("<huxley@@old-world.org>", false, None, None, None);
c("huxley@@old-world.org", false, None, None, None);
c("huxley@old-world.org.", false, None, None, None);
c("@old-world.org", false, None, None, None);
}
}