uri_parser 0.2.0

Simple URI parser with zero copy and easy access to all URI parts
Documentation
use nom::{IResult, digit, ErrorKind};
use std::str;
use std::path::Path;
use std::collections::HashMap;
use super::{URI,User};

named!(token<&[u8], &str>, map_res!(is_not!(":/?#[]@"), str::from_utf8));
named!(scheme <&[u8], &str>, map_res!(take_until!(":"), str::from_utf8));
named!(user <&[u8], User>, do_parse!(
    user: token >>
    password: opt!(do_parse!(
        tag!(":") >>
        password: token >>
        (password)
    )) >>
    tag!("@") >>
    (User{name:user, password:password})
));

named!(authority< &[u8], (Option<User>, &str, Option<u16>) >, 
do_parse!(
        tag!("//") >> 
        user: opt!(complete!(user)) >>
        host: token >>
        port: opt!(complete!(do_parse!(
            tag!(":") >>
            p: map_res!(digit, bytes_to_u16) >>
            (p)
        ))) >>
        (user, host, port)
        )
);
named!(path_token<&[u8], &str>, map_res!(is_not!(":?#[]"), str::from_utf8));
fn parse_path(i: &[u8]) -> IResult<&[u8], &Path> {
    if i.is_empty() || ! i[0] as char == '/' {
        return IResult::Error(ErrorKind::Custom(1));
    }
    path_token(i).map(|s| Path::new(s))
}

named!(query_token<&[u8], &str>, map_res!(is_not!("&=:#[]"), str::from_utf8));
named!(query_item<&[u8], (&str, &str)>, do_parse!(
    key: query_token >>
    char!('=') >>
    val: query_token >>
    (key,val)
));

named!(query<&[u8], HashMap<&str,&str> >, 
    map!(
    preceded!(
    tag!("?"),
    separated_list_complete!(char!('&'), query_item)
    ),
    |v: Vec<_>| v.into_iter().collect()
    )
);

named!(hash_token<&[u8], &str>, map_res!(is_not!(":#[]"), str::from_utf8));
named!(hash<&[u8], &str>, preceded!(
    tag!("#"),
    hash_token
));

named!(pub uri <&[u8], URI>, dbg!( do_parse!(
    scheme: scheme >>
    tag!(":") >>
    authority: opt!(authority) >>
    path: opt!(parse_path) >>
    query: opt!(complete!(query)) >>
    hash: opt!(complete!(hash)) >>
    
    ( match authority {
        Some(a) => URI {scheme, user:a.0, host:Some(a.1), port: a.2, path, query, hash},
        None => URI {scheme, user:None, host:None, port:None, path, query, hash}
    }
    )
)));

fn bytes_to_u16(b: &[u8]) -> Result<u16, String> {
    str::from_utf8(b)
        .map_err(|e| e.to_string())
        .and_then(|s| u16::from_str_radix(s, 10)
                .map_err(|e| e.to_string())
            )
}

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

    #[test]
    fn test_query() {
        let qs=b"?a=b&c=d";
        let d = query(qs).unwrap().1;
        assert_eq!(d.get("a"), Some(&"b"));
         assert_eq!(d.get("c"), Some(&"d"));  
    }

    #[test]
    fn test_conversion() {
        let n = b"1234";
        assert_eq!(bytes_to_u16(n), Ok(1234));
        
    }

    #[test]
    fn test_user() {
        let u="ivan@";
        assert_eq!(user(u.as_bytes()), IResult::Done("".as_bytes(), User{name:"ivan", password:None }));

        let u="ivan:heslo@";
        assert_eq!(user(u.as_bytes()), IResult::Done("".as_bytes(), User{name:"ivan", password:Some("heslo") }));

    }

    #[test]
    fn test_path() {
        assert_eq!(parse_path(b"/"), IResult::Done("".as_bytes(), Path::new("/")));
        assert!(parse_path(b"").is_err());
    }

    #[test]
    fn test_uri() {
        fn tst(u: &[u8], res: URI) -> () {
            use IResult::*;
            match uri(u) {
                Done(_, r) => assert_eq!(r, res),
                Error(e) => panic!("Parsing uri failed {:?}", e),
                Incomplete(i) => panic!("Incomplete parsing {:?}", i)
            }
        }
        let u=b"https://zderadicka.eu";
        tst(u, URI{scheme:"https", user:None, host:Some("zderadicka.eu"), port:None, path: None, query:None, hash:None});
        let u=b"https://zderadicka.eu:8080";
        tst(u, URI{scheme:"https", user:None, host:Some("zderadicka.eu"), port:Some(8080), path: None, query:None, hash:None});
        let u=b"https://ivan:secret@zderadicka.eu/";
        tst(u, URI{scheme: "https", user: Some(User{name:"ivan", password:Some("secret")}),
                host:Some("zderadicka.eu"), port:None, path: Some(Path::new("/")), query:None, hash:None});

        let u=b"https://ivan:secret@zderadicka.eu/home?q=hey#hash";
        let mut q = HashMap::new();
        q.insert("q", "hey");
        tst(u, URI{scheme: "https", user: Some(User{name:"ivan", password:Some("secret")}),
                host:Some("zderadicka.eu"), port:None, path: Some(Path::new("/home")), query:Some(q), hash:Some("hash")});
        
    }

    #[test]
    fn test_scheme() {
        let s = b"http:";
        assert_eq!(scheme(s), IResult::Done(":".as_bytes(), "http"));
    }

}