use std::convert::From;
use std::fmt;
use std::borrow::Cow;
use std::str::Utf8Error;
use std::sync::atomic::{AtomicIsize, Ordering};
use url;
type Index = (usize, usize);
const EMPTY: isize = -1;
#[derive(Debug)]
pub struct URI<'a> {
uri: Cow<'a, str>,
path: Index,
query: Option<Index>,
fragment: Option<Index>,
segment_count: AtomicIsize,
}
impl<'a> URI<'a> {
pub fn new<T: Into<Cow<'a, str>>>(uri: T) -> URI<'a> {
let uri = uri.into();
let qmark = uri.find('?');
let hmark = uri.find('#');
let end = uri.len();
let (path, query, fragment) = match (qmark, hmark) {
(Some(i), Some(j)) if i < j => ((0, i), Some((i+1, j)), Some((j+1, end))),
(Some(_i), Some(j)) => ((0, j), None, Some((j+1, end))),
(Some(i), None) => ((0, i), Some((i+1, end)), None),
(None, Some(j)) => ((0, j), None, Some((j+1, end))),
(None, None) => ((0, end), None, None),
};
URI {
uri: uri,
path: path,
query: query,
fragment: fragment,
segment_count: AtomicIsize::new(EMPTY),
}
}
#[inline(always)]
pub fn segment_count(&self) -> usize {
let count = self.segment_count.load(Ordering::Relaxed);
if count == EMPTY {
let real_count = self.segments().count();
if real_count <= isize::max_value() as usize {
self.segment_count.store(real_count as isize, Ordering::Relaxed);
}
real_count
} else {
count as usize
}
}
#[inline(always)]
pub fn segments(&self) -> Segments {
Segments(self.path())
}
#[inline(always)]
pub fn path(&self) -> &str {
let (i, j) = self.path;
&self.uri[i..j]
}
#[inline(always)]
pub fn query(&self) -> Option<&str> {
self.query.map(|(i, j)| &self.uri[i..j])
}
#[inline(always)]
pub fn fragment(&self) -> Option<&str> {
self.fragment.map(|(i, j)| &self.uri[i..j])
}
pub fn percent_decode(string: &[u8]) -> Result<Cow<str>, Utf8Error> {
let decoder = url::percent_encoding::percent_decode(string);
decoder.decode_utf8()
}
pub fn percent_decode_lossy(string: &[u8]) -> Cow<str> {
let decoder = url::percent_encoding::percent_decode(string);
decoder.decode_utf8_lossy()
}
pub fn percent_encode(string: &str) -> Cow<str> {
let set = url::percent_encoding::PATH_SEGMENT_ENCODE_SET;
url::percent_encoding::utf8_percent_encode(string, set).into()
}
#[inline(always)]
pub fn as_str(&self) -> &str {
&self.uri
}
}
impl<'a> Clone for URI<'a> {
#[inline(always)]
fn clone(&self) -> URI<'a> {
URI {
uri: self.uri.clone(),
path: self.path,
query: self.query,
fragment: self.fragment,
segment_count: AtomicIsize::new(EMPTY),
}
}
}
impl<'a, 'b> PartialEq<URI<'b>> for URI<'a> {
#[inline]
fn eq(&self, other: &URI<'b>) -> bool {
self.path() == other.path() &&
self.query() == other.query() &&
self.fragment() == other.fragment()
}
}
impl<'a> Eq for URI<'a> {}
impl<'a> From<&'a str> for URI<'a> {
#[inline(always)]
fn from(uri: &'a str) -> URI<'a> {
URI::new(uri)
}
}
impl From<String> for URI<'static> {
#[inline(always)]
fn from(uri: String) -> URI<'static> {
URI::new(uri)
}
}
impl<'a> fmt::Display for URI<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.segment_count() == 0 {
write!(f, "/")?;
} else {
for segment in self.segments() {
write!(f, "/{}", segment)?;
}
}
if let Some(query_str) = self.query() {
write!(f, "?{}", query_str)?;
}
if let Some(fragment_str) = self.fragment() {
write!(f, "#{}", fragment_str)?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Segments<'a>(pub &'a str);
impl<'a> Iterator for Segments<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let i = match self.0.find(|c| c != '/') {
Some(index) => index,
None => return None,
};
let j = self.0[i..].find('/').map_or(self.0.len(), |j| i + j);
let result = Some(&self.0[i..j]);
self.0 = &self.0[j..];
result
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SegmentError {
Utf8(Utf8Error),
BadStart(char),
BadChar(char),
BadEnd(char),
}
#[cfg(test)]
mod tests {
use super::URI;
fn seg_count(path: &str, expected: usize) -> bool {
let actual = URI::new(path).segment_count();
if actual != expected {
trace_!("Count mismatch: expected {}, got {}.", expected, actual);
trace_!("{}", if actual != expected { "lifetime" } else { "buf" });
trace_!("Segments (for {}):", path);
for (i, segment) in URI::new(path).segments().enumerate() {
trace_!("{}: {}", i, segment);
}
}
actual == expected
}
fn eq_segments(path: &str, expected: &[&str]) -> bool {
let uri = URI::new(path);
let actual: Vec<&str> = uri.segments().collect();
actual == expected
}
#[test]
fn send_and_sync() {
fn assert<T: Send + Sync>() {};
assert::<URI>();
}
#[test]
fn simple_segment_count() {
assert!(seg_count("", 0));
assert!(seg_count("/", 0));
assert!(seg_count("a", 1));
assert!(seg_count("/a", 1));
assert!(seg_count("a/", 1));
assert!(seg_count("/a/", 1));
assert!(seg_count("/a/b", 2));
assert!(seg_count("/a/b/", 2));
assert!(seg_count("a/b/", 2));
assert!(seg_count("ab/", 1));
}
#[test]
fn segment_count() {
assert!(seg_count("////", 0));
assert!(seg_count("//a//", 1));
assert!(seg_count("//abc//", 1));
assert!(seg_count("//abc/def/", 2));
assert!(seg_count("//////abc///def//////////", 2));
assert!(seg_count("a/b/c/d/e/f/g", 7));
assert!(seg_count("/a/b/c/d/e/f/g", 7));
assert!(seg_count("/a/b/c/d/e/f/g/", 7));
assert!(seg_count("/a/b/cdjflk/d/e/f/g", 7));
assert!(seg_count("//aaflja/b/cdjflk/d/e/f/g", 7));
assert!(seg_count("/a /b", 2));
}
#[test]
fn single_segments_match() {
assert!(eq_segments("", &[]));
assert!(eq_segments("a", &["a"]));
assert!(eq_segments("/a", &["a"]));
assert!(eq_segments("/a/", &["a"]));
assert!(eq_segments("a/", &["a"]));
assert!(eq_segments("///a/", &["a"]));
assert!(eq_segments("///a///////", &["a"]));
assert!(eq_segments("a///////", &["a"]));
assert!(eq_segments("//a", &["a"]));
assert!(eq_segments("", &[]));
assert!(eq_segments("abc", &["abc"]));
assert!(eq_segments("/a", &["a"]));
assert!(eq_segments("/abc/", &["abc"]));
assert!(eq_segments("abc/", &["abc"]));
assert!(eq_segments("///abc/", &["abc"]));
assert!(eq_segments("///abc///////", &["abc"]));
assert!(eq_segments("abc///////", &["abc"]));
assert!(eq_segments("//abc", &["abc"]));
}
#[test]
fn multi_segments_match() {
assert!(eq_segments("a/b/c", &["a", "b", "c"]));
assert!(eq_segments("/a/b", &["a", "b"]));
assert!(eq_segments("/a///b", &["a", "b"]));
assert!(eq_segments("a/b/c/d", &["a", "b", "c", "d"]));
assert!(eq_segments("///a///////d////c", &["a", "d", "c"]));
assert!(eq_segments("abc/abc", &["abc", "abc"]));
assert!(eq_segments("abc/abc/", &["abc", "abc"]));
assert!(eq_segments("///abc///////a", &["abc", "a"]));
assert!(eq_segments("/////abc/b", &["abc", "b"]));
assert!(eq_segments("//abc//c////////d", &["abc", "c", "d"]));
}
#[test]
fn multi_segments_match_funky_chars() {
assert!(eq_segments("a/b/c!!!", &["a", "b", "c!!!"]));
assert!(eq_segments("a /b", &["a ", "b"]));
assert!(eq_segments(" a/b", &[" a", "b"]));
assert!(eq_segments(" a/b ", &[" a", "b "]));
assert!(eq_segments(" a///b ", &[" a", "b "]));
assert!(eq_segments(" ab ", &[" ab "]));
}
#[test]
fn segment_mismatch() {
assert!(!eq_segments("", &["a"]));
assert!(!eq_segments("a", &[]));
assert!(!eq_segments("/a/a", &["a"]));
assert!(!eq_segments("/a/b", &["b", "a"]));
assert!(!eq_segments("/a/a/b", &["a", "b"]));
assert!(!eq_segments("///a/", &[]));
}
fn test_query(uri: &str, query: Option<&str>) {
let uri = URI::new(uri);
assert_eq!(uri.query(), query);
}
fn test_fragment(uri: &str, fragment: Option<&str>) {
let uri = URI::new(uri);
assert_eq!(uri.fragment(), fragment);
}
#[test]
fn query_does_not_exist() {
test_query("/test", None);
test_query("/a/b/c/d/e", None);
test_query("/////", None);
test_query("//a///", None);
test_query("/a/b/c#a?123", None);
test_query("/#", None);
test_query("/#?", None);
}
#[test]
fn query_exists() {
test_query("/test?abc", Some("abc"));
test_query("/a/b/c?abc", Some("abc"));
test_query("/a/b/c/d/e/f/g/?abc#hijklmnop", Some("abc"));
test_query("?123", Some("123"));
test_query("?", Some(""));
test_query("/?", Some(""));
test_query("?#", Some(""));
test_query("/?hi", Some("hi"));
}
#[test]
fn fragment_exists() {
test_fragment("/test#abc", Some("abc"));
test_fragment("/#abc", Some("abc"));
test_fragment("/#ab?c", Some("ab?c"));
test_fragment("/a/b/c?123#a", Some("a"));
test_fragment("/a/b/c#a?123", Some("a?123"));
test_fragment("/a/b/c?123#a?b", Some("a?b"));
test_fragment("/#a", Some("a"));
}
#[test]
fn fragment_does_not_exist() {
test_fragment("/testabc", None);
test_fragment("/abc", None);
test_fragment("/a/b/c?123", None);
test_fragment("/a", None);
}
#[test]
fn to_string() {
let uri_to_string = |string| URI::new(string).to_string();
assert_eq!(uri_to_string("/"), "/".to_string());
assert_eq!(uri_to_string("//"), "/".to_string());
assert_eq!(uri_to_string("//////a/"), "/a".to_string());
assert_eq!(uri_to_string("//ab"), "/ab".to_string());
assert_eq!(uri_to_string("//a"), "/a".to_string());
assert_eq!(uri_to_string("/a/b///c"), "/a/b/c".to_string());
assert_eq!(uri_to_string("/a///b/c/d///"), "/a/b/c/d".to_string());
assert_eq!(uri_to_string("/a/b/c#a?123"), "/a/b/c#a?123".to_string());
}
}