From 4fb7e6ebc6b1d429dcce4bc18139bd443fffa6ee Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Tue, 21 Mar 2017 10:35:31 -0700 Subject: [PATCH] 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. --- benches/end_to_end.rs | 4 +- examples/client.rs | 4 +- src/client/connect.rs | 23 ++-- src/client/mod.rs | 26 +++-- src/client/request.rs | 25 ++-- src/error.rs | 31 ++--- src/header/common/content_disposition.rs | 6 +- src/header/parsing.rs | 44 ++++--- src/http/h1/parse.rs | 2 +- src/http/str.rs | 31 ++++- src/lib.rs | 3 +- src/uri.rs | 140 ++++++++++++----------- 12 files changed, 188 insertions(+), 151 deletions(-) diff --git a/benches/end_to_end.rs b/benches/end_to_end.rs index efad83d32b..fca49e5862 100644 --- a/benches/end_to_end.rs +++ b/benches/end_to_end.rs @@ -28,7 +28,7 @@ fn get_one_at_a_time(b: &mut test::Bencher) { let client = hyper::Client::new(&handle); - let url: hyper::Url = format!("http://{}/get", addr).parse().unwrap(); + let url: hyper::Uri = format!("http://{}/get", addr).parse().unwrap(); b.bytes = 160 * 2 + PHRASE.len() as u64; b.iter(move || { @@ -51,7 +51,7 @@ fn post_one_at_a_time(b: &mut test::Bencher) { let client = hyper::Client::new(&handle); - let url: hyper::Url = format!("http://{}/get", addr).parse().unwrap(); + let url: hyper::Uri = format!("http://{}/get", addr).parse().unwrap(); let post = "foo bar baz quux"; b.bytes = 180 * 2 + post.len() as u64 + PHRASE.len() as u64; diff --git a/examples/client.rs b/examples/client.rs index 698cbbfde1..ccaaa2e989 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -24,8 +24,8 @@ fn main() { } }; - let url = hyper::Url::parse(&url).unwrap(); - if url.scheme() != "http" { + let url = url.parse::().unwrap(); + if url.scheme() != Some("http") { println!("This example only works with 'http' URLs."); return; } diff --git a/src/client/connect.rs b/src/client/connect.rs index f176a4d34c..8376287a57 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -7,7 +7,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio::reactor::Handle; use tokio::net::{TcpStream, TcpStreamNew}; use tokio_service::Service; -use Url; +use Uri; use super::dns; @@ -15,25 +15,25 @@ use super::dns; /// /// This trait is not implemented directly, and only exists to make /// the intent clearer. A connector should implement `Service` with -/// `Request=Url` and `Response: Io` instead. -pub trait Connect: Service + 'static { +/// `Request=Uri` and `Response: Io` instead. +pub trait Connect: Service + 'static { /// The connected Io Stream. type Output: AsyncRead + AsyncWrite + 'static; /// A Future that will resolve to the connected Stream. type Future: Future + 'static; /// Connect to a remote address. - fn connect(&self, Url) -> ::Future; + fn connect(&self, Uri) -> ::Future; } impl Connect for T -where T: Service + 'static, +where T: Service + 'static, T::Response: AsyncRead + AsyncWrite, T::Future: Future, { type Output = T::Response; type Future = T::Future; - fn connect(&self, url: Url) -> ::Future { + fn connect(&self, url: Uri) -> ::Future { self.call(url) } } @@ -66,21 +66,21 @@ impl fmt::Debug for HttpConnector { } impl Service for HttpConnector { - type Request = Url; + type Request = Uri; type Response = TcpStream; type Error = io::Error; type Future = HttpConnecting; - fn call(&self, url: Url) -> Self::Future { + fn call(&self, url: Uri) -> Self::Future { debug!("Http::connect({:?})", url); - let host = match url.host_str() { + let host = match url.host() { Some(s) => s, None => return HttpConnecting { state: State::Error(Some(io::Error::new(io::ErrorKind::InvalidInput, "invalid url"))), handle: self.handle.clone(), }, }; - let port = url.port_or_known_default().unwrap_or(80); + let port = url.port().unwrap_or(80); HttpConnecting { state: State::Resolving(self.dns.resolve(host.into(), port)), @@ -185,13 +185,12 @@ impl HttpsConnector { mod tests { use std::io; use tokio::reactor::Core; - use Url; use super::{Connect, HttpConnector}; #[test] fn test_non_http_url() { let mut core = Core::new().unwrap(); - let url = Url::parse("file:///home/sean/foo.txt").unwrap(); + let url = "/foo/bar?baz".parse().unwrap(); let connector = HttpConnector::new(1, &core.handle()); assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput); diff --git a/src/client/mod.rs b/src/client/mod.rs index ffc7b6156d..17f4416290 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -10,7 +10,7 @@ use std::marker::PhantomData; use std::rc::Rc; use std::time::Duration; -use futures::{Poll, Async, Future, Stream}; +use futures::{future, Poll, Async, Future, Stream}; use futures::unsync::oneshot; use tokio_io::{AsyncRead, AsyncWrite}; use tokio::reactor::Handle; @@ -24,7 +24,7 @@ use header::{Headers, Host}; use http::{self, TokioBody}; use method::Method; use self::pool::{Pool, Pooled}; -use Url; +use uri::{self, Uri}; pub use self::connect::{HttpConnector, Connect}; pub use self::request::Request; @@ -95,7 +95,7 @@ where C: Connect, { /// Send a GET Request using this Client. #[inline] - pub fn get(&self, url: Url) -> FutureResponse { + pub fn get(&self, url: Uri) -> FutureResponse { self.request(Request::new(Method::Get, url)) } @@ -135,18 +135,30 @@ where C: Connect, type Future = FutureResponse; fn call(&self, req: Self::Request) -> Self::Future { - let url = req.url().clone(); + let url = req.uri().clone(); + let domain = match uri::scheme_and_authority(&url) { + Some(uri) => uri, + None => { + return FutureResponse(Box::new(future::err(::Error::Io( + io::Error::new( + io::ErrorKind::InvalidInput, + "invalid URI for Client Request" + ) + )))); + } + }; + let host = Host::new(domain.host().expect("authority implies host").to_owned(), domain.port()); let (mut head, body) = request::split(req); let mut headers = Headers::new(); - headers.set(Host::new(url.host_str().unwrap().to_owned(), url.port())); + headers.set(host); headers.extend(head.headers.iter()); head.headers = headers; - let checkout = self.pool.checkout(&url[..::url::Position::BeforePath]); + let checkout = self.pool.checkout(domain.as_ref()); let connect = { let handle = self.handle.clone(); let pool = self.pool.clone(); - let pool_key = Rc::new(url[..::url::Position::BeforePath].to_owned()); + let pool_key = Rc::new(domain.to_string()); self.connector.connect(url) .map(move |io| { let (tx, rx) = oneshot::channel(); diff --git a/src/client/request.rs b/src/client/request.rs index 31b7104e09..2128f76d45 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -1,18 +1,15 @@ use std::fmt; -use Url; - use header::Headers; use http::{Body, RequestHead}; use method::Method; -use uri::Uri; +use uri::{self, Uri}; use version::HttpVersion; -use std::str::FromStr; /// A client request to a remote server. pub struct Request { method: Method, - url: Url, + uri: Uri, version: HttpVersion, headers: Headers, body: Option, @@ -22,10 +19,10 @@ pub struct Request { impl Request { /// Construct a new Request. #[inline] - pub fn new(method: Method, url: Url) -> Request { + pub fn new(method: Method, uri: Uri) -> Request { Request { method: method, - url: url, + uri: uri, version: HttpVersion::default(), headers: Headers::new(), body: None, @@ -33,9 +30,9 @@ impl Request { } } - /// Read the Request Url. + /// Read the Request Uri. #[inline] - pub fn url(&self) -> &Url { &self.url } + pub fn uri(&self) -> &Uri { &self.uri } /// Read the Request Version. #[inline] @@ -57,9 +54,9 @@ impl Request { #[inline] pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers } - /// Set the `Url` of this request. + /// Set the `Uri` of this request. #[inline] - pub fn set_url(&mut self, url: Url) { self.url = url; } + pub fn set_uri(&mut self, uri: Uri) { self.uri = uri; } /// Set the `HttpVersion` of this request. #[inline] @@ -81,7 +78,7 @@ impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Request") .field("method", &self.method) - .field("url", &self.url) + .field("uri", &self.uri) .field("version", &self.version) .field("headers", &self.headers) .finish() @@ -90,9 +87,9 @@ impl fmt::Debug for Request { pub fn split(req: Request) -> (RequestHead, Option) { let uri = if req.is_proxy { - Uri::from(req.url) + req.uri } else { - Uri::from_str(&req.url[::url::Position::BeforePath..::url::Position::AfterQuery]).expect("url is not uri") + uri::origin_form(&req.uri) }; let head = RequestHead { subject: ::http::RequestLine(req.method, uri), diff --git a/src/error.rs b/src/error.rs index ec3691524a..ecff601523 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,7 +6,8 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; use httparse; -use url; + +pub use uri::UriError; use self::Error::{ Method, @@ -21,8 +22,6 @@ use self::Error::{ Utf8 }; -pub use url::ParseError; - /// Result type often returned from methods that can have hyper `Error`s. pub type Result = ::std::result::Result; @@ -32,7 +31,7 @@ pub enum Error { /// An invalid `Method`, such as `GE,T`. Method, /// An invalid `Uri`, such as `exam ple.domain`. - Uri(url::ParseError), + Uri(UriError), /// An invalid `HttpVersion`, such as `HTP/1.1` Version, /// An invalid `Header`. @@ -102,15 +101,15 @@ impl StdError for Error { } } -impl From for Error { - fn from(err: IoError) -> Error { - Io(err) +impl From for Error { + fn from(err: UriError) -> Error { + Uri(err) } } -impl From for Error { - fn from(err: url::ParseError) -> Error { - Uri(err) +impl From for Error { + fn from(err: IoError) -> Error { + Io(err) } } @@ -145,7 +144,6 @@ mod tests { use std::error::Error as StdError; use std::io; use httparse; - use url; use super::Error; use super::Error::*; @@ -185,7 +183,6 @@ mod tests { fn test_from() { from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..)); - from_and_cause!(url::ParseError::EmptyHost => Uri(..)); from!(httparse::Error::HeaderName => Header); from!(httparse::Error::HeaderName => Header); @@ -196,14 +193,4 @@ mod tests { from!(httparse::Error::TooManyHeaders => TooLarge); from!(httparse::Error::Version => Version); } - - #[cfg(feature = "openssl")] - #[test] - fn test_from_ssl() { - use openssl::ssl::error::SslError; - - from!(SslError::StreamError( - io::Error::new(io::ErrorKind::Other, "ssl negotiation")) => Io(..)); - from_and_cause!(SslError::SslSessionClosed => Ssl(..)); - } } diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index ca3abb2333..3d0bde44bc 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -9,10 +9,9 @@ use language_tags::LanguageTag; use std::fmt; use unicase::UniCase; -use url::percent_encoding; use header::{Header, Raw, parsing}; -use header::parsing::{parse_extended_value, HTTP_VALUE}; +use header::parsing::{parse_extended_value, http_percent_encode}; use header::shared::Charset; /// The implied disposition of the content of the HTTP body @@ -182,8 +181,7 @@ impl fmt::Display for ContentDisposition { try!(write!(f, "{}", lang)); }; try!(write!(f, "'")); - try!(f.write_str( - &percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string())) + try!(http_percent_encode(f, bytes)) } }, DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)), diff --git a/src/header/parsing.rs b/src/header/parsing.rs index 8caecf78ea..84efe44a52 100644 --- a/src/header/parsing.rs +++ b/src/header/parsing.rs @@ -9,6 +9,7 @@ use url::percent_encoding; use header::Raw; use header::shared::Charset; + /// Reads a single raw string when parsing a header. pub fn from_one_raw_str(raw: &Raw) -> ::Result { if let Some(line) = raw.one() { @@ -132,25 +133,11 @@ pub fn parse_extended_value(val: &str) -> ::Result { }) } -define_encode_set! { - /// This encode set is used for HTTP header values and is defined at - /// https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } -} - -impl fmt::Debug for HTTP_VALUE { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.pad("HTTP_VALUE") - } -} impl Display for ExtendedValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let encoded_value = - percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); + percent_encoding::percent_encode(&self.value[..], self::percent_encoding_http::HTTP_VALUE); if let Some(ref lang) = self.language_tag { write!(f, "{}'{}'{}", self.charset, lang, encoded_value) } else { @@ -159,6 +146,33 @@ impl Display for ExtendedValue { } } +/// Percent encode a sequence of bytes with a character set defined in +/// https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} + +mod percent_encoding_http { + use std::fmt; + use url::percent_encoding; + + define_encode_set! { + /// This encode set is used for HTTP header values and is defined at + /// https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } + + impl fmt::Debug for HTTP_VALUE { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad("HTTP_VALUE") + } + } +} + #[cfg(test)] mod tests { use header::shared::Charset; diff --git a/src/http/h1/parse.rs b/src/http/h1/parse.rs index 780ea3c6ca..b6e85cb100 100644 --- a/src/http/h1/parse.rs +++ b/src/http/h1/parse.rs @@ -61,7 +61,7 @@ impl Http1Transaction for ServerTransaction { let path = unsafe { ByteStr::from_utf8_unchecked(path) }; let subject = RequestLine( method, - try!(::uri::from_mem_str(path)), + try!(::uri::from_byte_str(path)), ); headers.extend(HeadersAsBytesIter { diff --git a/src/http/str.rs b/src/http/str.rs index eb9f94c0d8..4479a01bb5 100644 --- a/src/http/str.rs +++ b/src/http/str.rs @@ -1,8 +1,9 @@ +use std::ops::Deref; use std::str; use bytes::Bytes; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ByteStr(Bytes); impl ByteStr { @@ -10,7 +11,35 @@ impl ByteStr { ByteStr(slice) } + pub fn from_static(s: &'static str) -> ByteStr { + ByteStr(Bytes::from_static(s.as_bytes())) + } + + pub fn slice(&self, from: usize, to: usize) -> ByteStr { + assert!(self.as_str().is_char_boundary(from)); + assert!(self.as_str().is_char_boundary(to)); + ByteStr(self.0.slice(from, to)) + } + + pub fn slice_to(&self, idx: usize) -> ByteStr { + assert!(self.as_str().is_char_boundary(idx)); + ByteStr(self.0.slice_to(idx)) + } + pub fn as_str(&self) -> &str { unsafe { str::from_utf8_unchecked(self.0.as_ref()) } } } + +impl Deref for ByteStr { + type Target = str; + fn deref(&self) -> &str { + self.as_str() + } +} + +impl<'a> From<&'a str> for ByteStr { + fn from(s: &'a str) -> ByteStr { + ByteStr(Bytes::from(s)) + } +} diff --git a/src/lib.rs b/src/lib.rs index c6d65121d7..5ffd15602a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![doc(html_root_url = "https://hyperium.github.io/hyper/")] #![deny(missing_docs)] -//#![deny(warnings)] +#![deny(warnings)] #![deny(missing_debug_implementations)] #![cfg_attr(all(test, feature = "nightly"), feature(test))] @@ -35,7 +35,6 @@ extern crate test; pub use uri::Uri; -pub use url::Url; pub use client::Client; pub use error::{Result, Error}; pub use header::Headers; diff --git a/src/uri.rs b/src/uri.rs index b54754e461..6afe7bda8a 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -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, authority_end: Option, query: Option, @@ -43,20 +38,23 @@ pub struct Uri { impl Uri { /// Parse a string into a `Uri`. - fn new(s: InternalUri) -> Result { + fn new(s: ByteStr) -> Result { 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 { } impl FromStr for Uri { - type Err = Error; + type Err = UriError; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { //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 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 failed") + Uri::new(ByteStr::from(s)) } } @@ -259,7 +238,7 @@ impl AsRef 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,33 +259,66 @@ impl Display for Uri { } } -pub fn from_mem_str(s: ByteStr) -> Result { - Uri::new(InternalUri::Shared(s)) +pub fn from_byte_str(s: ByteStr) -> Result { + Uri::new(s) } -#[derive(Clone)] -enum InternalUri { - Cow(Cow<'static, str>), - Shared(ByteStr), +pub fn scheme_and_authority(uri: &Uri) -> Option { + 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, } } -impl Deref for InternalUri { - type Target = str; +/// An error parsing a `Uri`. +#[derive(Clone, Debug)] +pub struct UriError(ErrorKind); - fn deref(&self) -> &str { - self.as_str() +#[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", + } + } +} macro_rules! test_parse { ( @@ -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"); -}