1use std::fmt;
4
5#[derive(Clone, Debug)]
6pub struct User {
7 pub names: Vec<String>,
8 pub emails: Vec<String>,
9 group_email: Option<String>,
10}
11
12impl User {
13 pub fn new(name: impl Into<String>, email: impl Into<String>) -> Self {
14 User {
15 names: vec![name.into()],
16 emails: vec![email.into()],
17 group_email: None,
18 }
19 }
20
21 pub fn parse(s: &str) -> Result<Self, ParseError> {
23 let s = s.trim();
24 if let Some(open) = s.find('<') {
25 if let Some(close) = s.find('>') {
26 if close > open && close == s.len() - 1 {
27 let name = s[..open].trim().to_string();
28 let email = s[open + 1..close].trim().to_string();
29 if !name.is_empty() && !email.is_empty() {
30 return Ok(User::new(name, email));
31 }
32 }
33 }
34 }
35 Err(ParseError {
36 input: s.to_string(),
37 })
38 }
39
40 pub fn none() -> User {
41 User {
42 names: vec![],
43 emails: vec![],
44 group_email: None,
45 }
46 }
47
48 pub fn name(&self) -> String {
49 if self.is_none() {
50 return "(none)".to_string();
51 }
52 self.names.join(" and ")
53 }
54
55 pub fn email(&self) -> String {
56 if self.emails.is_empty() {
57 return String::new();
58 }
59 if self.emails.len() == 1 {
60 return self.emails[0].clone();
61 }
62 let group = self.group_email.as_deref().unwrap_or("dev@example.com");
63 let group_prefix = group.split('@').next().unwrap_or("dev");
64 let group_domain = group.split('@').nth(1).unwrap_or("example.com");
65 let prefixes: Vec<&str> = self
66 .emails
67 .iter()
68 .map(|e| e.split('@').next().unwrap_or(""))
69 .collect();
70 format!("{}+{}@{}", group_prefix, prefixes.join("+"), group_domain)
71 }
72
73 pub fn initials(&self) -> String {
74 self.names
75 .join(" ")
76 .split_whitespace()
77 .filter_map(|w| w.chars().next())
78 .collect::<String>()
79 .to_lowercase()
80 }
81
82 pub fn is_none(&self) -> bool {
83 self.names.is_empty() && self.emails.is_empty()
84 }
85
86 pub fn combine(mut self, other: &User, group_email: &str) -> User {
87 if self.is_none() {
88 return other.clone();
89 }
90 if other.is_none() {
91 return self;
92 }
93 self.names.extend(other.names.clone());
94 self.emails.extend(other.emails.clone());
95 self.group_email = Some(group_email.to_string());
96 self
97 }
98}
99
100impl PartialEq for User {
101 fn eq(&self, other: &Self) -> bool {
102 self.name() == other.name() && self.email() == other.email()
103 }
104}
105impl Eq for User {}
106
107impl fmt::Display for User {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "{} <{}>", self.name(), self.email())
110 }
111}
112
113#[derive(Debug)]
114pub struct ParseError {
115 pub input: String,
116}
117
118impl fmt::Display for ParseError {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(
121 f,
122 "Couldn't parse '{}' as user (expected user in format: 'Jane Doe <jane@example.com>')",
123 self.input
124 )
125 }
126}
127
128impl std::error::Error for ParseError {}