feat(lib): remove extern Url type usage

BREAKING CHANGE: The `Url` type is no longer used. Any instance in the
  `Client` API has had it replaced with `hyper::Uri`.

  This also means `Error::Uri` has changed types to
  `hyper::error::UriError`.

  The type `hyper::header::parsing::HTTP_VALUE` has been made private,
  as an implementation detail. The function `http_percent_encoding`
  should be used instead.
This commit is contained in:
Sean McArthur
2017-03-21 10:35:31 -07:00
parent e81184e53c
commit 4fb7e6ebc6
12 changed files with 192 additions and 155 deletions

View File

@@ -1,13 +1,8 @@
use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt::{Display, self};
use std::ops::Deref;
use std::str::{self, FromStr};
use http::ByteStr;
use Url;
use url::ParseError as UrlError;
use Error;
/// The Request-URI of a Request's StartLine.
///
@@ -32,9 +27,9 @@ use Error;
/// | | | | |
/// scheme authority path query fragment
/// ```
#[derive(Clone)]
#[derive(Clone, Hash)]
pub struct Uri {
source: InternalUri,
source: ByteStr,
scheme_end: Option<usize>,
authority_end: Option<usize>,
query: Option<usize>,
@@ -43,20 +38,23 @@ pub struct Uri {
impl Uri {
/// Parse a string into a `Uri`.
fn new(s: InternalUri) -> Result<Uri, Error> {
fn new(s: ByteStr) -> Result<Uri, UriError> {
if s.len() == 0 {
Err(Error::Uri(UrlError::RelativeUrlWithoutBase))
Err(UriError(ErrorKind::Empty))
} else if s.as_bytes() == b"*" {
// asterisk-form
Ok(Uri {
source: InternalUri::Cow("*".into()),
source: ByteStr::from_static("*"),
scheme_end: None,
authority_end: None,
query: None,
fragment: None,
})
} else if s.as_bytes() == b"/" {
// shortcut for '/'
Ok(Uri::default())
} else if s.as_bytes()[0] == b'/' {
// origin-form
let query = parse_query(&s);
let fragment = parse_fragment(&s);
Ok(Uri {
@@ -67,22 +65,14 @@ impl Uri {
fragment: fragment,
})
} else if s.contains("://") {
// absolute-form
let scheme = parse_scheme(&s);
let auth = parse_authority(&s);
if let Some(end) = scheme {
match &s[..end] {
"ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {},
"blob" | "file" => return Err(Error::Method),
_ => return Err(Error::Method),
}
match auth {
Some(a) => {
if (end + 3) == a {
return Err(Error::Method);
}
},
None => return Err(Error::Method),
}
let scheme_end = scheme.expect("just checked for ':' above");
let auth_end = auth.expect("just checked for ://");
if scheme_end + 3 == auth_end {
// authority was empty
return Err(UriError(ErrorKind::MissingAuthority));
}
let query = parse_query(&s);
let fragment = parse_fragment(&s);
@@ -94,8 +84,10 @@ impl Uri {
fragment: fragment,
})
} else if (s.contains("/") || s.contains("?")) && !s.contains("://") {
return Err(Error::Method)
// last possibility is authority-form, above are illegal characters
return Err(UriError(ErrorKind::Malformed))
} else {
// authority-form
let len = s.len();
Ok(Uri {
source: s,
@@ -220,25 +212,12 @@ fn parse_fragment(s: &str) -> Option<usize> {
}
impl FromStr for Uri {
type Err = Error;
type Err = UriError;
fn from_str(s: &str) -> Result<Uri, Error> {
fn from_str(s: &str) -> Result<Uri, UriError> {
//TODO: refactor such that the to_owned() is only required at the end
//of successful parsing, so an Err doesn't needlessly clone the string.
Uri::new(InternalUri::Cow(Cow::Owned(s.to_owned())))
}
}
impl From<Url> for Uri {
fn from(url: Url) -> Uri {
let s = InternalUri::Cow(Cow::Owned(url.into_string()));
// TODO: we could build the indices with some simple subtraction,
// instead of re-parsing an already parsed string.
// For example:
// let scheme_end = url[..url::Position::AfterScheme].as_ptr() as usize
// - url.as_str().as_ptr() as usize;
// etc
Uri::new(s).expect("Uri::From<Url> failed")
Uri::new(ByteStr::from(s))
}
}
@@ -259,7 +238,7 @@ impl AsRef<str> for Uri {
impl Default for Uri {
fn default() -> Uri {
Uri {
source: InternalUri::Cow("/".into()),
source: ByteStr::from_static("/"),
scheme_end: None,
authority_end: None,
query: None,
@@ -280,34 +259,67 @@ impl Display for Uri {
}
}
pub fn from_mem_str(s: ByteStr) -> Result<Uri, Error> {
Uri::new(InternalUri::Shared(s))
pub fn from_byte_str(s: ByteStr) -> Result<Uri, UriError> {
Uri::new(s)
}
#[derive(Clone)]
enum InternalUri {
Cow(Cow<'static, str>),
Shared(ByteStr),
pub fn scheme_and_authority(uri: &Uri) -> Option<Uri> {
if uri.scheme_end.is_some() {
Some(Uri {
source: uri.source.slice_to(uri.authority_end.expect("scheme without authority")),
scheme_end: uri.scheme_end,
authority_end: uri.authority_end,
query: None,
fragment: None,
})
} else {
None
}
}
impl InternalUri {
fn as_str(&self) -> &str {
match *self {
InternalUri::Cow(ref s) => s.as_ref(),
InternalUri::Shared(ref m) => m.as_str(),
pub fn origin_form(uri: &Uri) -> Uri {
let start = uri.authority_end.unwrap_or(uri.scheme_end.unwrap_or(0));
let end = if let Some(f) = uri.fragment {
uri.source.len() - f - 1
} else {
uri.source.len()
};
Uri {
source: uri.source.slice(start, end),
scheme_end: None,
authority_end: None,
query: uri.query,
fragment: None,
}
}
/// An error parsing a `Uri`.
#[derive(Clone, Debug)]
pub struct UriError(ErrorKind);
#[derive(Clone, Debug)]
enum ErrorKind {
Empty,
Malformed,
MissingAuthority,
}
impl fmt::Display for UriError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad(self.description())
}
}
impl StdError for UriError {
fn description(&self) -> &str {
match self.0 {
ErrorKind::Empty => "empty Uri string",
ErrorKind::Malformed => "invalid character in Uri authority",
ErrorKind::MissingAuthority => "absolute Uri missing authority segment",
}
}
}
impl Deref for InternalUri {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
macro_rules! test_parse {
(
$test_name:ident,
@@ -443,13 +455,3 @@ fn test_uri_parse_error() {
err("localhost/");
err("localhost?key=val");
}
#[test]
fn test_uri_from_url() {
let uri = Uri::from(Url::parse("http://test.com/nazghul?test=3").unwrap());
assert_eq!(uri.path(), "/nazghul");
assert_eq!(uri.authority(), Some("test.com"));
assert_eq!(uri.scheme(), Some("http"));
assert_eq!(uri.query(), Some("test=3"));
assert_eq!(uri.as_ref(), "http://test.com/nazghul?test=3");
}