introduce a Uri Builder (#219)

This commit is contained in:
Sean McArthur
2018-10-08 13:54:43 -07:00
committed by GitHub
parent ec157886b1
commit 048de7a894
7 changed files with 297 additions and 19 deletions
+20 -1
View File
@@ -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.
@@ -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 {
@@ -42,4 +58,7 @@ reflexive! {
StatusCode,
HeaderName,
HeaderValue,
Scheme,
Authority,
PathAndQuery,
}
+1 -2
View File
@@ -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
///
+33 -5
View File
@@ -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.
@@ -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)
}
}
+156
View File
@@ -0,0 +1,156 @@
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: Option<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 mut builder = uri::Builder::new();
/// builder.scheme("https");
/// ```
pub fn scheme<T>(&mut self, scheme: T) -> &mut Self
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 uri = uri::Builder::new()
/// .authority("tokio.rs")
/// .build()
/// .unwrap();
/// ```
pub fn authority<T>(&mut self, auth: T) -> &mut Self
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 uri = uri::Builder::new()
/// .path_and_query("/hello?foo=bar")
/// .build()
/// .unwrap();
/// ```
pub fn path_and_query<T>(&mut self, p_and_q: T) -> &mut Self
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(&mut self) -> Result<Uri> {
self
.parts
.take()
.expect("cannot reuse Uri builder")
.and_then(|parts| parts.http_try_into())
}
fn map<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&mut Parts) -> Result<()>,
{
let res = if let Some(Ok(ref mut parts)) = self.parts {
f(parts)
} else {
return self;
};
if let Err(err) = res {
self.parts = Some(Err(err));
}
self
}
}
impl Default for Builder {
#[inline]
fn default() -> Builder {
Builder {
parts: Some(Ok(Parts::default())),
}
}
}
+23
View File
@@ -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;
@@ -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() {
+27 -2
View File
@@ -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
@@ -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)
}
}
+37 -9
View File
@@ -8,6 +8,7 @@ use std::str::FromStr;
use bytes::Bytes;
use byte_str::ByteStr;
use convert::HttpTryFrom;
use super::{ErrorKind, InvalidUri, InvalidUriBytes};
/// Represents the scheme component of a URI
@@ -109,19 +110,46 @@ impl Scheme {
}
}
impl HttpTryFrom<Bytes> for Scheme {
type Error = InvalidUriBytes;
#[inline]
fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
Scheme::from_shared(bytes)
}
}
impl<'a> HttpTryFrom<&'a [u8]> for Scheme {
type Error = InvalidUri;
#[inline]
fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
use self::Scheme2::*;
match Scheme2::parse_exact(s)? {
None => Err(ErrorKind::InvalidScheme.into()),
Standard(p) => Ok(Standard(p).into()),
Other(_) => {
// Unsafe: parse_exact already checks for a strict subset of UTF-8
Ok(Other(Box::new(unsafe {
ByteStr::from_utf8_unchecked(s.into())
})).into())
}
}
}
}
impl<'a> HttpTryFrom<&'a str> for Scheme {
type Error = InvalidUri;
#[inline]
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
HttpTryFrom::try_from(s.as_bytes())
}
}
impl FromStr for Scheme {
type Err = InvalidUri;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::Scheme2::*;
match Scheme2::parse_exact(s.as_bytes())? {
None => Err(ErrorKind::InvalidScheme.into()),
Standard(p) => Ok(Standard(p).into()),
Other(_) => {
Ok(Other(Box::new(s.into())).into())
}
}
HttpTryFrom::try_from(s)
}
}