From ed84a41e5709f8035ac893a9572638e1ace085ae Mon Sep 17 00:00:00 2001
From: Simon Bernier St-Pierre <sbernierstpierre@gmail.com>
Date: Wed, 31 Dec 2014 18:39:00 -0500
Subject: [PATCH] Implement ToMessage for Command and Message and use ToMessage
 in Connection.

---
 src/conn.rs         | 20 +++++++++++---------
 src/data/command.rs | 44 ++++++++++++++++++++++++--------------------
 src/data/message.rs | 16 ++++++++++++++++
 src/server/mod.rs   | 23 ++++++++++++-----------
 4 files changed, 63 insertions(+), 40 deletions(-)

diff --git a/src/conn.rs b/src/conn.rs
index 67b744c..fcb2e73 100644
--- a/src/conn.rs
+++ b/src/conn.rs
@@ -6,7 +6,7 @@ use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream};
 #[cfg(feature = "encode")] use encoding::{DecoderTrap, EncoderTrap, Encoding};
 #[cfg(feature = "encode")] use encoding::label::encoding_from_whatwg_label;
 use data::kinds::{IrcReader, IrcWriter};
-use data::message::Message;
+use data::message::ToMessage;
 #[cfg(feature = "ssl")] use openssl::ssl::{SslContext, SslMethod, SslStream};
 #[cfg(feature = "ssl")] use openssl::ssl::error::SslError;
 
