happy 0.1.1

A simple web framework with a focus on serving static files and hosting APIs
Documentation
use combine::{self, Parser}; 
use combine::*;
use regex;
use hyper::Method;

#[derive(Debug, Eq, PartialEq)]
pub struct ParseResult {
    method: Option<Method>,
    segments: Vec<Segment>,
}

#[derive(Debug, Eq, PartialEq)]
enum SegmentPiece {
    Literal(String),
    Pattern(String),
}

#[derive(Debug, Eq, PartialEq)]
struct Segment(Vec<SegmentPiece>);

type StrParser<'a, R> = combine::Parser<Output=R, Input=&'a str>;

impl ParseResult {
    pub fn compile(self) -> (Option<Method>, String) {
        use std::fmt::Write;
        use self::SegmentPiece::*;

        let mut out = String::new();
        write!(out, "/?").unwrap();
        for segment in self.segments {
            for piece in segment.0 {
                match piece {
                    Literal(l) => write!(out, "{}", regex::escape(&l)),
                    Pattern(p) => write!(out, "(?P<{}>[^/]+)", regex::escape(&p)),
                }.unwrap()
            }
            write!(out, "/").unwrap();
        }
        write!(out, "?").unwrap();

        return (self.method, out)
    }
}

fn verb<'a>() -> Box<StrParser<'a, Option<Method>>> {
    use combine::char::string_cmp;
    use hyper::Method::*;
    use std::ascii::AsciiExt;
    let verbs = &[
        (Options, "Options"), 
        (Get, "Get"), 
        (Post, "Post"), 
        (Put, "Put"), 
        (Delete, "Delete"), 
        (Head, "Head"), 
        (Trace, "Trace"), 
        (Connect, "Connect"), 
        (Patch, "Patch"),
    ];

    let construct_verb_parser = |method: Method, s: &'static str|{
        string_cmp(s, |a, b| AsciiExt::eq_ignore_ascii_case(&a, &b))
                                 .map(move |_| method.clone())
    };

    let verbs_combinator: Vec<_> = verbs.iter().cloned().map(|(a, b)| construct_verb_parser(a, b)).collect();
    let verbs_total = combine::choice(verbs_combinator);
    let verbs_and_space = verbs_total.and(many1::<String, _>(token(' '))).map(|x| x.0);
    optional(verbs_and_space).boxed()
}

fn segment_piece<'a>() -> Box<StrParser<'a, SegmentPiece>> {
    use combine::char::*;
    let literal = many1::<Vec<_>, _>(none_of("{}/".chars())).map(|r| r.into_iter().collect::<String>());
    let name = token('_').or(letter()).and(many(token('_').or(alpha_num()))).map(|(f, mut r): (char, String)| {r.insert(0, f); r});
    let pattern = token('{').and(name).and(token('}')).map(|((_, r), _)| SegmentPiece::Pattern(r));

    literal.map(|r| SegmentPiece::Literal(r)).or(pattern).boxed()
}

fn segment<'a>() -> Box<StrParser<'a, Segment>> {
    many1(segment_piece()).map(Segment).and(optional(token('/'))).map(|(v, _)| v).boxed()
}

fn url<'a>() -> Box<StrParser<'a, Vec<Segment>>> {
    optional(token('/')).and(many1(segment())).map(|(_, v)| v).boxed()

}

pub fn parse<'a>(input: &'a str) -> Result<ParseResult, ParseError<&'a str>> {
    use combine::Parser;
    
    verb().and(optional(token('/'))).and(url()).map(|((v, _), u)| 
      ParseResult {
        method: v,
        segments: u,
    }).parse(input).map(|(result, _remaining)| {
        result
    })
}

#[cfg(test)]
mod test {
    use combine::{ParseError};
    use super::*;
    use hyper::Method::*;
    use super::SegmentPiece::*;

    fn run(input: &str) -> Result<super::ParseResult, ParseError<&str>> {
        super::parse(input) 
    }

    fn run_ok(input: &str) -> super::ParseResult {
        run(input).unwrap()
    }

    fn compile_ok(input: &str) -> String {
        run(input).unwrap().compile().1
    }

    #[test]
    fn compile_a_few() {
        assert_eq!(compile_ok("abc"), r"/?abc/?");
        assert_eq!(compile_ok("abc/def"), r"/?abc/def/?");
        assert_eq!(compile_ok("{named}"), r"/?(?P<named>[^/]+)/?");
        assert_eq!(compile_ok("{a}-{b}"), r"/?(?P<a>[^/]+)\-(?P<b>[^/]+)/?");
        assert_eq!(compile_ok("a/{named}/b"), r"/?a/(?P<named>[^/]+)/b/?");
        assert_eq!(compile_ok("{a}/{b}"), r"/?(?P<a>[^/]+)/(?P<b>[^/]+)/?");
    }

