extern crate nom;
use std::str;
use core::hash::Hash;
use std::collections::HashMap;
use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
pub mod parsers;
#[derive(Debug)]
pub enum AurisParseErrorKind {
Failed,
}
#[derive(Debug)]
pub struct ParseError {
kind: AurisParseErrorKind,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.kind {
AurisParseErrorKind::Failed => write!(f, "Parsing failed"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum UserInfo<T> {
User(T),
UserAndPassword(T, T),
}
impl UserInfo<&str> {
fn to_owned(&self) -> UserInfo<String> {
match self {
UserInfo::User(d) => UserInfo::User((*d).to_string()),
UserInfo::UserAndPassword(u, p) => {
UserInfo::UserAndPassword((*u).to_string(), (*p).to_string())
}
}
}
}
impl fmt::Display for UserInfo<String> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UserInfo::User(user) => write!(f, "{}", user),
UserInfo::UserAndPassword(user, password) => write!(f, "{}:{}", user, password),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Authority<T>
where
T: Ord + Hash,
{
pub host: T,
pub userinfo: Option<UserInfo<T>>,
pub port: Option<u16>,
}
impl Authority<&str> {
fn to_owned(&self) -> Authority<String> {
Authority {
host: self.host.to_string(),
userinfo: self.userinfo.as_ref().map(|u| u.to_owned()),
port: self.port,
}
}
}
impl fmt::Display for Authority<String> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut formatted = String::new();
let userinfo_string = match self.userinfo.as_ref() {
Some(userinfo) => format!("{}@", userinfo),
None => String::new(),
};
formatted.push_str(&userinfo_string);
formatted.push_str(&self.host);
let port_string = match self.port.as_ref() {
Some(port) => format!(":{}", port),
None => String::new(),
};
formatted.push_str(&port_string);
write!(f, "{}", formatted)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct URI<T>
where
T: Ord + Hash,
{
pub scheme: T,
pub authority: Authority<T>,
pub path: Option<Vec<T>>,
pub qs: Option<HashMap<T, T>>,
pub fragment: Option<T>,
}
impl URI<&str> {
fn to_owned(&self) -> URI<String> {
URI {
scheme: self.scheme.to_owned(),
authority: self.authority.to_owned(),
path: self
.path
.as_ref()
.map(|p: &Vec<&str>| p.iter().map(|f| String::from(*f)).collect()),
qs: self.qs.as_ref().map(|qs| {
qs.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect()
}),
fragment: self.fragment.map(|f| f.to_string()),
}
}
}
impl FromStr for URI<String> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match parsers::uri(s) {
Ok((_, obj)) => Ok(obj.to_owned()),
Err(_) => Err(ParseError {
kind: AurisParseErrorKind::Failed,
}),
}
}
}
impl fmt::Display for URI<String> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut formatted = String::new();
formatted.push_str(&self.scheme);
formatted.push_str("://");
formatted.push_str(&format!("{}", self.authority));
if let Some(ref path) = self.path {
for segment in path {
formatted.push('/');
formatted.push_str(segment);
}
}
if let Some(ref qs) = self.qs {
formatted.push('?');
let pairs: Vec<String> = qs.iter().map(|(k, v)| format!("{}={}", k, v)).collect();
formatted.push_str(&pairs.join("&"));
}
if let Some(ref fragment) = self.fragment {
formatted.push('#');
formatted.push_str(fragment);
}
write!(f, "{}", formatted)
}
}
pub enum Host<S = String> {
Domain(S),
Ipv4(Ipv4Addr),
Ipv6(Ipv6Addr),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display_roundtrip_simple() {
let uri_str = "http://example.com";
let parsed: URI<String> = uri_str.parse().unwrap();
assert_eq!(format!("{}", parsed), uri_str);
}
#[test]
fn test_display_roundtrip_with_path() {
let parsed: URI<String> = "http://example.com/path/to/resource".parse().unwrap();
assert_eq!(format!("{}", parsed), "http://example.com/path/to/resource");
}
#[test]
fn test_display_roundtrip_with_userinfo() {
let parsed: URI<String> = "ftp://user:pass@ftp.example.com/file".parse().unwrap();
assert_eq!(format!("{}", parsed), "ftp://user:pass@ftp.example.com/file");
}
#[test]
fn test_display_roundtrip_with_port() {
let parsed: URI<String> = "http://example.com:8080/api".parse().unwrap();
assert_eq!(format!("{}", parsed), "http://example.com:8080/api");
}
#[test]
fn test_display_with_fragment() {
let parsed: URI<String> = "http://example.com/page#section".parse().unwrap();
assert_eq!(format!("{}", parsed), "http://example.com/page#section");
}
#[test]
fn test_display_full_uri() {
let parsed: URI<String> = "https://user:pass@example.com:443/path/to/resource#top".parse().unwrap();
let displayed = format!("{}", parsed);
assert!(displayed.starts_with("https://"));
assert!(displayed.contains("user:pass@"));
assert!(displayed.contains("example.com:443"));
assert!(displayed.contains("/path/to/resource"));
assert!(displayed.contains("#top"));
}
}
#[cfg(test)]
mod quickcheck_tests {
use super::*;
use quickcheck::{Arbitrary, Gen};
use quickcheck_macros::quickcheck;
fn gen_scheme(g: &mut Gen) -> String {
let first = *g.choose(&['a', 'b', 'c', 'h', 'f', 's', 'm', 't']).unwrap();
let schemes = ["http", "https", "ftp", "mailto", "ssh", "file", "tcp"];
if bool::arbitrary(g) {
schemes[usize::arbitrary(g) % schemes.len()].to_string()
} else {
first.to_string()
}
}
fn gen_host(g: &mut Gen) -> String {
let parts = ["example", "test", "localhost", "foo", "bar", "api", "www"];
let tlds = ["com", "org", "net", "io", "dev"];
let part = parts[usize::arbitrary(g) % parts.len()];
if bool::arbitrary(g) {
format!("{}.{}", part, tlds[usize::arbitrary(g) % tlds.len()])
} else {
part.to_string()
}
}
fn gen_path_segment(g: &mut Gen) -> String {
let segments = ["path", "to", "resource", "api", "v1", "users", "123", "file.txt"];
segments[usize::arbitrary(g) % segments.len()].to_string()
}
fn gen_userinfo(g: &mut Gen) -> Option<(String, Option<String>)> {
if bool::arbitrary(g) {
let users = ["user", "admin", "test", "guest"];
let user = users[usize::arbitrary(g) % users.len()].to_string();
let password = if bool::arbitrary(g) {
let passwords = ["pass", "secret", "1234", "pwd"];
Some(passwords[usize::arbitrary(g) % passwords.len()].to_string())
} else {
None
};
Some((user, password))
} else {
None
}
}
fn gen_fragment(g: &mut Gen) -> Option<String> {
if bool::arbitrary(g) {
let fragments = ["top", "section", "anchor", "main", "content"];
Some(fragments[usize::arbitrary(g) % fragments.len()].to_string())
} else {
None
}
}
#[derive(Debug, Clone)]
struct ValidUri(String);
impl Arbitrary for ValidUri {
fn arbitrary(g: &mut Gen) -> Self {
let scheme = gen_scheme(g);
let host = gen_host(g);
let userinfo = gen_userinfo(g);
let port: Option<u16> = if bool::arbitrary(g) {
Some(*g.choose(&[80, 443, 8080, 3000, 5432, 27017]).unwrap())
} else {
None
};
let path_segments: Vec<String> = if bool::arbitrary(g) {
let count = usize::arbitrary(g) % 4;
(0..count).map(|_| gen_path_segment(g)).collect()
} else {
vec![]
};
let fragment = gen_fragment(g);
let mut uri = format!("{}://", scheme);
if let Some((user, pass)) = userinfo {
uri.push_str(&user);
if let Some(p) = pass {
uri.push(':');
uri.push_str(&p);
}
uri.push('@');
}
uri.push_str(&host);
if let Some(p) = port {
uri.push(':');
uri.push_str(&p.to_string());
}
for seg in &path_segments {
uri.push('/');
uri.push_str(seg);
}
if let Some(frag) = fragment {
uri.push('#');
uri.push_str(&frag);
}
ValidUri(uri)
}
}
#[quickcheck]
fn prop_valid_uri_parses(uri: ValidUri) -> bool {
uri.0.parse::<URI<String>>().is_ok()
}
#[quickcheck]
fn prop_scheme_preserved(uri: ValidUri) -> bool {
if let Ok(parsed) = uri.0.parse::<URI<String>>() {
uri.0.starts_with(&format!("{}://", parsed.scheme))
} else {
false
}
}
#[quickcheck]
fn prop_host_in_original(uri: ValidUri) -> bool {
if let Ok(parsed) = uri.0.parse::<URI<String>>() {
uri.0.contains(&parsed.authority.host)
} else {
false
}
}
#[quickcheck]
fn prop_display_parse_roundtrip(uri: ValidUri) -> bool {
if let Ok(parsed1) = uri.0.parse::<URI<String>>() {
let displayed = format!("{}", parsed1);
if let Ok(parsed2) = displayed.parse::<URI<String>>() {
parsed1.scheme == parsed2.scheme
&& parsed1.authority.host == parsed2.authority.host
&& parsed1.authority.port == parsed2.authority.port
&& parsed1.authority.userinfo == parsed2.authority.userinfo
&& parsed1.path == parsed2.path
&& parsed1.fragment == parsed2.fragment
} else {
false
}
} else {
false
}
}
#[quickcheck]
fn prop_port_preserved(uri: ValidUri) -> bool {
if let Ok(parsed) = uri.0.parse::<URI<String>>() {
if let Some(port) = parsed.authority.port {
uri.0.contains(&format!(":{}", port))
} else {
true
}
} else {
false
}
}
#[quickcheck]
fn prop_fragment_preserved(uri: ValidUri) -> bool {
if let Ok(parsed) = uri.0.parse::<URI<String>>() {
if let Some(ref frag) = parsed.fragment {
uri.0.ends_with(&format!("#{}", frag))
} else {
!uri.0.contains('#')
}
} else {
false
}
}
}