irc_proto/
prefix.rs

1//! A module providing an enum for a message prefix.
2use std::fmt;
3use std::str::FromStr;
4
5/// The Prefix indicates "the true origin of the message", according to the server.
6#[derive(Clone, Eq, PartialEq, Debug)]
7pub enum Prefix {
8    /// servername, e.g. collins.mozilla.org
9    ServerName(String),
10    /// nickname [ ["!" username] "@" hostname ]
11    /// i.e. Nickname(nickname, username, hostname)
12    /// Any of the strings may be ""
13    Nickname(String, String, String),
14}
15
16impl Prefix {
17    /// Creates a prefix by parsing a string.
18    ///
19    /// # Example
20    /// ```
21    /// # extern crate irc_proto;
22    /// # use irc_proto::Prefix;
23    /// # fn main() {
24    /// Prefix::new_from_str("nickname!username@hostname");
25    /// Prefix::new_from_str("example.com");
26    /// # }
27    /// ```
28    pub fn new_from_str(s: &str) -> Prefix {
29        #[derive(Copy, Clone, Eq, PartialEq)]
30        enum Active {
31            Name,
32            User,
33            Host,
34        }
35
36        let mut name = String::new();
37        let mut user = String::new();
38        let mut host = String::new();
39        let mut active = Active::Name;
40        let mut is_server = false;
41
42        for c in s.chars() {
43            if c == '.' && active == Active::Name {
44                // We won't return Nickname("nick", "", "") but if @ or ! are
45                // encountered, then we set this back to false
46                is_server = true;
47            }
48
49            match c {
50                '!' if active == Active::Name => {
51                    is_server = false;
52                    active = Active::User;
53                }
54
55                '@' if active != Active::Host => {
56                    is_server = false;
57                    active = Active::Host;
58                }
59
60                _ => {
61                    // Push onto the active buffer
62                    match active {
63                        Active::Name => &mut name,
64                        Active::User => &mut user,
65                        Active::Host => &mut host,
66                    }
67                    .push(c)
68                }
69            }
70        }
71
72        if is_server {
73            Prefix::ServerName(name)
74        } else {
75            Prefix::Nickname(name, user, host)
76        }
77    }
78}
79
80/// This implementation never returns an error and is isomorphic with `Display`.
81impl FromStr for Prefix {
82    type Err = ();
83
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        Ok(Prefix::new_from_str(s))
86    }
87}
88
89/// This is isomorphic with `FromStr`
90impl fmt::Display for Prefix {
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        match self {
93            Prefix::ServerName(name) => write!(f, "{}", name),
94            Prefix::Nickname(name, user, host) => match (&name[..], &user[..], &host[..]) {
95                ("", "", "") => write!(f, ""),
96                (name, "", "") => write!(f, "{}", name),
97                (name, user, "") => write!(f, "{}!{}", name, user),
98                (name, "", host) => write!(f, "{}@{}", name, host),
99                (name, user, host) => write!(f, "{}!{}@{}", name, user, host),
100            },
101        }
102    }
103}
104
105impl<'a> From<&'a str> for Prefix {
106    fn from(s: &str) -> Self {
107        Prefix::new_from_str(s)
108    }
109}
110
111#[cfg(test)]
112mod test {
113    use super::Prefix::{self, Nickname, ServerName};
114
115    // Checks that str -> parsed -> Display doesn't lose data
116    fn test_parse(s: &str) -> Prefix {
117        let prefix = Prefix::new_from_str(s);
118        let s2 = format!("{}", prefix);
119        assert_eq!(s, &s2);
120        prefix
121    }
122
123    #[test]
124    fn print() {
125        let s = format!("{}", Nickname("nick".into(), "".into(), "".into()));
126        assert_eq!(&s, "nick");
127        let s = format!("{}", Nickname("nick".into(), "user".into(), "".into()));
128        assert_eq!(&s, "nick!user");
129        let s = format!("{}", Nickname("nick".into(), "user".into(), "host".into()));
130        assert_eq!(&s, "nick!user@host");
131    }
132
133    #[test]
134    fn parse_word() {
135        assert_eq!(
136            test_parse("only_nick"),
137            Nickname("only_nick".into(), String::new(), String::new())
138        )
139    }
140
141    #[test]
142    fn parse_host() {
143        assert_eq!(test_parse("host.tld"), ServerName("host.tld".into()))
144    }
145
146    #[test]
147    fn parse_nick_user() {
148        assert_eq!(
149            test_parse("test!nick"),
150            Nickname("test".into(), "nick".into(), String::new())
151        )
152    }
153
154    #[test]
155    fn parse_nick_user_host() {
156        assert_eq!(
157            test_parse("test!nick@host"),
158            Nickname("test".into(), "nick".into(), "host".into())
159        )
160    }
161
162    #[test]
163    fn parse_dot_and_symbols() {
164        assert_eq!(
165            test_parse("test.net@something"),
166            Nickname("test.net".into(), "".into(), "something".into())
167        )
168    }
169
170    #[test]
171    fn parse_danger_cases() {
172        assert_eq!(
173            test_parse("name@name!user"),
174            Nickname("name".into(), "".into(), "name!user".into())
175        );
176        assert_eq!(
177            // can't reverse the parse
178            "name!@".parse::<Prefix>().unwrap(),
179            Nickname("name".into(), "".into(), "".into())
180        );
181        assert_eq!(
182            // can't reverse the parse
183            "name!@hostname".parse::<Prefix>().unwrap(),
184            Nickname("name".into(), "".into(), "hostname".into())
185        );
186        assert_eq!(
187            test_parse("name!.user"),
188            Nickname("name".into(), ".user".into(), "".into())
189        );
190        assert_eq!(
191            test_parse("name!user.user"),
192            Nickname("name".into(), "user.user".into(), "".into())
193        );
194        assert_eq!(
195            test_parse("name!user@host.host"),
196            Nickname("name".into(), "user".into(), "host.host".into())
197        );
198        assert_eq!(
199            test_parse("!user"),
200            Nickname("".into(), "user".into(), "".into())
201        );
202        assert_eq!(
203            "!@host.host".parse::<Prefix>().unwrap(),
204            Nickname("".into(), "".into(), "host.host".into())
205        );
206    }
207}