rust-irc/src/client/data/message.rs

206 lines
6.9 KiB
Rust
Raw Normal View History

//! Messages to and from the server.
use std::borrow::ToOwned;
use std::str::FromStr;
/// IRC Message data.
2015-02-03 19:11:33 +01:00
#[derive(Clone, PartialEq, Debug)]
2014-11-02 23:25:45 +01:00
pub struct Message {
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
2014-11-02 23:25:45 +01:00
pub prefix: Option<String>,
/// The IRC command as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
2014-11-02 23:25:45 +01:00
pub command: String,
/// The command arguments.
2014-11-02 23:25:45 +01:00
pub args: Vec<String>,
/// The message suffix as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
2014-11-03 06:52:15 +01:00
/// This is the only part of the message that is allowed to contain spaces.
2014-11-02 23:25:45 +01:00
pub suffix: Option<String>,
}
impl Message {
/// Creates a new Message.
2014-11-02 23:25:45 +01:00
pub fn new(prefix: Option<&str>, command: &str, args: Option<Vec<&str>>, suffix: Option<&str>)
-> Message {
Message {
prefix: prefix.map(|s| s.to_owned()),
command: command.to_owned(),
2014-12-23 19:31:10 +01:00
args: args.map_or(Vec::new(), |v| v.iter().map(|&s| s.to_owned()).collect()),
suffix: suffix.map(|s| s.to_owned()),
2014-11-02 23:25:45 +01:00
}
}
/// Creates a new Message from already owned data.
pub fn from_owned(prefix: Option<String>, command: String, args: Option<Vec<String>>,
suffix: Option<String>) -> Message {
Message {
prefix: prefix, command: command, args: args.unwrap_or(Vec::new()), suffix: suffix
}
}
/// Gets the nickname of the message source, if it exists.
pub fn get_source_nickname(&self) -> Option<&str> {
2015-01-09 23:38:46 +01:00
self.prefix.as_ref().and_then(|s| s.find('!').map(|i| &s[..i]))
}
/// Converts a Message into a String according to the IRC protocol.
2014-11-02 23:25:45 +01:00
pub fn into_string(&self) -> String {
let mut ret = String::new();
if let Some(ref prefix) = self.prefix {
ret.push(':');
2015-02-21 15:28:12 +01:00
ret.push_str(&prefix);
2014-11-02 23:25:45 +01:00
ret.push(' ');
}
2015-02-21 15:28:12 +01:00
ret.push_str(&self.command);
2014-11-02 23:25:45 +01:00
for arg in self.args.iter() {
ret.push(' ');
2015-02-21 15:28:12 +01:00
ret.push_str(&arg);
2014-11-02 23:25:45 +01:00
}
if let Some(ref suffix) = self.suffix {
ret.push_str(" :");
2015-02-21 15:28:12 +01:00
ret.push_str(&suffix);
2014-11-02 23:25:45 +01:00
}
ret.push_str("\r\n");
2014-11-02 23:25:45 +01:00
ret
}
}
impl FromStr for Message {
2015-02-03 19:11:33 +01:00
type Err = &'static str;
fn from_str(s: &str) -> Result<Message, &'static str> {
let mut state = s.clone();
2015-02-03 19:11:33 +01:00
if s.len() == 0 { return Err("Cannot parse an empty string as a message.") }
let prefix = if state.starts_with(":") {
2015-01-09 23:38:46 +01:00
let prefix = state.find(' ').map(|i| &state[1..i]);
state = state.find(' ').map_or("", |i| &state[i+1..]);
prefix
} else {
None
};
let suffix = if state.contains(" :") {
let suffix = state.find(" :").map(|i| &state[i+2..state.len()-2]);
state = state.find(" :").map_or("", |i| &state[..i+1]);
suffix
} else {
None
};
2015-01-09 23:38:46 +01:00
let command = match state.find(' ').map(|i| &state[..i]) {
Some(cmd) => {
2015-01-09 23:38:46 +01:00
state = state.find(' ').map_or("", |i| &state[i+1..]);
cmd
}
2015-02-03 19:11:33 +01:00
_ => return Err("Cannot parse a message without a command.")
};
2015-01-09 23:38:46 +01:00
if suffix.is_none() { state = &state[..state.len() - 2] }
let args: Vec<_> = state.splitn(14, ' ').filter(|s| s.len() != 0).collect();
2015-02-03 19:11:33 +01:00
Ok(Message::new(prefix, command, if args.len() > 0 { Some(args) } else { None }, suffix))
}
}
2015-04-26 06:01:33 +02:00
impl<'a> From<&'a str> for Message {
fn from(s: &'a str) -> Message {
s.parse().unwrap()
}
}
#[cfg(test)]
mod test {
2015-04-26 06:11:51 +02:00
use super::Message;
#[test]
fn new() {
let message = Message {
prefix: None,
command: format!("PRIVMSG"),
args: vec![format!("test")],
suffix: Some(format!("Testing!")),
};
assert_eq!(Message::new(None, "PRIVMSG", Some(vec!["test"]), Some("Testing!")), message);
}
#[test]
fn get_source_nickname() {
assert_eq!(Message::new(None, "PING", None, None).get_source_nickname(), None);
assert_eq!(Message::new(
Some("irc.test.net"), "PING", None, None
).get_source_nickname(), None);
assert_eq!(Message::new(
Some("test!test@test"), "PING", None, None
).get_source_nickname(), Some("test"));
}
#[test]
fn into_string() {
let message = Message {
prefix: None,
command: format!("PRIVMSG"),
args: vec![format!("test")],
suffix: Some(format!("Testing!")),
};
2015-02-21 16:31:46 +01:00
assert_eq!(&message.into_string()[..], "PRIVMSG test :Testing!\r\n");
let message = Message {
prefix: Some(format!("test!test@test")),
command: format!("PRIVMSG"),
args: vec![format!("test")],
suffix: Some(format!("Still testing!")),
};
2015-02-21 16:31:46 +01:00
assert_eq!(&message.into_string()[..], ":test!test@test PRIVMSG test :Still testing!\r\n");
}
#[test]
fn from_string() {
let message = Message {
prefix: None,
command: format!("PRIVMSG"),
args: vec![format!("test")],
suffix: Some(format!("Testing!")),
};
2015-02-03 19:11:33 +01:00
assert_eq!("PRIVMSG test :Testing!\r\n".parse(), Ok(message));
let message = Message {
prefix: Some(format!("test!test@test")),
command: format!("PRIVMSG"),
args: vec![format!("test")],
suffix: Some(format!("Still testing!")),
};
2015-02-03 19:11:33 +01:00
assert_eq!(":test!test@test PRIVMSG test :Still testing!\r\n".parse(), Ok(message));
}
#[test]
fn to_message() {
let message = Message {
prefix: None,
command: format!("PRIVMSG"),
args: vec![format!("test")],
suffix: Some(format!("Testing!")),
};
2015-04-26 06:11:51 +02:00
let msg: Message = "PRIVMSG test :Testing!\r\n".into();
assert_eq!(msg, message);
let message = Message {
prefix: Some(format!("test!test@test")),
command: format!("PRIVMSG"),
args: vec![format!("test")],
suffix: Some(format!("Still testing!")),
};
2015-04-26 06:11:51 +02:00
let msg: Message = ":test!test@test PRIVMSG test :Still testing!\r\n".into();
assert_eq!(msg, message);
}
#[test]
fn to_message_with_colon_in_arg() {
// Apparently, UnrealIRCd (and perhaps some others) send some messages that include
// colons within individual parameters. So, let's make sure it parses correctly.
let message = Message {
prefix: Some(format!("test!test@test")),
command: format!("COMMAND"),
args: vec![format!("ARG:test")],
2015-04-26 06:11:51 +02:00
suffix: Some(format!("Testing!")),
};
2015-04-26 06:11:51 +02:00
let msg: Message = ":test!test@test COMMAND ARG:test :Testing!\r\n".into();
assert_eq!(msg, message);
}
#[test]
2015-03-22 04:08:41 +01:00
#[should_panic]
fn to_message_invalid_format() {
2015-04-26 06:11:51 +02:00
let _: Message = ":invalid :message".into();
}
}