wurl 0.1.0

WebSocket CLI for developers
Documentation
use std::io::{Error, ErrorKind};
use std::collections::vec_deque::VecDeque;

#[derive(PartialEq, Debug)]
pub enum Kind {
    Message,
    Ping,
    Pong,
    Close,
}

#[derive(PartialEq, Debug)]
pub struct Message {
    pub kind: Kind,
    pub message: Option<String>,
    pub code: Option<u16>,
}

impl Message {
    fn from_message(message: String) -> Message {
        Message {
            kind: Kind::Message,
            code: None,
            message: Some(message),
        }
    }

    fn from_ping(input: String) -> Message {
        let suffix = input.replace("/ping", "");
        let message = suffix.trim();
        Message {
            kind: Kind::Ping,
            code: None,
            message: if !message.is_empty() {
                Some(message.to_owned())
            } else {
                None
            },
        }
    }

    fn from_pong(input: String) -> Message {
        let suffix = input.replace("/pong", "");
        let message = suffix.trim();
        Message {
            kind: Kind::Pong,
            code: None,
            message: if !message.is_empty() {
                Some(message.to_owned())
            } else {
                None
            },
        }
    }

    fn from_close(input: String) -> Message {
        let mut split = input.split_whitespace().collect::<VecDeque<&str>>();
        split.pop_front();
        let code = split.pop_front();
        let message = join_deque(&mut split);
        Message {
            kind: Kind::Close,
            code: if let Some(item) = code {
                Some(item.parse::<u16>().unwrap())
            } else {
                None
            },
            message: if !message.is_empty() {
                Some(message.to_owned())
            } else {
                None
            },
        }
    }
}

pub fn parse_message(message: String) -> Result<Message, Error> {
    if message.starts_with('/') {
        if message.starts_with("/ping") {
            return Ok(Message::from_ping(message));
        }

        if message.starts_with("/pong") {
            return Ok(Message::from_pong(message));
        }

        if message.starts_with("/close") {
            return Ok(Message::from_close(message));
        }

        return Err(
            Error::new(
                ErrorKind::InvalidInput,
                "Unrecognized command. If you meant to send this as a message, add a leading space to escape the command"
            )
        );
    }

    Ok(Message::from_message(message.trim().to_string()))
}

fn join_deque(deque: &mut VecDeque<&str>) -> String {
    let first = deque.pop_front();
    if first.is_none() {
        return String::new();
    }

    let mut result = String::from(first.unwrap());
    for item in deque.iter() {
        result = format!("{} {}", result, item);
    }

    String::from(result)
}

#[cfg(test)]
mod tests {
    use std::io::ErrorKind;
    use messages::{parse_message, Kind, Message};

    #[test]
    fn test_parse_message() {
        let mut actual = parse_message(String::from("Hello there"));
        assert!(actual.is_ok(), "Normal message did not return successfully");
        assert_eq!(
            Message {
                kind: Kind::Message,
                message: Some(String::from("Hello there")),
                code: None,
            },
            actual.unwrap(),
            "Normal message was not parsed correctly"
        );

        actual = parse_message(String::from("/ping"));
        assert!(actual.is_ok(), "Ping command did not return successfully");
        assert_eq!(
            Message {
                kind: Kind::Ping,
                message: None,
                code: None,
            },
            actual.unwrap(),
            "Ping command was not parsed correctly"
        );

        actual = parse_message(String::from("/ping pinging you"));
        assert!(
            actual.is_ok(),
            "Ping command with message did not return successfully"
        );
        assert_eq!(
            Message {
                kind: Kind::Ping,
                message: Some(String::from("pinging you")),
                code: None,
            },
            actual.unwrap(),
            "Ping command with message was not parsed correctly"
        );

        actual = parse_message(String::from("/pong"));
        assert!(actual.is_ok(), "Pong command did not return successfully");
        assert_eq!(
            Message {
                kind: Kind::Pong,
                message: None,
                code: None,
            },
            actual.unwrap(),
            "Pong command was not parsed correctly"
        );

        actual = parse_message(String::from("/pong ponging you"));
        assert!(
            actual.is_ok(),
            "Pong command with message did not return successfully"
        );
        assert_eq!(
            Message {
                kind: Kind::Pong,
                message: Some(String::from("ponging you")),
                code: None,
            },
            actual.unwrap(),
            "Pong command with message was not parsed correctly"
        );

        actual = parse_message(String::from("/close"));
        assert!(actual.is_ok(), "Close command did not return successfully");
        assert_eq!(
            Message {
                kind: Kind::Close,
                message: None,
                code: None,
            },
            actual.unwrap(),
            "Close command was not parsed correctly"
        );

        actual = parse_message(String::from("/close 1000"));
        assert!(
            actual.is_ok(),
            "Close command with code did not return successfully"
        );
        assert_eq!(
            Message {
                kind: Kind::Close,
                message: None,
                code: Some(1000),
            },
            actual.unwrap(),
            "Close command with code was not parsed correctly"
        );

        actual = parse_message(String::from("/close 1000 close down, you"));
        assert!(
            actual.is_ok(),
            "Close command with code and message did not return successfully"
        );
        assert_eq!(
            Message {
                kind: Kind::Close,
                message: Some(String::from("close down, you")),
                code: Some(1000),
            },
            actual.unwrap(),
            "Close command with code and message was not parsed correctly"
        );

        actual = parse_message(String::from("/foobar"));
        assert!(actual.is_err(), "Invalid command did not return error");
        assert_eq!(
            ErrorKind::InvalidInput,
            actual.unwrap_err().kind(),
            "Close command with code and message was not parsed correctly"
        );

        actual = parse_message(String::from(" /foobar"));
        assert!(
            actual.is_ok(),
            "Escaped invalid command did not return successfully"
        );
        assert_eq!(
            Message {
                kind: Kind::Message,
                message: Some(String::from("/foobar")),
                code: None,
            },
            actual.unwrap(),
            "Escaped invalid command was not parsed correctly"
        );
    }
}