Skip to content

Commit

Permalink
introduce a Uri Builder
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmonstar committed Oct 8, 2018
1 parent 0072b9d commit a64bd79
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 15 deletions.
21 changes: 20 additions & 1 deletion src/convert.rs
Expand Up @@ -3,7 +3,7 @@ use header::{HeaderName, HeaderValue};
use method::Method;
use sealed::Sealed;
use status::StatusCode;
use uri::Uri;
use uri::{Scheme, Authority, PathAndQuery, Uri};

/// Private trait for the `http` crate to have generic methods with fallible
/// conversions.
Expand All @@ -22,6 +22,22 @@ pub trait HttpTryFrom<T>: Sized + Sealed {
fn try_from(t: T) -> Result<Self, Self::Error>;
}

pub(crate) trait HttpTryInto<T>: Sized {
fn http_try_into(self) -> Result<T, Error>;
}

#[doc(hidden)]
impl<T, U> HttpTryInto<U> for T
where
U: HttpTryFrom<T>,
T: Sized,
{
fn http_try_into(self) -> Result<U, Error> {
HttpTryFrom::try_from(self)
.map_err(|e: U::Error| e.into())
}
}

macro_rules! reflexive {
($($t:ty,)*) => ($(
impl HttpTryFrom<$t> for $t {
Expand All @@ -42,4 +58,7 @@ reflexive! {
StatusCode,
HeaderName,
HeaderValue,
Scheme,
Authority,
PathAndQuery,
}
3 changes: 1 addition & 2 deletions src/request.rs
Expand Up @@ -725,8 +725,7 @@ impl fmt::Debug for Parts {
}

impl Builder {
/// Creates a new default instance of `Builder` to construct either a
/// `Head` or a `Request`.
/// Creates a new default instance of `Builder` to construct a `Request`.
///
/// # Examples
///
Expand Down
38 changes: 33 additions & 5 deletions src/uri/authority.rs
Expand Up @@ -8,6 +8,7 @@ use std::str::FromStr;
use bytes::Bytes;

use byte_str::ByteStr;
use convert::HttpTryFrom;
use super::{ErrorKind, InvalidUri, InvalidUriBytes, URI_CHARS, Port};

/// Represents the authority component of a URI.
Expand Down Expand Up @@ -397,17 +398,44 @@ impl Hash for Authority {
}
}

impl FromStr for Authority {
type Err = InvalidUri;
impl HttpTryFrom<Bytes> for Authority {
type Error = InvalidUriBytes;
#[inline]
fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
Authority::from_shared(bytes)
}
}

fn from_str(s: &str) -> Result<Self, InvalidUri> {
let end = Authority::parse_non_empty(s.as_bytes())?;
impl<'a> HttpTryFrom<&'a [u8]> for Authority {
type Error = InvalidUri;
#[inline]
fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
// parse first, and only turn into Bytes if valid
let end = Authority::parse_non_empty(s)?;

if end != s.len() {
return Err(ErrorKind::InvalidAuthority.into());
}

Ok(Authority { data: s.into() })
Ok(Authority {
data: unsafe { ByteStr::from_utf8_unchecked(s.into()) },
})
}
}

impl<'a> HttpTryFrom<&'a str> for Authority {
type Error = InvalidUri;
#[inline]
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
HttpTryFrom::try_from(s.as_bytes())
}
}

impl FromStr for Authority {
type Err = InvalidUri;

fn from_str(s: &str) -> Result<Self, InvalidUri> {
HttpTryFrom::try_from(s)
}
}

Expand Down
146 changes: 146 additions & 0 deletions src/uri/builder.rs
@@ -0,0 +1,146 @@
use {Uri, Result};
use convert::{HttpTryFrom, HttpTryInto};
use super::{Authority, Scheme, Parts, PathAndQuery};

/// A builder for `Uri`s.
///
/// This type can be used to construct an instance of `Uri`
/// through a builder pattern.
#[derive(Debug)]
pub struct Builder {
parts: Result<Parts>,
}

impl Builder {
/// Creates a new default instance of `Builder` to construct a `Uri`.
///
/// # Examples
///
/// ```
/// # use http::*;
///
/// let uri = uri::Builder::new()
/// .scheme("https")
/// .authority("hyper.rs")
/// .path_and_query("/")
/// .build()
/// .unwrap();
/// ```
#[inline]
pub fn new() -> Builder {
Builder::default()
}

/// Set the `Scheme` for this URI.
///
/// # Examples
///
/// ```
/// # use http::*;
///
/// let builder = uri::Builder::new()
/// .scheme("https");
/// ```
pub fn scheme<T>(self, scheme: T) -> Builder
where Scheme: HttpTryFrom<T>,
{
self.map(|parts| {
parts.scheme = Some(scheme.http_try_into()?);
Ok(())
})
}

/// Set the `Authority` for this URI.
///
/// # Examples
///
/// ```
/// # use http::*;
///
/// let builder = uri::Builder::new()
/// .authority("tokio.rs");
/// ```
pub fn authority<T>(self, auth: T) -> Builder
where Authority: HttpTryFrom<T>,
{
self.map(|parts| {
parts.authority = Some(auth.http_try_into()?);
Ok(())
})
}

/// Set the `PathAndQuery` for this URI.
///
/// # Examples
///
/// ```
/// # use http::*;
///
/// let builder = uri::Builder::new()
/// .path_and_query("/hello?foo=bar");
/// ```
pub fn path_and_query<T>(self, p_and_q: T) -> Builder
where PathAndQuery: HttpTryFrom<T>,
{
self.map(|parts| {
parts.path_and_query = Some(p_and_q.http_try_into()?);
Ok(())
})
}

/// Consumes this builder, and tries to construct a valid `Uri` from
/// the configured pieces.
///
/// # Errors
///
/// This function may return an error if any previously configured argument
/// failed to parse or get converted to the internal representation. For
/// example if an invalid `scheme` was specified via `scheme("!@#%/^")`
/// the error will be returned when this function is called rather than
/// when `scheme` was called.
///
/// Additionally, the various forms of URI require certain combinations of
/// parts to be set to be valid. If the parts don't fit into any of the
/// valid forms of URI, a new error is returned.
///
/// # Examples
///
/// ```
/// # use http::*;
///
/// let uri = Uri::builder()
/// .build()
/// .unwrap();
/// ```
pub fn build(self) -> Result<Uri> {
Ok(self
.parts?
.http_try_into()?)
}

fn map<F>(mut self, f: F) -> Builder
where F: FnOnce(&mut Parts) -> Result<()>,
{
let res = if let Ok(ref mut parts) = self.parts {
f(parts)
} else {
return self;
};

if let Err(err) = res {
self.parts = Err(err);
}

self
}
}

impl Default for Builder {
#[inline]
fn default() -> Builder {
Builder {
parts: Ok(Parts::default()),
}
}
}

23 changes: 23 additions & 0 deletions src/uri/mod.rs
Expand Up @@ -38,11 +38,13 @@ use std::error::Error;
use self::scheme::Scheme2;

pub use self::authority::Authority;
pub use self::builder::Builder;
pub use self::path::PathAndQuery;
pub use self::scheme::Scheme;
pub use self::port::Port;

mod authority;
mod builder;
mod path;
mod port;
mod scheme;
Expand Down Expand Up @@ -178,6 +180,27 @@ const URI_CHARS: [u8; 256] = [
];

impl Uri {
/// Creates a new builder-style object to manufacture a `Uri`.
///
/// This method returns an instance of `Builder` which can be usd to
/// create a `Uri`.
///
/// # Examples
///
/// ```
/// use http::Uri;
///
/// let uri = Uri::builder()
/// .scheme("https")
/// .authority("hyper.rs")
/// .path_and_query("/")
/// .build()
/// .unwrap();
/// ```
pub fn builder() -> Builder {
Builder::new()
}

/// Attempt to convert a `Uri` from `Parts`
pub fn from_parts(src: Parts) -> Result<Uri, InvalidUriParts> {
if src.scheme.is_some() {
Expand Down
29 changes: 27 additions & 2 deletions src/uri/path.rs
Expand Up @@ -4,6 +4,7 @@ use std::str::FromStr;
use bytes::Bytes;

use byte_str::ByteStr;
use convert::HttpTryFrom;
use super::{ErrorKind, InvalidUri, InvalidUriBytes, URI_CHARS};

/// Represents the path component of a URI
Expand Down Expand Up @@ -250,11 +251,35 @@ impl PathAndQuery {
}
}

impl HttpTryFrom<Bytes> for PathAndQuery {
type Error = InvalidUriBytes;
#[inline]
fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
PathAndQuery::from_shared(bytes)
}
}

impl<'a> HttpTryFrom<&'a [u8]> for PathAndQuery {
type Error = InvalidUri;
#[inline]
fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
PathAndQuery::from_shared(s.into()).map_err(|e| e.0)
}
}

impl<'a> HttpTryFrom<&'a str> for PathAndQuery {
type Error = InvalidUri;
#[inline]
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
HttpTryFrom::try_from(s.as_bytes())
}
}

impl FromStr for PathAndQuery {
type Err = InvalidUri;

#[inline]
fn from_str(s: &str) -> Result<Self, InvalidUri> {
PathAndQuery::from_shared(s.into()).map_err(|e| e.0)
HttpTryFrom::try_from(s)
}
}

Expand Down

0 comments on commit a64bd79

Please sign in to comment.