@@ -26,7 +26,7 @@ type NetReaderWriterPair = (BufferedReader<NetStream>, BufferedWriter<NetStream>
 impl Connection<BufferedReader<NetStream>, BufferedWriter<NetStream>> {
     /// Creates a thread-safe TCP connection to the specified server.
     #[experimental]
-    pub fn connect(host: &str, port: u16) -> IoResult<NetConnection> {  
+    pub fn connect(host: &str, port: u16) -> IoResult<NetConnection> {
         let (reader, writer) = try!(Connection::connect_internal(host, port));
         Ok(Connection::new(reader, writer))
     }
@@ -52,7 +52,7 @@ impl Connection<BufferedReader<NetStream>, BufferedWriter<NetStream>> {
         let socket = try!(TcpStream::connect(format!("{}:{}", host, port)[]));
         let ssl = try!(ssl_to_io(SslContext::new(SslMethod::Tlsv1)));
         let ssl_socket = try!(ssl_to_io(SslStream::new(&ssl, socket)));
-        Ok((BufferedReader::new(NetStream::SslTcpStream(ssl_socket.clone())), 
+        Ok((BufferedReader::new(NetStream::SslTcpStream(ssl_socket.clone())),
             BufferedWriter::new(NetStream::SslTcpStream(ssl_socket))))
     }
 
@@ -78,13 +78,13 @@ impl Connection<BufferedReader<NetStream>, BufferedWriter<NetStream>> {
         *self.writer.lock() = writer;
         Ok(())
     }
-    
+
     /// Sets the keepalive for the network stream.
     #[experimental]
     pub fn set_keepalive(&self, delay_in_seconds: Option<uint>) -> IoResult<()> {
         self.mod_stream(|tcp| tcp.set_keepalive(delay_in_seconds))
     }
-    
+
     /// Sets the timeout for the network stream.
     #[experimental]
     pub fn set_timeout(&self, timeout_ms: Option<u64>) {
@@ -114,7 +114,8 @@ impl<T: IrcReader, U: IrcWriter> Connection<T, U> {
     /// Sends a Message over this connection.
     #[experimental]
     #[cfg(feature = "encode")]
-    pub fn send(&self, message: Message, encoding: &str) -> IoResult<()> {
+    pub fn send<T: ToMessage>(&self, tomsg: T, encoding: &str) -> IoResult<()> {
+        let message = tomsg.to_message();
         let encoding = match encoding_from_whatwg_label(encoding) {
             Some(enc) => enc,
             None => return Err(IoError {
@@ -139,7 +140,8 @@ impl<T: IrcReader, U: IrcWriter> Connection<T, U> {
     /// Sends a message over this connection.
     #[experimental]
     #[cfg(not(feature = "encode"))]
-    pub fn send(&self, message: Message) -> IoResult<()> {
+    pub fn send<T: ToMessage>(&self, tomsg: T) -> IoResult<()> {
+        let message = tomsg.to_message();
         let mut writer = self.writer.lock();
         try!(writer.write_str(message.into_string()[]));
         writer.flush()
@@ -181,7 +183,7 @@ impl<T: IrcReader, U: IrcWriter> Connection<T, U> {
     pub fn reader<'a>(&'a self) -> MutexGuard<'a, T> {
         self.reader.lock()
     }
-    
+
     /// Acquires the Writer lock.
     #[experimental]
     pub fn writer<'a>(&'a self) -> MutexGuard<'a, U> {
@@ -261,7 +263,7 @@ mod test {
             Message::new(None, "PRIVMSG", Some(vec!["test"]), Some("€ŠšŽžŒœŸ")), "UTF-8"
         ).is_ok());
         let data = UTF_8.decode(conn.writer().get_ref(), DecoderTrap::Strict).unwrap();
-        assert_eq!(data[], "PRIVMSG test :€ŠšŽžŒœŸ\r\n");  
+        assert_eq!(data[], "PRIVMSG test :€ŠšŽžŒœŸ\r\n");
     }
 
     #[test]
diff --git a/src/data/command.rs b/src/data/command.rs
index a88f3b3..94ce31e 100644
--- a/src/data/command.rs
+++ b/src/data/command.rs
@@ -2,10 +2,10 @@
 #![stable]
 use std::io::{InvalidInput, IoError, IoResult};
 use std::str::FromStr;
-use data::message::Message;
+use data::message::{Message, ToMessage};
 
-/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This 
-/// also includes commands from the 
+/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This
+/// also includes commands from the
 /// [capabilities extension](https://tools.ietf.org/html/draft-mitchell-irc-capabilities-01).
 /// Additionally, this includes some common additional commands from popular IRCds.
 #[stable]
@@ -144,17 +144,18 @@ pub enum Command<'a> {
     HOSTSERV(&'a str),
     /// MEMOSERV message
     MEMOSERV(&'a str),
-    
+
     // Capabilities extension to IRCv3
     /// CAP COMMAND [param]
     CAP(CapSubCommand, Option<&'a str>),
 }
 
-impl<'a> Command<'a> {
+impl<'a> ToMessage for Command<'a> {
+
     /// Converts a Command into a Message.
     #[stable]
-    pub fn to_message(self) -> Message {
-        match self {
+    fn to_message(&self) -> Message {
+        match *self {
             Command::PASS(p) => Message::new(None, "PASS", None, Some(p)),
             Command::NICK(n) => Message::new(None, "NICK", None, Some(n)),
             Command::USER(u, m, r) => Message::new(None, "USER", Some(vec![u, m, "*"]), Some(r)),
@@ -162,7 +163,7 @@ impl<'a> Command<'a> {
             Command::MODE(t, m, Some(p)) => Message::new(None, "MODE", Some(vec![t, m, p]), None),
             Command::MODE(t, m, None) => Message::new(None, "MODE", Some(vec![t, m]), None),
             Command::SERVICE(n, r, d, t, re, i) => Message::new(None, "SERVICE",
-                                                   Some(vec![n, r, d, t, re]), Some(i)),
+            Some(vec![n, r, d, t, re]), Some(i)),
             Command::QUIT(Some(m)) => Message::new(None, "QUIT", None, Some(m)),
             Command::QUIT(None) => Message::new(None, "QUIT", None, None),
             Command::SQUIT(s, c) => Message::new(None, "SQUIT", Some(vec![s]), Some(c)),
@@ -186,7 +187,7 @@ impl<'a> Command<'a> {
             Command::MOTD(Some(t)) => Message::new(None, "MOTD", None, Some(t)),
             Command::MOTD(None) => Message::new(None, "MOTD", None, None),
             Command::LUSERS(Some(m), Some(t)) => Message::new(None, "LUSERS", Some(vec![m]),
-                                                 Some(t)),
+            Some(t)),
             Command::LUSERS(Some(m), None) => Message::new(None, "LUSERS", Some(vec![m]), None),
             Command::LUSERS(None, _) => Message::new(None, "LUSERS", None, None),
             Command::VERSION(Some(t)) => Message::new(None, "VERSION", None, Some(t)),
@@ -200,7 +201,7 @@ impl<'a> Command<'a> {
             Command::TIME(Some(t)) => Message::new(None, "TIME", None, Some(t)),
             Command::TIME(None) => Message::new(None, "TIME", None, None),
             Command::CONNECT(t, p, Some(r)) => Message::new(None, "CONNECT", Some(vec![t, p]),
-                                               Some(r)),
+            Some(r)),
             Command::CONNECT(t, p, None) => Message::new(None, "CONNECT", Some(vec![t, p]), None),
             Command::TRACE(Some(t)) => Message::new(None, "TRACE", None, Some(t)),
             Command::TRACE(None) => Message::new(None, "TRACE", None, None),
@@ -209,20 +210,20 @@ impl<'a> Command<'a> {
             Command::INFO(Some(t)) => Message::new(None, "INFO", None, Some(t)),
             Command::INFO(None) => Message::new(None, "INFO", None, None),
             Command::SERVLIST(Some(m), Some(t)) => Message::new(None, "SERVLIST", Some(vec![m]),
-                                                   Some(t)),
+            Some(t)),
             Command::SERVLIST(Some(m), None) => Message::new(None, "SERVLIST", Some(vec![m]), None),
             Command::SERVLIST(None, _) => Message::new(None, "SERVLIST", None, None),
             Command::SQUERY(s, t) => Message::new(None, "SQUERY", Some(vec![s, t]), None),
             Command::WHO(Some(s), Some(true)) => Message::new(None, "WHO", Some(vec![s, "o"]),
-                                                 None),
+            None),
             Command::WHO(Some(s), _) => Message::new(None, "WHO", Some(vec![s]), None),
             Command::WHO(None, _) => Message::new(None, "WHO", None, None),
             Command::WHOIS(Some(t), m) => Message::new(None, "WHOIS", Some(vec![t, m]), None),
             Command::WHOIS(None, m) => Message::new(None, "WHOIS", Some(vec![m]), None),
             Command::WHOWAS(n, Some(c), Some(t)) => Message::new(None, "WHOWAS", Some(vec![n, c]),
-                                                    Some(t)),
+            Some(t)),
             Command::WHOWAS(n, Some(c), None) => Message::new(None, "WHOWAS", Some(vec![n, c]),
-                                                 None),
+            None),
             Command::WHOWAS(n, None, _) => Message::new(None, "WHOWAS", Some(vec![n]), None),
             Command::KILL(n, c) => Message::new(None, "KILL", Some(vec![n]), Some(c)),
             Command::PING(s, Some(t)) => Message::new(None, "PING", Some(vec![s]), Some(t)),
@@ -236,18 +237,18 @@ impl<'a> Command<'a> {
             Command::DIE => Message::new(None, "DIE", None, None),
             Command::RESTART => Message::new(None, "RESTART", None, None),
             Command::SUMMON(u, Some(t), Some(c)) => Message::new(None, "SUMMON", Some(vec![u, t]),
-                                                    Some(c)),
+            Some(c)),
             Command::SUMMON(u, Some(t), None) => Message::new(None, "SUMMON", Some(vec![u, t]),
-                                                 None),
+            None),
             Command::SUMMON(u, None, _) => Message::new(None, "SUMMON", Some(vec![u]), None),
             Command::USERS(Some(t)) => Message::new(None, "USERS", None, Some(t)),
             Command::USERS(None) => Message::new(None, "USERS", None, None),
             Command::WALLOPS(t) => Message::new(None, "WALLOPS", None, Some(t)),
-            Command::USERHOST(u) => Message::new(None, "USERHOST", Some(u), None),
-            Command::ISON(u) => Message::new(None, "ISON", Some(u), None),
+            Command::USERHOST(ref u) => Message::new(None, "USERHOST", Some(u.clone()), None),
+            Command::ISON(ref u) => Message::new(None, "ISON", Some(u.clone()), None),
             Command::SAJOIN(n, c) => Message::new(None, "SAJOIN", Some(vec![n, c]), None),
             Command::SAMODE(t, m, Some(p)) => Message::new(None, "SAMODE", Some(vec![t, m, p]),
-                                              None),
+            None),
             Command::SAMODE(t, m, None) => Message::new(None, "SAMODE", Some(vec![t, m]), None),
             Command::SANICK(o, n) => Message::new(None, "SANICK", Some(vec![o, n]), None),
             Command::SAPART(c, r) => Message::new(None, "SAPART", Some(vec![c]), Some(r)),
@@ -262,6 +263,9 @@ impl<'a> Command<'a> {
         }
     }
 
+}
+
+impl<'a> Command<'a> {
     /// Converts a Message into a Command.
     #[stable]
     pub fn from_message(m: &'a Message) -> IoResult<Command<'a>> {
@@ -981,7 +985,7 @@ impl CapSubCommand {
     }
 }
 
-impl FromStr for CapSubCommand { 
+impl FromStr for CapSubCommand {
     fn from_str(s: &str) -> Option<CapSubCommand> {
         match s {
             "LS"    => Some(CapSubCommand::LS),
diff --git a/src/data/message.rs b/src/data/message.rs
index 74de5d8..0669df2 100644
--- a/src/data/message.rs
+++ b/src/data/message.rs
@@ -3,6 +3,14 @@
 use std::borrow::ToOwned;
 use std::str::FromStr;
 
+/// Represents something that can be converted to a message.
+pub trait ToMessage {
+
+    /// Convert to message.
+    fn to_message(&self) -> Message;
+
+}
+
 /// IRC Message data.
 #[experimental]
 #[deriving(Clone, PartialEq, Show)]
@@ -54,6 +62,14 @@ impl Message {
     }
 }
 
+impl ToMessage for Message {
+
+    fn to_message(&self) -> Message {
+        self.clone()
+    }
+
+}
+
 impl FromStr for Message {
     fn from_str(s: &str) -> Option<Message> {
         let mut state = s.clone();
diff --git a/src/server/mod.rs b/src/server/mod.rs
index c014b66..1e55da9 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -8,6 +8,7 @@ use conn::{Connection, NetStream};
 use data::{Command, Config, Message, Response, User};
 use data::Command::{JOIN, NICK, NICKSERV, PONG};
 use data::kinds::{IrcReader, IrcWriter};
+use data::message::ToMessage;
 #[cfg(feature = "ctcp")] use time::now;
 
 pub mod utils;
@@ -42,7 +43,7 @@ pub struct IrcServer<T: IrcReader, U: IrcWriter> {
 pub type NetIrcServer = IrcServer<BufferedReader<NetStream>, BufferedWriter<NetStream>>;
 
 impl IrcServer<BufferedReader<NetStream>, BufferedWriter<NetStream>> {
-    /// Creates a new IRC Server connection from the configuration at the specified path, 
+    /// Creates a new IRC Server connection from the configuration at the specified path,
     /// connecting immediately.
     #[experimental]
     pub fn new(config: &str) -> IoResult<NetIrcServer> {
@@ -65,7 +66,7 @@ impl IrcServer<BufferedReader<NetStream>, BufferedWriter<NetStream>> {
     /// Reconnects to the IRC server.
     #[experimental]
     pub fn reconnect(&self) -> IoResult<()> {
-        self.conn.reconnect(self.config().server(), self.config.port()) 
+        self.conn.reconnect(self.config().server(), self.config.port())
     }
 }
 
@@ -133,7 +134,7 @@ impl<T: IrcReader, U: IrcWriter> IrcServer<T, U> {
                 for chan in self.config.channels().into_iter() {
                     self.send(JOIN(chan[], None)).unwrap();
                 }
-            } else if resp == Response::ERR_NICKNAMEINUSE || 
+            } else if resp == Response::ERR_NICKNAMEINUSE ||
                       resp == Response::ERR_ERRONEOUSNICKNAME {
                 let alt_nicks = self.config.get_alternate_nicknames();
                 let mut index = self.alt_nick_index.write();
@@ -190,7 +191,7 @@ impl<T: IrcReader, U: IrcWriter> IrcServer<T, U> {
             let resp = if target.starts_with("#") { target[] } else { source };
             match msg.suffix {
                 Some(ref msg) if msg.starts_with("\u{001}") => {
-                    let tokens: Vec<_> = { 
+                    let tokens: Vec<_> = {
                         let end = if msg.ends_with("\u{001}") {
                             msg.len() - 1
                         } else {
@@ -199,9 +200,9 @@ impl<T: IrcReader, U: IrcWriter> IrcServer<T, U> {
                         msg[1..end].split_str(" ").collect()
                     };
                     println!("we made it this far.");
-                    match tokens[0] { 
-                        "FINGER" => self.send_ctcp(resp, format!("FINGER :{} ({})", 
-                                                                 self.config.real_name(), 
+                    match tokens[0] {
+                        "FINGER" => self.send_ctcp(resp, format!("FINGER :{} ({})",
+                                                                 self.config.real_name(),
                                                                  self.config.username())[]),
                         "VERSION" => self.send_ctcp(resp, "VERSION irc:git:Rust"),
                         "SOURCE" => {
@@ -212,7 +213,7 @@ impl<T: IrcReader, U: IrcWriter> IrcServer<T, U> {
                         },
                         "PING" => self.send_ctcp(resp, format!("PING {}", tokens[1])[]),
                         "TIME" => self.send_ctcp(resp, format!("TIME :{}", now().rfc822z())[]),
-                        "USERINFO" => self.send_ctcp(resp, format!("USERINFO :{}", 
+                        "USERINFO" => self.send_ctcp(resp, format!("USERINFO :{}",
                                                                    self.config.user_info())[]),
                         _ => {}
                     }
@@ -276,7 +277,7 @@ impl<'a, T: IrcReader, U: IrcWriter> Iterator<IoResult<Message>> for ServerItera
                     desc: "Failed to parse message.",
                     detail: Some(msg)
                 })
-            }   
+            }
         );
         match res {
             Err(ref err) if err.kind == IoErrorKind::EndOfFile => None,
@@ -451,7 +452,7 @@ mod test {
         // but ignores the ordering of these entries.
         let mut levels = server.list_users("#test").unwrap()[0].access_levels();
         levels.retain(|l| exp.access_levels().contains(l));
-        assert_eq!(levels.len(), exp.access_levels().len()); 
+        assert_eq!(levels.len(), exp.access_levels().len());
     }
 
     #[test]
@@ -492,7 +493,7 @@ mod test {
         for message in server.iter() {
             println!("{}", message);
         }
-        assert_eq!(get_server_value(server)[], 
+        assert_eq!(get_server_value(server)[],
         "NOTICE test :\u{001}SOURCE https://github.com/aatxe/irc\u{001}\r\n\
          NOTICE test :\u{001}SOURCE\u{001}\r\n");
     }