use std::default::Default;
use itertools::Itertools;
pub struct URI<'a> {
scheme: &'a str,
userinfo: Option<&'a str>,
host: Option<&'a str>,
port: Option<u16>,
path: Vec<&'a str>,
query: Vec<(String, String)>,
fragment: Option<&'a str>,
}
impl<'a> Default for URI<'a> {
fn default() -> URI<'a> {
URI {
scheme: "http",
userinfo: None,
host: Some("localhost"),
port: Some(80),
path: Vec::new(),
query: Vec::new(),
fragment: None,
}
}
}
impl<'a> URI<'a> {
pub fn new(scheme: &'a str) -> URI<'a> {
URI {
scheme,
userinfo: None,
host: None,
port: None,
path: Vec::new(),
query: Vec::new(),
fragment: None,
}
}
pub fn userinfo(&'a mut self, value: &'a str) -> &'a mut URI {
self.userinfo = Some(value);
self
}
pub fn host(&'a mut self, value: &'a str) -> &'a mut URI {
self.host = Some(value);
self
}
pub fn port(&'a mut self, value: u16) -> &'a mut URI {
self.port = Some(value);
self
}
pub fn path(&'a mut self, segment: &'a str) -> &'a mut URI {
self.path.push(segment);
self
}
pub fn path_vec(&'a mut self, segments: &Vec<&'a str>) -> &'a mut URI {
self.path.extend(segments);
self
}
pub fn query<T, U>(&'a mut self, attribute: U, value: T) -> &'a mut URI
where T: ToString,
U: ToString,
{
self.query.push((attribute.to_string(), value.to_string()));
self
}
pub fn query_vec<T, U>(&'a mut self, values: &Vec<(T, U)>) -> &'a mut URI
where T: ToString,
U: ToString,
{
self.query.extend(values.iter()
.map(|(a, v)| (a.to_string(), v.to_string()))
.collect::<Vec<(String, String)>>()
);
self
}
pub fn fragment(&'a mut self, value: &'a str) -> &'a mut URI {
self.fragment = Some(value);
self
}
pub fn build(&'a self) -> String {
let authority = match self.userinfo {
Some(a) => format!("{}@", a),
None => String::new(),
};
let authority = match self.host {
Some(h) => format!("{}{}", authority, h),
None => authority,
};
let authority = match self.port {
Some(p) => format!("{}:{}", authority, p),
None => authority,
};
let scheme = if authority.is_empty() {
format!("{}:", self.scheme)
} else {
format!("{}://", self.scheme)
};
let path = self.path.iter()
.format("/")
.to_string();
let path = if authority.is_empty() {
path
} else {
format!("/{}", path)
};
let query = self.query.iter()
.map(|(a, v)| format!("{}={}", a, v))
.format("&")
.to_string();
let query = if query.is_empty() {
query
} else {
format!("?{}", query)
};
let fragment = match self.fragment {
Some(f) => format!("#{}", f),
None => String::new(),
};
format!("{}{}{}{}{}",
scheme, authority, path, query, fragment)
}
}
#[cfg(test)]
mod tests {
use crate::URI;
#[test]
fn test_builder() {
let uri = URI::default().build();
assert_eq!("http://localhost:80/", uri);
let uri = URI::new("https")
.host("github.com")
.path("repos")
.query("page", 1)
.build();
assert_eq!("https://github.com/repos?page=1", uri);
let uri = URI::new("https")
.userinfo("john.doe")
.host("www.example.com")
.port(123)
.path_vec(vec!["forum", "questions"].as_ref())
.query_vec(vec![("tag", "networking"), ("order", "newest")].as_ref())
.fragment("top")
.build();
assert_eq!("https://john.doe@www.example.com:123/forum/questions?tag=networking&order=newest#top", uri);
let uri = URI::new("mailto")
.path("John.Doe@example.com")
.build();
assert_eq!("mailto:John.Doe@example.com", uri);
let uri = URI::new("news")
.path("comp.infosystems.www.servers.unix")
.build();
assert_eq!("news:comp.infosystems.www.servers.unix", uri);
let uri = URI::new("telnet")
.host("192.0.2.16")
.port(80)
.path("")
.build();
assert_eq!("telnet://192.0.2.16:80/", uri);
let uri = URI::new("tel")
.path("+1-816-555-1212")
.build();
assert_eq!("tel:+1-816-555-1212", uri);
}
}