redis-subscribe 0.2.1

Easily subscribe and unsubscribe to redis pubsub.
Documentation
use nom::{
    branch::alt,
    bytes::streaming::tag_no_case,
    character::streaming::{char, crlf, i64, not_line_ending, u64},
    multi::count,
    sequence::{delimited, tuple},
    IResult,
};

#[derive(Debug, PartialEq)]
pub enum Response {
    Null,
    SimpleString(String),
    Error(String),
    Integer(i64),
    Bulk(String),
    Array(Vec<Response>),
}

type NomResult<'a, T> = IResult<&'a str, T>;

pub fn parse(input: &mut String) -> Vec<Response> {
    let mut result = Vec::new();

    loop {
        let (remainder, response) = match parse_response(input.as_str()) {
            Ok(parsed) => parsed,
            Err(_) => return result,
        };

        result.push(response);
        *input = remainder.to_string();
    }
}

fn parse_response(input: &str) -> NomResult<Response> {
    alt((
        parse_simple,
        parse_error,
        parse_integer,
        parse_bulk_string,
        parse_null,
        parse_array,
    ))(input)
}

fn parse_simple(input: &str) -> NomResult<Response> {
    let (remainder, response) = delimited(char('+'), not_line_ending, crlf)(input)?;

    Ok((remainder, Response::SimpleString(response.to_string())))
}

fn parse_error(input: &str) -> NomResult<Response> {
    let (remainder, response) = delimited(char('-'), not_line_ending, crlf)(input)?;

    Ok((remainder, Response::Error(response.to_string())))
}

fn parse_integer(input: &str) -> NomResult<Response> {
    let (remainder, response) = delimited(char(':'), i64, crlf)(input)?;

    Ok((remainder, Response::Integer(response)))
}

fn parse_bulk_string(input: &str) -> NomResult<Response> {
    let (remainder, (_, _, _, data, _)) =
        tuple((char('$'), u64, crlf, not_line_ending, crlf))(input)?;

    Ok((remainder, Response::Bulk(data.to_string())))
}

fn parse_null(input: &str) -> NomResult<Response> {
    let (remainder, _) = tuple((tag_no_case("$-1"), crlf))(input)?;

    Ok((remainder, Response::Null))
}

fn parse_array(input: &str) -> NomResult<Response> {
    let (remainder, amount) = delimited(char('*'), u64, crlf)(input)?;
    let (remainder, entries) = count(parse_response, amount as usize)(remainder)?;

    Ok((remainder, Response::Array(entries)))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple_string() {
        let (rem, res) = parse_response("+OK\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(Response::SimpleString("OK".to_string()), res);
    }

    #[test]
    fn error() {
        let (rem, res) = parse_response("-Error message\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(Response::Error("Error message".to_string()), res);
    }

    #[test]
    fn integer() {
        let (rem, res) = parse_response(":1000\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(Response::Integer(1000), res);
    }

    #[test]
    fn bulk() {
        let (rem, res) = parse_response("$6\r\nfoobar\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(Response::Bulk("foobar".to_string()), res);
    }

    #[test]
    fn null() {
        let (rem, res) = parse_response("$-1\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(Response::Null, res);
    }

    #[test]
    fn array() {
        let (rem, res) = parse_response("*0\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(Response::Array(vec![]), res);
    }

    #[test]
    fn array_filled() {
        let (rem, res) = parse_response("*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(
            Response::Array(vec![
                Response::Bulk("foo".to_string()),
                Response::Bulk("bar".to_string())
            ]),
            res
        );
    }

    #[test]
    fn array_nested() {
        let (rem, res) =
            parse_response("*2\r\n*3\r\n:1\r\n:2\r\n:3\r\n*2\r\n+Foo\r\n-Bar\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(
            Response::Array(vec![
                Response::Array(vec![
                    Response::Integer(1),
                    Response::Integer(2),
                    Response::Integer(3),
                ]),
                Response::Array(vec![
                    Response::SimpleString("Foo".to_string()),
                    Response::Error("Bar".to_string())
                ])
            ]),
            res
        );
    }

    #[test]
    fn array_null() {
        let (rem, res) = parse_response("*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n").unwrap();

        assert_eq!("", rem);
        assert_eq!(
            Response::Array(vec![
                Response::Bulk("foo".to_string()),
                Response::Null,
                Response::Bulk("bar".to_string())
            ]),
            res
        );
    }
}