safe_uri 0.1.0-beta.4

Simple and safe URI types.
Documentation
use safe_uri::*;
use std::net::{Ipv4Addr, Ipv6Addr};
use tap::Tap;

#[test]
fn wikipedia_article() {
    assert_eq!(
        UriRef::parse("https://en.wikipedia.org/wiki/Backslash".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("https").unwrap());
            u.authority = Some(Host::Name(HostName::try_from("en.wikipedia.org").unwrap()).into());
            u.resource = Path::try_from("/wiki/Backslash").unwrap().into();
        }),
    );
}

#[test]
fn ipv4_localhost() {
    assert_eq!(
        UriRef::parse("http://127.0.0.1/api/v2/user/35/alias/1".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("http").unwrap());
            u.authority = Some(Host::Ipv4Addr(Ipv4Addr::LOCALHOST).into());
            u.resource = Path::try_from("/api/v2/user/35/alias/1").unwrap().into();
        }),
    );
}

#[test]
fn ipv4_port_query_fragment() {
    assert_eq!(
        UriRef::parse("ftp://127.3.2.1:8080?hello=world#africa".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("ftp").unwrap());
            u.authority = Some(Authority::new().tap_mut(|a| {
                a.host = Host::Ipv4Addr([127, 3, 2, 1].into());
                a.port = Some(8080);
            }));
            u.resource = Resource::new().tap_mut(|r| {
                r.query = Some(Query::try_from("hello=world").unwrap());
                r.fragment = Some(Fragment::try_from("africa").unwrap());
            });
        }),
    );
}

#[test]
fn ipv6_localhost() {
    assert_eq!(
        UriRef::parse("http://[::1]/".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("http").unwrap());
            u.authority = Some(Host::Ipv6Addr(Ipv6Addr::LOCALHOST).into());
            u.resource = Path::try_from("/").unwrap().into();
        }),
    );
}

#[test]
fn ipv6_3_port_80() {
    assert_eq!(
        UriRef::parse("http://[::3]:80".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("http").unwrap());
            u.authority = Some(Authority::new().tap_mut(|a| {
                a.host = Host::Ipv6Addr(3.into());
                a.port = Some(80);
            }));
        }),
    );
}

#[test]
fn ipv6_with_missing_closing_bracket() {
    Uri::parse("http://[::4/path".into()).unwrap_err();
}

#[test]
fn user_info_password() {
    let actual = Uri::parse("http://user:password@example.com".into()).unwrap();
    let expected = Uri {
        scheme: Scheme::from_static("http"),
        authority: Some(Authority {
            user_info: Some(UserInfo::from_static("user:password")),
            host: Host::Name(HostName::from_static("example.com")),
            ..Default::default()
        }),
        ..Default::default()
    };
    assert_eq!(actual, expected);
}

#[test]
fn normalize_percent_encoding() {
    let actual =
        Uri::parse("http://%6Cogin@examp%6Ce.com/he%6C%6Co?name=%6Cenny#%6Cast".into()).unwrap();
    let expected = Uri {
        scheme: Scheme::from_static("http"),
        authority: Some(Authority {
            user_info: Some(UserInfo::from_static("login")),
            host: Host::Name(HostName::from_static("example.com")),
            ..Default::default()
        }),
        resource: Resource {
            path: Path::from_static("/hello"),
            query: Some(Query::from_static("name=lenny")),
            fragment: Some(Fragment::from_static("last")),
            ..Default::default()
        },
        ..Default::default()
    };
    assert_eq!(actual, expected);
}

#[test]
fn not_normalizable_percent_encoding() {
    let actual =
        Uri::parse("http://my%20user@my%20host/my%20path?my%20query#my%20fragment".into()).unwrap();
    let expected = Uri {
        scheme: Scheme::from_static("http"),
        authority: Some(Authority {
            user_info: Some(UserInfo::from_static("my%20user")),
            host: Host::Name(HostName::from_static("my%20host")),
            ..Default::default()
        }),
        resource: Resource {
            path: Path::from_static("/my%20path"),
            query: Some(Query::from_static("my%20query")),
            fragment: Some(Fragment::from_static("my%20fragment")),
            ..Default::default()
        },
        ..Default::default()
    };
    assert_eq!(actual, expected);
}

#[test]
fn port_delim() {
    assert_eq!(
        Uri::parse("http:)g:)1".into()).unwrap(),
        Uri::new().tap_mut(|u| {
            u.scheme = Scheme::from_static("http");
            u.resource.path = Path::from_static(")g:)1");
        }),
    );
}

#[test]
fn parse_urn() {
    assert_eq!(
        UriRef::parse("urn:animal:dog:nose".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("urn").unwrap());
            u.resource.path = Path::try_from("animal:dog:nose").unwrap();
        }),
    );
}

#[test]
fn file_absolute_unix() {
    assert_eq!(
        UriRef::parse("file:///home/test".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("file").unwrap());
            u.authority = Some(Host::Name(HostName::from_static("")).into());
            u.resource.path = Path::try_from("/home/test").unwrap();
        }),
    );
}

#[test]
fn file_absolute_windows_backslashs() {
    UriRef::parse(r"file://C:\Users\Test".into()).unwrap_err();
}

#[test]
fn file_absolute_windows_forward_slashs() {
    assert_eq!(
        UriRef::parse("file:///C:/Users/Test".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("file").unwrap());
            u.authority = Some(Host::Name(HostName::from_static("")).into());
            u.resource.path = Path::try_from("/C:/Users/Test").unwrap();
        }),
    );
}

#[test]
fn scheme_with_double_slash() {
    assert_eq!(
        UriRef::parse("http://".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.scheme = Some(Scheme::try_from("http").unwrap());
            u.authority = Some(Host::Name(HostName::from_static("")).into());
        }),
    );
}

#[test]
fn utf_8_host_and_path() {
    UriRef::parse("http://über.de/hää".into()).unwrap_err();
}

#[test]
fn authority_only() {
    assert_eq!(
        UriRef::parse("//example.com".into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.authority = Some(Host::Name(HostName::try_from("example.com").unwrap()).into());
        }),
    );
}

#[test]
fn path_only() {
    let path = "/my/resource";
    assert_eq!(
        UriRef::parse(path.into()).unwrap(),
        UriRef::new().tap_mut(|u| {
            u.resource.path = Path::try_from(path).unwrap();
        }),
    );
}

#[test]
fn invalid_percent_encoding() {
    UriRef::parse("-//!!%=#".into()).unwrap_err();
}

#[test]
fn port_with_plus() {
    UriRef::parse("//host:+1".into()).unwrap_err();
}

#[test]
fn port_with_minus() {
    UriRef::parse("//host:-1".into()).unwrap_err();
}

#[test]
fn port_with_minus_minus() {
    UriRef::parse("//host:--1".into()).unwrap_err();
}

#[test]
fn weird_invalid_percent_encoding() {
    Uri::parse("x+:%:".into()).unwrap_err();
}

#[test]
fn weird_reparse() {
    let uri = Uri::parse("x://%3AN@".into()).unwrap();
    dbg!(&uri);
    let uri_string = dbg!(uri.to_string());
    let same_uri = Uri::parse(uri_string.into()).unwrap();
    assert_eq!(uri, same_uri);
}