diff --git a/src/request.rs b/src/request.rs index 37c00129..3a9395f8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -9,26 +9,28 @@ //! //! Creating a `Request` to send //! -//! ```no_run -//! use http::{Request, Response}; +//! ``` +//! use http::{HeaderValue, Request, Response, header}; //! -//! let mut request = Request::builder() -//! .uri("https://www.rust-lang.org/") -//! .header("User-Agent", "my-awesome-agent/1.0"); +//! # fn main() -> http::Result<()> { +//! let mut request = Request::builder2() +//! .try_uri("https://www.rust-lang.org/")? +//! .header(header::USER_AGENT, HeaderValue::from_static("my-awesome-agent/1.0")); //! //! if needs_awesome_header() { -//! request = request.header("Awesome", "yes"); +//! request = request.try_header("Awesome", "yes")?; //! } //! -//! let response = send(request.body(()).unwrap()); -//! +//! let response = send(request.body(())); +//! # Ok(()) +//! # } //! # fn needs_awesome_header() -> bool { //! # true //! # } -//! # +//! //! fn send(req: Request<()>) -> Response<()> { //! // ... -//! # panic!() +//! # Response::builder().body(()).unwrap() //! } //! ``` //! @@ -72,26 +74,28 @@ use crate::{Extensions, Result, Uri}; /// /// Creating a `Request` to send /// -/// ```no_run -/// use http::{Request, Response}; +/// ``` +/// use http::{HeaderValue, Request, Response, header}; /// -/// let mut request = Request::builder() -/// .uri("https://www.rust-lang.org/") -/// .header("User-Agent", "my-awesome-agent/1.0"); +/// # fn main() -> http::Result<()> { +/// let mut request = Request::builder2() +/// .try_uri("https://www.rust-lang.org/")? +/// .header(header::USER_AGENT, HeaderValue::from_static("my-awesome-agent/1.0")); /// /// if needs_awesome_header() { -/// request = request.header("Awesome", "yes"); +/// request = request.try_header("Awesome", "yes")?; /// } /// -/// let response = send(request.body(()).unwrap()); -/// +/// let response = send(request.body(())); +/// # Ok(()) +/// # } /// # fn needs_awesome_header() -> bool { /// # true /// # } -/// # +/// /// fn send(req: Request<()>) -> Response<()> { /// // ... -/// # panic!() +/// # Response::builder().body(()).unwrap() /// } /// ``` /// @@ -209,6 +213,7 @@ impl Request<()> { /// .unwrap(); /// ``` #[inline] + #[deprecated(note="Please use builder2")] pub fn builder() -> Builder { Builder::new() } @@ -227,6 +232,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use get2")] pub fn get(uri: T) -> Builder where Uri: TryFrom, @@ -250,6 +256,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use put2")] pub fn put(uri: T) -> Builder where Uri: TryFrom, @@ -273,6 +280,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use post2")] pub fn post(uri: T) -> Builder where Uri: TryFrom, @@ -296,6 +304,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use delete2")] pub fn delete(uri: T) -> Builder where Uri: TryFrom, @@ -320,6 +329,7 @@ impl Request<()> { /// .unwrap(); /// # assert_eq!(*request.method(), Method::OPTIONS); /// ``` + #[deprecated(note="Please use options2")] pub fn options(uri: T) -> Builder where Uri: TryFrom, @@ -343,6 +353,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use head2")] pub fn head(uri: T) -> Builder where Uri: TryFrom, @@ -366,6 +377,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use connect2")] pub fn connect(uri: T) -> Builder where Uri: TryFrom, @@ -389,6 +401,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use patch2")] pub fn patch(uri: T) -> Builder where Uri: TryFrom, @@ -411,6 +424,7 @@ impl Request<()> { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note="Please use trace2")] pub fn trace(uri: T) -> Builder where Uri: TryFrom, @@ -420,6 +434,212 @@ impl Request<()> { } } +/// An HTTP request builder +/// +/// This type can be used to construct an instance or `Request` +/// through a builder-like pattern. +#[derive(Debug)] +pub struct Builder2 { + inner: Parts, +} + +impl Request<()> { + /// Creates a new builder-style object to manufacture a `Request` + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # fn main() -> Result<()> { + /// let request = Request::builder2() + /// .method(Method::GET) + /// .uri(Uri::from_static("https://www.rust-lang.org/")) + /// .try_header("X-Custom-Foo", "Bar")? + /// .body(()); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn builder2() -> Builder2 { + Builder2::new() + } + + /// Creates a new builder initialized with a GET method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::get2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn get2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::GET).uri(uri) + } + + /// Creates a new builder initialized with a PUT method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::put2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn put2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::PUT).uri(uri) + } + + /// Creates a new builder initialized with a POST method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::post2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn post2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::POST).uri(uri) + } + + /// Creates a new builder initialized with a DELETE method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::delete2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn delete2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::DELETE).uri(uri) + } + + /// Creates a new builder initialized with an OPTIONS method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::options2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// assert_eq!(request.method(), Method::OPTIONS); + /// ``` + pub fn options2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::OPTIONS).uri(uri) + } + + /// Creates a new builder initialized with a HEAD method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used + /// to create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::head2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn head2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::HEAD).uri(uri) + } + + /// Creates a new builder initialized with a CONNECT method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::connect2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn connect2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::CONNECT).uri(uri) + } + + /// Creates a new builder initialized with a PATCH method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::patch2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn patch2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::PATCH).uri(uri) + } + + /// Creates a new builder initialized with a TRACE method and the given URI. + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Request`. + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// let request = Request::trace2(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn trace2(uri: T) -> Builder2 + where + Uri: From, + { + Builder2::new().method(Method::TRACE).uri(uri) + } +} + impl Request { /// Creates a new blank `Request` with the body /// @@ -748,7 +968,6 @@ impl Builder { /// /// ``` /// # use http::*; - /// /// let req = request::Builder::new() /// .method("POST") /// .body(()) @@ -1061,13 +1280,358 @@ impl Default for Builder { } } +impl Builder2 { + /// Creates a new default instance of `Builder2` to construct a `Request`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let req = request::Builder2::new() + /// .method(Method::POST) + /// .body(()); + /// ``` + #[inline] + pub fn new() -> Builder2 { + Builder2::default() + } + + /// Set the HTTP method for this request. + /// + /// This function will configure the HTTP method of the `Request` that will + /// be returned from `Builder2::build`. + /// + /// By default this is `GET`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let req = Request::builder2() + /// .method(Method::POST) + /// .body(()); + /// ``` + pub fn method(mut self, method: T) -> Builder2 + where + Method: From, + { + self.inner.method = method.into(); + self + } + + /// Get the HTTP Method for this request. + /// + /// By default this is `GET`. If builder has error, returns None. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let mut req = Request::builder2(); + /// assert_eq!(req.method_ref(), Method::GET); + /// + /// req = req.method(Method::POST); + /// assert_eq!(req.method_ref(), Method::POST); + /// ``` + pub fn method_ref(&self) -> &Method { + &self.inner.method + } + + /// Set the URI for this request. + /// + /// This function will configure the URI of the `Request` that will + /// be returned from `Builder2::build`. + /// + /// By default this is `/`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let req = Request::builder2() + /// .uri(Uri::from_static("https://www.rust-lang.org/")) + /// .body(()); + /// ``` + pub fn uri(mut self, uri: T) -> Builder2 + where + Uri: From, + { + self.inner.uri = uri.into(); + self + } + + /// Set the URI for this request. + /// + /// This function will configure the URI of the `Request` that will + /// be returned from `Builder2::build`. + /// + /// By default this is `/`. + /// + /// # Errors + /// + /// This method does a fallible conversion, and returns an error if + /// the conversion fails. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # fn main() -> Result<()> { + /// let req = Request::builder2() + /// .try_uri("https://www.rust-lang.org/")? + /// .body(()); + /// # Ok(()) + /// # } + /// ``` + pub fn try_uri(mut self, uri: T) -> Result + where + Uri: TryFrom, + crate::Error: From<>::Error>, + { + self.inner.uri = Uri::try_from(uri)?; + Ok(self) + } + + /// Get the URI for this request + /// + /// By default this is `/`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # fn main() -> Result<()> { + /// let mut req = Request::builder2(); + /// assert_eq!(req.uri_ref(), "/" ); + /// + /// req = req.try_uri("https://www.rust-lang.org/")?; + /// assert_eq!(req.uri_ref(), "https://www.rust-lang.org/"); + /// # Ok(()) + /// # } + /// ``` + pub fn uri_ref(&self) -> &Uri { + &self.inner.uri + } + + /// Set the HTTP version for this request. + /// + /// This function will configure the HTTP version of the `Request` that + /// will be returned from `Builder2::build`. + /// + /// By default this is HTTP/1.1 + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let req = Request::builder2() + /// .version(Version::HTTP_2) + /// .body(()); + /// ``` + pub fn version(mut self, version: Version) -> Builder2 { + self.inner.version = version; + self + } + + /// Appends a header to this request builder. + /// + /// This function will append the provided key/value as a header to the + /// internal `HeaderMap` being constructed. Essentially this is equivalent + /// to calling `HeaderMap::append`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # use http::header::{HeaderName, HeaderValue}; + /// # #[allow(non_snake_case)] // look like mime::TEXT_HTML + /// # let TEXT_HTML: HeaderValue = HeaderValue::from_static("text/html"); + /// + /// let req = Request::builder2() + /// .header(header::ACCEPT, TEXT_HTML) + /// .header( + /// HeaderName::from_static("x-custom-foo"), + /// HeaderValue::from_static("bar"), + /// ) + /// .body(()); + /// ``` + pub fn header(mut self, key: K, value: V) -> Builder2 + where + HeaderName: From, + HeaderValue: From, + { + self.inner.headers.append(HeaderName::from(key), value.into()); + self + } + + /// Appends a header to this request builder. + /// + /// This function will append the provided key/value as a header to the + /// internal `HeaderMap` being constructed. Essentially this is equivalent + /// to calling `HeaderMap::append`. + /// + /// # Errors + /// + /// This method does fallible conversions, and returns an error if + /// one of the conversions fail. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # fn main() -> Result<()> { + /// let req = Request::builder2() + /// .try_header("Accept", "text/html")? + /// .try_header("X-Custom-Foo", "bar")? + /// .body(()); + /// # Ok(()) + /// # } + /// ``` + pub fn try_header(mut self, key: K, value: V) -> Result + where + HeaderName: TryFrom, + crate::Error: From<>::Error>, + HeaderValue: TryFrom, + crate::Error: From<>::Error>, + { + let name = HeaderName::try_from(key)?; + let value = HeaderValue::try_from(value)?; + self.inner.headers.append(name, value); + Ok(self) + } + + /// Get header on this request builder. + /// when builder has error returns None + /// + /// # Example + /// + /// ``` + /// # use http::Request; + /// # fn main() -> Result<(), http::Error> { + /// let req = Request::builder2() + /// .try_header("Accept", "text/html")? + /// .try_header("X-Custom-Foo", "bar")?; + /// let headers = req.headers_ref(); + /// assert_eq!(headers["Accept"], "text/html"); + /// assert_eq!(headers["X-Custom-Foo"], "bar"); + /// # Ok(()) + /// # } + /// ``` + pub fn headers_ref(&self) -> &HeaderMap { + &self.inner.headers + } + + /// Get headers on this request builder. + /// + /// # Example + /// + /// ``` + /// # use http::{header::HeaderValue, Request}; + /// let mut req = Request::builder2(); + /// { + /// let headers = req.headers_mut(); + /// headers.insert("Accept", HeaderValue::from_static("text/html")); + /// headers.insert("X-Custom-Foo", HeaderValue::from_static("bar")); + /// } + /// let headers = req.headers_ref(); + /// assert_eq!( headers["Accept"], "text/html" ); + /// assert_eq!( headers["X-Custom-Foo"], "bar" ); + /// ``` + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.inner.headers + } + + /// Adds an extension to this builder + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let req = Request::builder2() + /// .extension("My Extension") + /// .body(()); + /// + /// assert_eq!(req.extensions().get::<&'static str>(), + /// Some(&"My Extension")); + /// ``` + pub fn extension(mut self, extension: T) -> Builder2 + where + T: Any + Send + Sync + 'static, + { + self.inner.extensions.insert(extension); + self + } + + /// Get a reference to the extensions for this request builder. + /// + /// # Example + /// + /// ``` + /// # use http::Request; + /// let req = Request::builder2() + /// .extension("My Extension") + /// .extension(5u32); + /// let extensions = req.extensions_ref(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_ref(&self) -> &Extensions { + &self.inner.extensions + } + + /// Get a mutable reference to the extensions for this request builder. + /// + /// # Example + /// + /// ``` + /// # use http::Request; + /// let mut req = Request::builder2().extension("My Extension"); + /// let mut extensions = req.extensions_mut(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// extensions.insert(5u32); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_mut(&mut self) -> &mut Extensions { + &mut self.inner.extensions + } + + /// "Consumes" this builder, using the provided `body` to return a + /// constructed `Request`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let request = Request::builder2() + /// .body(()); + /// ``` + pub fn body(self, body: T) -> Request { + Request { + head: self.inner, + body, + } + } +} + +impl Default for Builder2 { + #[inline] + fn default() -> Builder2 { + Builder2 { + inner: Parts::new(), + } + } +} + #[cfg(test)] mod tests { use super::*; #[test] fn it_can_map_a_body_from_one_type_to_another() { - let request = Request::builder().body("some string").unwrap(); + let request = Request::builder2().body("some string"); let mapped_request = request.map(|s| { assert_eq!(s, "some string"); 123u32 diff --git a/src/response.rs b/src/response.rs index 07efd847..e919da56 100644 --- a/src/response.rs +++ b/src/response.rs @@ -205,16 +205,45 @@ pub struct Parts { /// /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. +/// This builder can represent an erroneous state, so finalizing +/// it (with the `.body` method) may return an error. +/// +/// See also [`Builder2`]. #[derive(Debug)] +// Note: rustc 1.39.0 does some use I can't find, so I can't deprecate +// the type. Instead, I have just deprecated every function that +// returns or consumes a Builder. +//#[deprecated(note = "Please use Builder2, it will replace Builder")] pub struct Builder { inner: Result, } +/// An HTTP response builder +/// +/// This type can be used to construct an instance of `Response` through a +/// builder-like pattern. +/// +/// This builder can not represent an erroneous state, so as long +/// as you have a `Builder2` you can get a `Response`. +/// Most methods on this builder is guaranteed to return a builder. +/// The exception, `try_header()`, is explicit by returning a +/// `Result`. +/// +/// See also [`Builder`] +#[derive(Debug)] +pub struct Builder2 { + inner: Parts, +} + impl Response<()> { /// Creates a new builder-style object to manufacture a `Response` /// /// This method returns an instance of `Builder` which can be used to - /// create a `Response`. + /// create a `Result`. + /// This builder can represent an erroneous state, so finalizing + /// it (with the `.body` method) may return an error. + /// + /// See also [`builder2`](#method.builder2). /// /// # Examples /// @@ -227,9 +256,35 @@ impl Response<()> { /// .unwrap(); /// ``` #[inline] + #[deprecated(note = "Please use Self::builder2, it will replace Builder")] pub fn builder() -> Builder { + #[allow(deprecated)] Builder::new() } + + /// Creates a new builder-style object to manufacture a `Response` + /// + /// This method returns an instance of `Builder2` which can be used to + /// create a `Response`. + /// This builder can not represent an erroneous state, so as long + /// as you have a `Builder2` you can get a `Response`. + /// + /// See also [`builder`](#method.builder). + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # use http::header::CONTENT_TYPE; + /// let response = Response::builder2() + /// .status(StatusCode::OK) + /// .header(CONTENT_TYPE, HeaderValue::from_static("text/html")) + /// .body(()); + /// ``` + #[inline] + pub fn builder2() -> Builder2 { + Builder2::new() + } } impl Response { @@ -540,6 +595,7 @@ impl Builder { /// .unwrap(); /// ``` #[inline] + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn new() -> Builder { Builder::default() } @@ -561,6 +617,7 @@ impl Builder { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn status(self, status: T) -> Builder where StatusCode: TryFrom, @@ -589,6 +646,7 @@ impl Builder { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn version(self, version: Version) -> Builder { self.and_then(move |mut head| { head.version = version; @@ -615,6 +673,7 @@ impl Builder { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn header(self, key: K, value: V) -> Builder where HeaderName: TryFrom, @@ -646,6 +705,7 @@ impl Builder { /// assert_eq!( headers["Accept"], "text/html" ); /// assert_eq!( headers["X-Custom-Foo"], "bar" ); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn headers_ref(&self) -> Option<&HeaderMap> { self.inner.as_ref().ok().map(|h| &h.headers) } @@ -669,6 +729,7 @@ impl Builder { /// assert_eq!( headers["Accept"], "text/html" ); /// assert_eq!( headers["X-Custom-Foo"], "bar" ); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn headers_mut(&mut self) -> Option<&mut HeaderMap> { self.inner.as_mut().ok().map(|h| &mut h.headers) } @@ -688,6 +749,7 @@ impl Builder { /// assert_eq!(response.extensions().get::<&'static str>(), /// Some(&"My Extension")); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn extension(self, extension: T) -> Builder where T: Any + Send + Sync + 'static, @@ -711,6 +773,7 @@ impl Builder { /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn extensions_ref(&self) -> Option<&Extensions> { self.inner.as_ref().ok().map(|h| &h.extensions) } @@ -729,6 +792,7 @@ impl Builder { /// extensions.insert(5u32); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn extensions_mut(&mut self) -> Option<&mut Extensions> { self.inner.as_mut().ok().map(|h| &mut h.extensions) } @@ -753,6 +817,7 @@ impl Builder { /// .body(()) /// .unwrap(); /// ``` + #[deprecated(note = "Please use Builder2, it will replace Builder")] pub fn body(self, body: T) -> Result> { self.inner.map(move |head| { Response { @@ -783,12 +848,301 @@ impl Default for Builder { } } +impl Builder2 { + /// Creates a new default instance of `Builder` to construct either a + /// `Head` or a `Response`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let response = response::Builder2::new() + /// .body(()); + /// ``` + #[inline] + pub fn new() -> Builder2 { + Builder2::default() + } + + /// Set the HTTP status for this response. + /// + /// This function will configure the HTTP status code of the `Response` that + /// will be returned from `Builder2::build`. + /// + /// By default this is `200`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let response = Response::builder2() + /// .status(StatusCode::NOT_FOUND) + /// .body(()); + /// ``` + pub fn status(mut self, status: StatusCode) -> Builder2 { + self.inner.status = status.into(); + self + } + + /// Try to Set the HTTP status for this response. + /// + /// This function will configure the HTTP status code of the `Response` that + /// will be returned from `Builder2::build`, using a fallible conversion. + /// If the conversion succeeds, the `Builder2` is updated with the status. + /// If the conversion fails, the `Builder2` is discarded and the error is returned. + /// + /// By default the status is `200`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # fn main() -> Result<()> { + /// let response = Response::builder2() + /// .try_status(404)? + /// .body(()); + /// # Ok(()) + /// # } + /// ``` + pub fn try_status(self, status: T) -> Result + where + StatusCode: TryFrom, + crate::Error: From<>::Error>, + { + use std::convert::TryInto; + Ok(self.status(status.try_into()?)) + } + + /// Set the HTTP version for this response. + /// + /// This function will configure the HTTP version of the `Response` that + /// will be returned from `Builder::build`. + /// + /// By default this is HTTP/1.1 + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let response = Response::builder2() + /// .version(Version::HTTP_2) + /// .body(()); + /// ``` + pub fn version(mut self, version: Version) -> Builder2 { + self.inner.version = version; + self + } + + /// Appends a header to this response builder. + /// + /// This function will append the provided key/value as a header to the + /// internal `HeaderMap` being constructed. Essentially this is equivalent + /// to calling `HeaderMap::append`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # use http::header::HeaderValue; + /// # fn main() -> Result<()> { + /// let response = Response::builder2() + /// .try_header("Content-Type", "text/html")? + /// .try_header("X-Custom-Foo", "bar")? + /// .try_header("content-length", 0)? + /// .body(()); + /// # Ok(()) + /// # } + /// ``` + pub fn try_header(self, key: K, value: V) -> Result + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + let name = >::try_from(key).map_err(Into::into)?; + let value = >::try_from(value).map_err(Into::into)?; + Ok(self.header(name, value)) + } + + /// Appends a header to this response builder. + /// + /// This function will append the provided key/value as a header to the + /// internal `HeaderMap` being constructed. Essentially this is equivalent + /// to calling `HeaderMap::append`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// # use http::header::{HeaderName, HeaderValue}; + /// # use http::header::{CONTENT_TYPE, CONTENT_LENGTH}; + /// + /// let response = Response::builder2() + /// .header(CONTENT_TYPE, HeaderValue::from_static("text/html")) + /// .header(HeaderName::from_static("x-custom-foo"), 17) + /// .header(CONTENT_LENGTH, 0) + /// .body(()); + /// ``` + pub fn header(mut self, key: K, value: V) -> Builder2 + where + HeaderName: From, + HeaderValue: From, + { + self.inner + .headers + .append(HeaderName::from(key), HeaderValue::from(value)); + self + } + + /// Get header on this response builder. + /// + /// When builder has error returns None. + /// + /// # Example + /// + /// ``` + /// # use http::Response; + /// # use http::header::{ACCEPT, HeaderName, HeaderValue}; + /// let res = Response::builder2() + /// .header(ACCEPT, HeaderValue::from_static("text/html")) + /// .header(HeaderName::from_static("x-custom-foo"), 17); + /// let headers = res.headers_ref(); + /// assert_eq!( headers["Accept"], "text/html" ); + /// assert_eq!( headers["X-Custom-Foo"], "17" ); + /// ``` + pub fn headers_ref(&self) -> &HeaderMap { + &self.inner.headers + } + + /// Get header on this response builder. + /// when builder has error returns None + /// + /// # Example + /// + /// ``` + /// # use http::*; + /// # use http::header::HeaderValue; + /// let mut res = Response::builder2(); + /// { + /// let headers = res.headers_mut(); + /// headers.insert("Accept", HeaderValue::from_static("text/html")); + /// headers.insert("X-Custom-Foo", HeaderValue::from_static("bar")); + /// } + /// let headers = res.headers_ref(); + /// assert_eq!( headers["Accept"], "text/html" ); + /// assert_eq!( headers["X-Custom-Foo"], "bar" ); + /// ``` + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.inner.headers + } + + /// Adds an extension to this builder + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let response = Response::builder() + /// .extension("My Extension") + /// .body(()) + /// .unwrap(); + /// + /// assert_eq!(response.extensions().get::<&'static str>(), + /// Some(&"My Extension")); + /// ``` + pub fn extension(mut self, extension: T) -> Builder2 + where + T: Any + Send + Sync + 'static, + { + self.inner.extensions.insert(extension); + self + } + + /// Get a reference to the extensions for this response builder. + /// + /// If the builder has an error, this returns `None`. + /// + /// # Example + /// + /// ``` + /// # use http::Response; + /// let req = Response::builder().extension("My Extension").extension(5u32); + /// let extensions = req.extensions_ref().unwrap(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_ref(&self) -> &Extensions { + &self.inner.extensions + } + + /// Get a mutable reference to the extensions for this response builder. + /// + /// If the builder has an error, this returns `None`. + /// + /// # Example + /// + /// ``` + /// # use http::Response; + /// let mut req = Response::builder().extension("My Extension"); + /// let mut extensions = req.extensions_mut().unwrap(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// extensions.insert(5u32); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_mut(&mut self) -> &mut Extensions { + &mut self.inner.extensions + } + + /// "Consumes" this builder, using the provided `body` to return a + /// constructed `Response`. + /// + /// # 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 `head` was specified via `header("Foo", + /// "Bar\r\n")` the error will be returned when this function is called + /// rather than when `header` was called. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let response = Response::builder() + /// .body(()) + /// .unwrap(); + /// ``` + pub fn body(self, body: T) -> Response { + Response { + head: self.inner, + body, + } + } +} + +impl Default for Builder2 { + #[inline] + fn default() -> Builder2 { + Builder2 { + inner: Parts::new(), + } + } +} + + #[cfg(test)] mod tests { - use super::*; + use super::Response; #[test] fn it_can_map_a_body_from_one_type_to_another() { + #[allow(deprecated)] let response = Response::builder().body("some string").unwrap(); let mapped_response = response.map(|s| { assert_eq!(s, "some string"); @@ -796,4 +1150,14 @@ mod tests { }); assert_eq!(mapped_response.body(), &123u32); } + + #[test] + fn it_can_map_a_body_from_one_type_to_another_2() { + let response = Response::builder2().body("some string"); + let mapped_response = response.map(|s| { + assert_eq!(s, "some string"); + 123u32 + }); + assert_eq!(mapped_response.body(), &123u32); + } } diff --git a/src/uri/builder.rs b/src/uri/builder.rs index 825c0faf..b21d69f9 100644 --- a/src/uri/builder.rs +++ b/src/uri/builder.rs @@ -28,6 +28,7 @@ impl Builder { /// .unwrap(); /// ``` #[inline] + #[deprecated(note = "Please use `uri::Builder2::new` instead.")] pub fn new() -> Builder { Builder::default() } @@ -42,6 +43,7 @@ impl Builder { /// let mut builder = uri::Builder::new(); /// builder.scheme("https"); /// ``` + #[deprecated(note = "Please use `uri::Builder2` instead.")] pub fn scheme(self, scheme: T) -> Self where Scheme: TryFrom, @@ -65,7 +67,9 @@ impl Builder { /// .authority("tokio.rs") /// .build() /// .unwrap(); + /// assert_eq!(uri.to_string(), "tokio.rs"); /// ``` + #[deprecated(note = "Please use `uri::Builder2` instead.")] pub fn authority(self, auth: T) -> Self where Authority: TryFrom, @@ -90,6 +94,7 @@ impl Builder { /// .build() /// .unwrap(); /// ``` + #[deprecated(note = "Please use `uri::Builder2` instead.")] pub fn path_and_query(self, p_and_q: T) -> Self where PathAndQuery: TryFrom, @@ -126,6 +131,7 @@ impl Builder { /// .build() /// .unwrap(); /// ``` + #[deprecated(note = "Please use `uri::Builder2` instead.")] pub fn build(self) -> Result { let parts = self.parts?; Uri::from_parts(parts).map_err(Into::into) @@ -159,6 +165,7 @@ mod tests { #[test] fn build_from_str() { + #[allow(deprecated)] let uri = Builder::new() .scheme(Scheme::HTTP) .authority("hyper.rs") @@ -174,6 +181,7 @@ mod tests { #[test] fn build_from_string() { for i in 1..10 { + #[allow(deprecated)] let uri = Builder::new() .path_and_query(format!("/foo?a={}", i)) .build() @@ -188,6 +196,7 @@ mod tests { fn build_from_string_ref() { for i in 1..10 { let p_a_q = format!("/foo?a={}", i); + #[allow(deprecated)] let uri = Builder::new().path_and_query(&p_a_q).build().unwrap(); let expected_query = format!("a={}", i); assert_eq!(uri.path(), "/foo"); diff --git a/src/uri/builder2.rs b/src/uri/builder2.rs new file mode 100644 index 00000000..f4d70fbe --- /dev/null +++ b/src/uri/builder2.rs @@ -0,0 +1,287 @@ +use std::convert::{TryFrom, TryInto}; + +use super::{Authority, PathAndQuery, Scheme}; +use crate::Uri; + +/// A builder for `Uri`s. +/// +/// This type can be used to construct an instance of `Uri` +/// through a builder pattern. +/// +/// The [Scheme], [Authority], and [PathAndQuery] can be set on the +/// builder, either directly or with fallible conversion. +/// +/// # Examples +/// +/// ``` +/// # use http::*; +/// let uri = uri::Builder2::new() +/// .scheme(uri::Scheme::HTTPS) +/// .authority(uri::Authority::from_static("hyper.rs")) +/// .path_and_query(uri::PathAndQuery::from_static("/guides/client/basic/")) +/// .build(); +/// assert_eq!(uri.to_string(), "https://hyper.rs/guides/client/basic/"); +/// ``` +/// +/// ``` +/// # use http::*; +/// # fn main() -> Result<()> { +/// let uri = uri::Builder2::new() +/// .try_scheme("https")? +/// .try_authority("hyper.rs")? +/// .try_path_and_query("/guides/client/basic/")? +/// .build(); +/// assert_eq!(uri.to_string(), "https://hyper.rs/guides/client/basic/"); +/// # Ok(()) +/// # } +/// ``` +/// +/// It is possible to build an Uri with only the authority or only the +/// path and query part. +/// Invalid combinations does not have the `build` method, and will +/// not compile. +/// +/// ``` +/// # use http::*; +/// let uri = uri::Builder2::new() +/// .authority(uri::Authority::from_static("hyper.rs")) +/// .build(); +/// assert_eq!(uri.to_string(), "hyper.rs"); +/// ``` +/// +/// ``` +/// # use http::*; +/// let uri = uri::Builder2::new() +/// .path_and_query(uri::PathAndQuery::from_static("/2020/page.html")) +/// .build(); +/// assert_eq!(uri.to_string(), "/2020/page.html"); +/// ``` +#[derive(Debug, Default)] +pub struct Builder2 { + parts: Parts, +} + +macro_rules! setter { + ($field:ident, $try_field:ident, $type:ty, $rettype:ty, $retval:expr, $doc:expr) => { + #[doc = $doc] + pub fn $field(self, $field: $type) -> Builder2<$rettype> { + Builder2::<$rettype> { parts: $retval(self.parts, $field) } + } + } +} +macro_rules! try_setter { + ($field:ident, $try_field:ident, $type:ty, $rettype:ty, $doc:expr) => { + #[doc = $doc] + pub fn $try_field(self, $field: T) -> Result, crate::Error> + where + $type: TryFrom, + <$type as TryFrom>::Error: Into, + { + Ok(self.$field($field.try_into().map_err(Into::into)?)) + } + } +} + +macro_rules! methods { + ($field:ident, $try_field:ident, $type:ty, $rettype:ty, $retval:expr) => { + setter!( + $field, $try_field, $type, $rettype, $retval, + concat!("Set ", stringify!($type), " on this builder.") + ); + try_setter!( + $field, $try_field, $type, $rettype, + concat!("Set ", stringify!($type), " on this builder with fallible conversion.") + ); + } +} + +impl Builder2 { + /// Creates a new default instance of `Builder2` to construct a `Uri`. + /// + /// See also [Uri::builder2]. + #[inline] + pub fn new() -> Builder2<()> { + Builder2::default() + } + + methods!(scheme, try_scheme, Scheme, Scheme, |_, s| s); + methods!(authority, try_authority, Authority, Authority, |_, a| a); + methods!(path_and_query, try_path_and_query, PathAndQuery, PathAndQuery, |_, pq| pq); + + /// Consumes this builder, and returns a valid `Uri` from + /// the configured pieces. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let uri = Uri::builder2() + /// .build(); + /// assert_eq!(uri.to_string(), ""); + /// ``` + pub fn build(self) -> Uri { + use super::scheme::Scheme2; + Uri { + scheme: Scheme { + inner: Scheme2::None, + }, + authority: Authority::empty(), + path_and_query: PathAndQuery::empty(), + } + } +} + + +impl Builder2 { + methods!(scheme, try_scheme, Scheme, Scheme, |_, s| s); + methods!(authority, try_authority, Authority, (Scheme, Authority), (|p, a| (p, a))); + methods!(path_and_query, try_path_and_query, PathAndQuery, (Scheme, PathAndQuery), |p, pq| (p, pq)); +} + +impl Builder2 { + methods!(scheme, try_scheme, Scheme, (Scheme, Authority), |a, s| (s, a)); + methods!(authority, try_authority, Authority, Authority, |_, a| a); + methods!(path_and_query, try_path_and_query, PathAndQuery, (Authority, PathAndQuery), |a, pq| (a, pq)); + + /// Consumes this builder, and returns a valid `Uri` from + /// the configured pieces. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let uri = Uri::builder2() + /// .authority(uri::Authority::from_static("hyper.rs")) + /// .build(); + /// assert_eq!(uri.to_string(), "hyper.rs"); + /// ``` + pub fn build(self) -> Uri { + use super::scheme::Scheme2; + Uri { + scheme: Scheme { + inner: Scheme2::None, + }, + authority: self.parts, + path_and_query: PathAndQuery::empty(), + } + } +} + +impl Builder2 { + methods!(scheme, try_scheme, Scheme, (Scheme, PathAndQuery), |p, s| (s, p)); + methods!(authority, try_authority, Authority, (Authority, PathAndQuery), (|p, a| (a, p))); + methods!(path_and_query, try_path_and_query, PathAndQuery, PathAndQuery, |_, pq| pq); + + /// Consumes this builder, and returns a valid `Uri` from + /// the configured pieces. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let uri = uri::Builder2::new() + /// .path_and_query(uri::PathAndQuery::from_static("/2020/page.html")) + /// .build(); + /// assert_eq!(uri.to_string(), "/2020/page.html"); + /// ``` + pub fn build(self) -> Uri { + use super::scheme::Scheme2; + Uri { + scheme: Scheme { + inner: Scheme2::None, + }, + authority: Authority::empty(), + path_and_query: self.parts, + } + } +} + +impl Builder2<(Scheme, Authority)> { + methods!(scheme, try_scheme, Scheme, (Scheme, Authority), |p: (_,_), s| (s, p.1)); + methods!(authority, try_authority, Authority, (Scheme, Authority), |p: (_,_), a| (p.0, a)); + methods!(path_and_query, try_path_and_query, PathAndQuery, (Scheme, Authority, PathAndQuery), |p: (_,_), pq| (p.0, p.1, pq)); +} + +impl Builder2<(Scheme, PathAndQuery)> { + methods!(scheme, try_scheme, Scheme, (Scheme, PathAndQuery), |p: (_,_), s| (s, p.1)); + methods!(authority, try_authority, Authority, (Scheme, Authority, PathAndQuery), |p: (_,_), a| (p.0, a, p.1)); + methods!(path_and_query, try_path_and_query, PathAndQuery, (Scheme, PathAndQuery), |p: (_,_), pq| (p.0, pq)); +} + +impl Builder2<(Authority, PathAndQuery)> { + methods!(scheme, try_scheme, Scheme, (Scheme, Authority, PathAndQuery), |p: (_,_), s| (s, p.0, p.1)); + methods!(authority, try_authority, Authority, (Authority, PathAndQuery), |p: (_,_), a| (a, p.1)); + methods!(path_and_query, try_path_and_query, PathAndQuery, (Authority, PathAndQuery), |p: (_,_), pq| (p.0, pq)); +} + +impl Builder2<(Scheme, Authority, PathAndQuery)> { + methods!(scheme, try_scheme, Scheme, (Scheme, Authority, PathAndQuery), |p: (_,_,_), s| (s, p.1, p.2)); + methods!(authority, try_authority, Authority, (Scheme, Authority, PathAndQuery), |p: (_,_,_), a| (p.0, a, p.2)); + methods!(path_and_query, try_path_and_query, PathAndQuery, (Scheme, Authority, PathAndQuery), |p: (_,_,_), pq| (p.0, p.1, pq)); + + /// Consumes this builder, and returns a valid `Uri` from + /// the configured pieces. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// let uri = Uri::builder2() + /// .scheme(uri::Scheme::HTTPS) + /// .authority(uri::Authority::from_static("hyper.rs")) + /// .path_and_query(uri::PathAndQuery::from_static("/guides/client/basic/")) + /// .build(); + /// assert_eq!(uri.to_string(), "https://hyper.rs/guides/client/basic/"); + /// ``` + pub fn build(self) -> Uri { + Uri { + scheme: self.parts.0, + authority: self.parts.1, + path_and_query: self.parts.2, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn build_from_str() -> Result<(), crate::Error> { + let uri = Builder2::new() + .scheme(Scheme::HTTP) + .try_authority("hyper.rs")? + .try_path_and_query("/foo?a=1")? + .build(); + assert_eq!(uri.scheme_str(), Some("http")); + assert_eq!(uri.authority().unwrap().host(), "hyper.rs"); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some("a=1")); + Ok(()) + } + + #[test] + fn build_from_string() -> Result<(), crate::Error> { + for i in 1..10 { + let uri = Builder2::new() + .try_path_and_query(format!("/foo?a={}", i))? + .build(); + let expected_query = format!("a={}", i); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some(expected_query.as_str())); + } + Ok(()) + } + + #[test] + fn build_from_string_ref() -> Result<(), crate::Error> { + for i in 1..10 { + let p_a_q = format!("/foo?a={}", i); + let uri = Builder2::new().try_path_and_query(&p_a_q)?.build(); + let expected_query = format!("a={}", i); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some(expected_query.as_str())); + } + Ok(()) + } +} diff --git a/src/uri/mod.rs b/src/uri/mod.rs index 57c052f7..d4eabddf 100644 --- a/src/uri/mod.rs +++ b/src/uri/mod.rs @@ -36,12 +36,14 @@ use self::scheme::Scheme2; pub use self::authority::Authority; pub use self::builder::Builder; +pub use self::builder2::Builder2; pub use self::path::PathAndQuery; pub use self::port::Port; pub use self::scheme::Scheme; mod authority; mod builder; +mod builder2; mod path; mod port; mod scheme; @@ -197,10 +199,34 @@ impl Uri { /// .build() /// .unwrap(); /// ``` + #[deprecated(note = "Please use builder2 instead")] pub fn builder() -> Builder { + #[allow(deprecated)] Builder::new() } + /// Creates a new builder-style object to manufacture a `Uri`. + /// + /// This method returns an instance of a `Builder2` which can be used + /// to create a `Uri`. + /// + /// # Examples + /// + /// ``` + /// use http::Uri; + /// use http::uri::{Scheme, Authority, PathAndQuery}; + /// + /// let uri = Uri::builder2() + /// .scheme(Scheme::HTTPS) + /// .authority(Authority::from_static("hyper.rs")) + /// .path_and_query(PathAndQuery::from_static("/")) + /// .build(); + /// assert_eq!(uri.to_string(), "https://hyper.rs/"); + /// ``` + pub fn builder2() -> Builder2 { + Builder2::new() + } + /// Attempt to convert a `Uri` from `Parts` pub fn from_parts(src: Parts) -> Result { if src.scheme.is_some() {