use {
crate::code::Code,
std::{fmt, str::FromStr},
};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum ParseError {
EmptyCommand,
EmptyMessage,
UnexpectedEnd,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match *self {
ParseError::EmptyCommand => "String was empty",
ParseError::EmptyMessage => "Message did not have a code",
ParseError::UnexpectedEnd => "Unexpected end of the string",
}
)
}
}
impl std::error::Error for ParseError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Message {
pub prefix: Option<Prefix>,
pub code: Code,
pub args: Vec<String>,
}
impl Message {
pub fn parse(line: &str) -> Result<Message, ParseError> {
if line.is_empty() || line.trim().is_empty() {
return Err(ParseError::EmptyMessage);
}
let mut state = line.trim_end_matches("\r\n");
let mut prefix: Option<Prefix> = None;
let code: Option<&str>;
let mut args: Vec<String> = Vec::new();
if state.starts_with(':') {
match state.find(' ') {
None => return Err(ParseError::UnexpectedEnd),
Some(idx) => {
prefix = parse_prefix(&state[1..idx]);
state = &state[idx + 1..];
}
}
}
match state.find(' ') {
None => {
if state.is_empty() {
return Err(ParseError::EmptyMessage);
} else {
code = Some(&state[..]);
state = &state[state.len()..];
}
}
Some(idx) => {
code = Some(state[..idx].into());
state = &state[idx + 1..];
}
}
if !state.is_empty() {
loop {
if state.starts_with(':') {
args.push(state[1..].into());
break;
} else {
match state.find(' ') {
None => {
args.push(state[..].into());
break;
}
Some(idx) => {
args.push(state[..idx].into());
state = &state[idx + 1..];
}
}
}
}
}
let code = match code {
None => return Err(ParseError::EmptyCommand),
Some(text) => match text.parse() {
Ok(code) => code,
Err(_) => Code::Unknown(text.into()),
},
};
Ok(Message { prefix, code, args })
}
}
impl FromStr for Message {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Message::parse(s)
}
}
fn parse_prefix(prefix: &str) -> Option<Prefix> {
match prefix.find('!') {
None => Some(Prefix::Server(prefix.to_string())),
Some(excpos) => {
let nick = &prefix[..excpos];
let rest = &prefix[excpos + 1..];
match rest.find('@') {
None => None,
Some(atpos) => {
let user = &rest[..atpos];
let host = &rest[atpos + 1..];
Some(Prefix::User(PrefixUser::new(nick, user, host)))
}
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Prefix {
User(PrefixUser),
Server(String),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrefixUser {
pub nickname: String,
pub username: String,
pub hostname: String,
}
impl PrefixUser {
fn new(nick: &str, user: &str, host: &str) -> PrefixUser {
PrefixUser {
nickname: nick.into(),
username: user.into(),
hostname: host.into(),
}
}
}
#[test]
fn test_full() {
let res = Message::parse(":org.prefix.cool COMMAND arg1 arg2 arg3 :suffix is pretty cool yo");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(msg.code, Code::Unknown("COMMAND".to_string()));
assert_eq!(
msg.args,
vec!["arg1", "arg2", "arg3", "suffix is pretty cool yo"]
);
}
#[test]
fn test_no_prefix() {
let res = Message::parse("NICK arg1 arg2 arg3 :suffix is pretty cool yo");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(msg.prefix, None);
assert_eq!(msg.code, Code::Nick);
assert_eq!(
msg.args,
vec!["arg1", "arg2", "arg3", "suffix is pretty cool yo"]
);
}
#[test]
fn test_no_suffix() {
let res = Message::parse(":org.prefix.cool NICK arg1 arg2 arg3");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(msg.code, Code::Nick);
assert_eq!(msg.args, vec!["arg1", "arg2", "arg3"]);
}
#[test]
fn test_no_args() {
let res = Message::parse(":org.prefix.cool NICK :suffix is pretty cool yo");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(msg.code, Code::Nick);
assert_eq!(msg.args, vec!["suffix is pretty cool yo"]);
}
#[test]
fn test_only_command() {
let res = Message::parse("NICK");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(msg.prefix, None);
assert_eq!(msg.code, Code::Nick);
assert_eq!(msg.args.len(), 0);
}
#[test]
fn test_empty_message() {
let res = Message::parse("");
assert!(res.is_err());
let err = res.err().unwrap();
assert!(err == ParseError::EmptyMessage);
}
#[test]
fn test_empty_message_trim() {
let res = Message::parse(" ");
assert!(res.is_err());
let err = res.err().unwrap();
assert!(err == ParseError::EmptyMessage);
}
#[test]
fn test_only_prefix() {
let res = Message::parse(":org.prefix.cool");
assert!(res.is_err());
let err = res.err().unwrap();
assert!(err == ParseError::UnexpectedEnd);
}
#[test]
fn test_prefix_none() {
let res = Message::parse("COMMAND :suffix is pretty cool yo");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(msg.args, vec!["suffix is pretty cool yo"]);
}
#[test]
fn test_prefix_server() {
let res = Message::parse(":irc.freenode.net COMMAND :suffix is pretty cool yo");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(msg.prefix, Some(Prefix::Server("irc.freenode.net".into())));
}
#[test]
fn test_prefix_user() {
let res = Message::parse(":bob!bob@bob.com COMMAND :suffix is pretty cool yo");
assert!(res.is_ok());
let msg = res.ok().unwrap();
assert_eq!(
msg.prefix,
Some(Prefix::User(PrefixUser::new("bob", "bob", "bob.com")))
);
}