    #[test]
    fn segment_piece() {
        use self::SegmentPiece::*;

        let res = super::segment_piece().parse("abc").unwrap().0;
        assert_eq!(res, Literal("abc".into()));

        let res = super::segment_piece().parse("{abc}").unwrap().0;
        assert_eq!(res, Pattern("abc".into()));

        let res = super::segment_piece().parse("{{abc}}");
        assert!(res.is_err());

        let res = super::segment_piece().parse("{}");
        assert!(res.is_err());

        let res = super::segment_piece().parse("}");
        assert!(res.is_err());

        let res = super::segment_piece().parse("{");
        assert!(res.is_err());

        let res = super::segment_piece().parse("{abc");
        assert!(res.is_err());

        let res = super::segment_piece().parse("/");
        assert!(res.is_err());

    }

    #[test]
    fn segment() {
        use self::SegmentPiece::*;
        let res = super::segment().parse("abc").unwrap().0;
        assert_eq!(res, 
           Segment(vec![
                Literal("abc".into()) 
        ]));

        let res = super::segment().parse("{abc}").unwrap().0;
        assert_eq!(res, 
           Segment(vec![
                Pattern("abc".into()) 
        ]));

        let res = super::segment().parse("abc{def}").unwrap().0;
        assert_eq!(res, 
           Segment(vec![
                Literal("abc".into()),
                Pattern("def".into()),
        ]));

        let res = super::segment().parse("abc{def}ghi").unwrap().0;
        assert_eq!(res, 
           Segment(vec![
                Literal("abc".into()),
                Pattern("def".into()),
                Literal("ghi".into()),
        ]));
    }

    #[test]
    fn url() {
        use self::SegmentPiece::*;

        let res = super::url().parse("abc").unwrap().0;
        assert_eq!(res.len(), 1);
        assert_eq!(res[0].0[0], Literal("abc".into()));

        let res = super::url().parse("/abc").unwrap().0;
        assert_eq!(res.len(), 1);
        assert_eq!(res[0].0[0], Literal("abc".into()));

        let res = super::url().parse("abc/").unwrap().0;
        assert_eq!(res.len(), 1);
        assert_eq!(res[0].0[0], Literal("abc".into()));

        let res = super::url().parse("/abc/").unwrap().0;
        assert_eq!(res.len(), 1);
        assert_eq!(res[0].0[0], Literal("abc".into()));

        let res = super::url().parse("/abc/def").unwrap().0;
        assert_eq!(res.len(), 2);
        assert_eq!(res[0].0[0], Literal("abc".into()));
        assert_eq!(res[1].0[0], Literal("def".into()));
    }

    #[test]
    fn basic_with_method() {
        assert_eq!(
            run_ok("GET foo"),
            super::ParseResult {
                method: Some(Get),
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                ]                    
            }
        );

        assert_eq!(
            run_ok("GET foo/bar"),
            super::ParseResult {
                method: Some(Get),
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                    Segment(vec![Literal("bar".into())]),
                ]                    
            }
        );

        assert_eq!(
            run_ok("GET /foo/bar"),
            super::ParseResult {
                method: Some(Get),
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                    Segment(vec![Literal("bar".into())]),
                ]                    
            }
        );

        assert_eq!(
            run_ok("GET /foo/bar/"),
            super::ParseResult {
                method: Some(Get),
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                    Segment(vec![Literal("bar".into())]),
                ]                    
            }
        );

        assert_eq!(
            run_ok("foo"),
            super::ParseResult {
                method: None,
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                ]                    
            }
        );

        assert_eq!(
            run_ok("foo/bar"),
            super::ParseResult {
                method: None,
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                    Segment(vec![Literal("bar".into())]),
                ]                    
            }
        );

        assert_eq!(
            run_ok("/foo/bar"),
            super::ParseResult {
                method: None,
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                    Segment(vec![Literal("bar".into())]),
                ]                    
            }
        );

        assert_eq!(
            run_ok("/foo/bar/"),
            super::ParseResult {
                method: None,
                segments: vec![
                    Segment(vec![Literal("foo".into())]),
                    Segment(vec![Literal("bar".into())]),
                ]                    
            }
        );
    }
}