diff --git a/lib/src/context/builder.rs b/lib/src/context/builder.rs new file mode 100644 index 0000000..b227693 --- /dev/null +++ b/lib/src/context/builder.rs @@ -0,0 +1,92 @@ + +use tokio_core::reactor::Handle; +use std::sync::Arc; +use unsafe_any::UnsafeAny; +use hyper::{Body, HttpVersion}; +use hyper::header::Header; + +use super::Context; +use state::State; +use util::typemap::TypeMap; +use request; +use data; +use errors::Error; +use http::Method; + +/// Helper struct for construct a [`Context`] +/// +/// [`Context`]: struct.Context.html +pub struct Builder { + handle: Handle, + + state: State, + + // for request + request: request::Builder, + body: Body, +} + +impl Builder { + /// Create a new `Builder` from a tokio_core `Handle` + pub fn new(handle: Handle) -> Self { + Self { + handle, + state: State::default(), + request: request::Builder::default(), + body: Body::default(), + } + } + + /// Set the shared data. + pub fn shared(mut self, shared: Arc>) -> Self { + self.state.shared = shared; + self + } + + /// Set the HTTP method to `method` + pub fn method(mut self, method: Method) -> Self { + self.request = self.request.method(method); + self + } + + /// Set the uri + pub fn uri(mut self, uri: &str) -> Self { + self.request = self.request.uri(uri); + self + } + + /// Set the HTTP version + pub fn version(mut self, ver: HttpVersion) -> Self { + self.request = self.request.version(ver); + self + } + + /// Set an header + pub fn header(mut self, value: H) -> Self { + self.request = self.request.header(value); + self + } + + /// Set the request data + pub fn data>(mut self, body: B) -> Self { + self.body = body.into(); + self + } + + /// Create the `Context`, returning any error that occurs during build. + pub fn finalize(self) -> Result { + let Self { + handle, + state, + request, + body, + } = self; + + Ok(Context::new( + handle, + request.finalize()?, + state, + data::Data::new(body), + )) + } +} diff --git a/lib/src/context.rs b/lib/src/context/mod.rs similarity index 91% rename from lib/src/context.rs rename to lib/src/context/mod.rs index 29399f4..293d239 100644 --- a/lib/src/context.rs +++ b/lib/src/context/mod.rs @@ -1,3 +1,5 @@ +mod builder; + use std::ops::Deref; use tokio_core::reactor::Handle; @@ -9,6 +11,8 @@ use state::{FromState, State}; use Data; pub use state::Key; +pub use self::builder::Builder; + /// `Context` represents the context of the current HTTP request. /// /// A `Context` consists of: @@ -79,6 +83,13 @@ impl Context { pub fn deconstruct(self) -> (Handle, State, Request, Data) { (self.handle, self.state, self.request, self.body) } + + /// Create a new context [`Builder`] + /// + /// [`Builder`]: struct.Builder.html + pub fn builder(handle: Handle) -> Builder { + Builder::new(handle) + } } impl Deref for Context { diff --git a/lib/src/errors.rs b/lib/src/errors.rs index 8a39ae6..9b80c57 100644 --- a/lib/src/errors.rs +++ b/lib/src/errors.rs @@ -53,7 +53,9 @@ impl From for ListenErrorKind { /// A generic "error" that can occur from inside Shio. #[derive(Debug)] -pub struct Error { inner: ErrorKind } +pub struct Error { + inner: ErrorKind, +} #[derive(Debug)] enum ErrorKind { @@ -63,13 +65,25 @@ enum ErrorKind { impl From for Error { fn from(err: ListenError) -> Self { - Self { inner: ErrorKind::Listen(err) } + Self { + inner: ErrorKind::Listen(err), + } } } impl From for Error { fn from(err: hyper::Error) -> Self { - Self { inner: ErrorKind::Hyper(err) } + Self { + inner: ErrorKind::Hyper(err), + } + } +} + +impl From for Error { + fn from(err: hyper::error::UriError) -> Self { + Self { + inner: ErrorKind::Hyper(err.into()), + } } } diff --git a/lib/src/http.rs b/lib/src/http.rs index 12465fd..ea44a20 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -11,7 +11,7 @@ use http_types; // It will be removed in 0.4. /// The Request Method (VERB). -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Method(http_types::Method); impl Method { @@ -55,7 +55,22 @@ impl Method { Self::PATCH => hyper::Method::Patch, Self::DELETE => hyper::Method::Delete, - _ => unimplemented!() + _ => unimplemented!(), + } + } + + // Temporary until Hyper 0.12 + pub(crate) fn from_hyper_method(hm: &hyper::Method) -> Self { + match *hm { + hyper::Method::Options => Self::OPTIONS, + hyper::Method::Head => Self::HEAD, + hyper::Method::Get => Self::GET, + hyper::Method::Post => Self::POST, + hyper::Method::Put => Self::PUT, + hyper::Method::Patch => Self::PATCH, + hyper::Method::Delete => Self::DELETE, + + _ => unimplemented!(), } } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 3c65e3b..72cbf97 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -38,10 +38,10 @@ pub use errors::Error; /// Re-exports important traits and types. Meant to be glob imported when using Shio. pub mod prelude { - pub use {Context, Request, Response, Shio, http}; + pub use {http, Context, Request, Response, Shio}; pub use router::Parameters; pub use ext::{BoxFuture, FutureExt}; pub use http::{Method, StatusCode}; - pub use futures::{Future, Stream, IntoFuture}; + pub use futures::{Future, IntoFuture, Stream}; } diff --git a/lib/src/request/builder.rs b/lib/src/request/builder.rs new file mode 100644 index 0000000..4e232c9 --- /dev/null +++ b/lib/src/request/builder.rs @@ -0,0 +1,72 @@ + +use hyper::{error, Headers, HttpVersion, Uri}; +use hyper::header::Header; + +use super::Request; +use errors::Error; +use http::Method; + +/// Helper struct for construct a [`Request`] +/// +/// [`Request`]: struct.Context.html +pub struct Builder { + method: Method, + uri: Result, + version: HttpVersion, + headers: Headers, +} + +impl Default for Builder { + fn default() -> Self { + Self { + method: Method::GET, + uri: "/".parse::(), + version: HttpVersion::Http11, + headers: Headers::new(), + } + } +} + +impl Builder { + pub fn new() -> Self { + Self::default() + } + + /// Set the HTTP method to `method` + pub fn method(mut self, method: Method) -> Self { + self.method = method; + self + } + + /// Set the uri + pub fn uri(mut self, uri: &str) -> Self { + self.uri = uri.parse::(); + self + } + + /// Set the HTTP version + pub fn version(mut self, ver: HttpVersion) -> Self { + self.version = ver; + self + } + + /// Set an header + pub fn header(mut self, value: H) -> Self { + self.headers.set(value); + self + } + + /// Create the `Context`, returning any error that occurs during build. + pub fn finalize(self) -> Result { + let Self { + method, + uri, + version, + headers, + } = self; + + let uri = uri?; + + Ok(Request::new((method, uri, version, headers))) + } +} diff --git a/lib/src/request/mod.rs b/lib/src/request/mod.rs index 5977f7d..39461ec 100644 --- a/lib/src/request/mod.rs +++ b/lib/src/request/mod.rs @@ -1,5 +1,9 @@ +mod builder; -use hyper::{self, Method}; +use hyper; + +pub use self::builder::Builder; +use http::Method; pub struct Request { method: Method, @@ -49,4 +53,10 @@ impl Request { pub fn path(&self) -> &str { self.uri.path() } + + /// Create a new `Builder` + #[inline] + pub fn builder() -> Builder { + Builder::default() + } } diff --git a/lib/src/response/builder.rs b/lib/src/response/builder.rs index 60d8451..6a6b08b 100644 --- a/lib/src/response/builder.rs +++ b/lib/src/response/builder.rs @@ -13,7 +13,7 @@ use http::header::Header; /// /// ```rust /// # use shio::response::{self, Response}; -/// # use shio::StatusCode; +/// # use shio::http::StatusCode; /// // A 204, "No Content", Response /// let response: Response = response::Builder::new().status(StatusCode::NoContent).into(); /// @@ -39,7 +39,7 @@ impl Builder { /// /// ```rust /// # use shio::response::{self, Response}; - /// # use shio::StatusCode; + /// # use shio::http::StatusCode; /// let response: Response = response::Builder::new().status(StatusCode::BadRequest).into(); /// ``` #[inline] @@ -52,7 +52,7 @@ impl Builder { /// /// ```rust /// # use shio::response::{self, Response}; - /// # use shio::header; + /// # use shio::http::header; /// let response: Response = response::Builder::new() /// // Date: Tue, 15 Nov 1994 08:12:31 GMT /// .header(header::Date(std::time::SystemTime::now().into())) diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index f4d89f6..fb3e66f 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -8,7 +8,7 @@ pub use self::parameters::Parameters; use std::collections::HashMap; -use hyper::{self, Method, StatusCode}; +use hyper::{self, StatusCode}; use regex::RegexSet; use futures::future; @@ -16,6 +16,7 @@ use handler::Handler; use context::Context; use response::Response; use ext::BoxFuture; +use http::Method; // From: https://github.com/crumblingstatue/try_opt/blob/master/src/lib.rs#L30 macro_rules! try_opt { @@ -52,10 +53,11 @@ impl Router { /// For example, to match a `Get` request to `/users`: /// /// ```rust - /// # use shio::{Method, Response, StatusCode}; + /// # use shio::Response; /// # use shio::router::Router; + /// # use shio::http::{Method, StatusCode}; /// # let mut router = Router::new(); - /// router.route((Method::GET, "/users", |_| { + /// router.add((Method::GET, "/users", |_| { /// // [...] /// # Response::with(StatusCode::NoContent) /// })); @@ -65,7 +67,7 @@ impl Router { /// [`Pattern`]: struct.Pattern.html pub fn add>(&mut self, route: R) { let route: Route = route.into(); - let method = route.method().clone(); + let method = Method::from_hyper_method(&route.method()); self.routes .entry(method.clone()) @@ -124,10 +126,9 @@ impl Handler for Router { #[cfg(test)] mod tests { use tokio_core::reactor::Core; - use hyper; use super::{Parameters, Router}; - use {Context, Handler, Response, State}; + use {Context, Handler, Response}; use http::{Method, StatusCode}; // Empty handler to use for route tests @@ -141,9 +142,9 @@ mod tests { let mut router = Router::new(); router.add((Method::GET, "/hello", empty_handler)); - assert!(router.find(&hyper::Method::Get, "/hello").is_some()); - assert!(router.find(&hyper::Method::Get, "/aa").is_none()); - assert!(router.find(&hyper::Method::Get, "/hello/asfa").is_none()); + assert!(router.find(&Method::GET, "/hello").is_some()); + assert!(router.find(&Method::GET, "/aa").is_none()); + assert!(router.find(&Method::GET, "/hello/asfa").is_none()); } /// Test for _some_ match for static in PUT, POST, DELETE @@ -154,10 +155,10 @@ mod tests { router.add((Method::POST, "/hello", empty_handler)); router.add((Method::DELETE, "/hello", empty_handler)); - assert!(router.find(&hyper::Method::Get, "/hello").is_none()); - assert!(router.find(&hyper::Method::Put, "/hello").is_some()); - assert!(router.find(&hyper::Method::Post, "/hello").is_some()); - assert!(router.find(&hyper::Method::Delete, "/hello").is_some()); + assert!(router.find(&Method::GET, "/hello").is_none()); + assert!(router.find(&Method::PUT, "/hello").is_some()); + assert!(router.find(&Method::POST, "/hello").is_some()); + assert!(router.find(&Method::DELETE, "/hello").is_some()); } /// Test for the correct match for static @@ -172,11 +173,15 @@ mod tests { // This is an implementation detail; store the source strings and we'll // match against that assert_eq!( - router.find(&hyper::Method::Get, "/hello").unwrap().pattern().as_str(), + router + .find(&Method::GET, "/hello") + .unwrap() + .pattern() + .as_str(), "^/hello$" ); assert_eq!( - router.find(&hyper::Method::Get, "/aa").unwrap().pattern().as_str(), + router.find(&Method::GET, "/aa").unwrap().pattern().as_str(), "^/aa$" ); } @@ -187,10 +192,10 @@ mod tests { let mut router = Router::new(); router.add((Method::GET, "/user/{id}", empty_handler)); - assert!(router.find(&hyper::Method::Get, "/user/asfa").is_some()); - assert!(router.find(&hyper::Method::Get, "/user/profile").is_some()); - assert!(router.find(&hyper::Method::Get, "/user/3289").is_some()); - assert!(router.find(&hyper::Method::Get, "/user").is_none()); + assert!(router.find(&Method::GET, "/user/asfa").is_some()); + assert!(router.find(&Method::GET, "/user/profile").is_some()); + assert!(router.find(&Method::GET, "/user/3289").is_some()); + assert!(router.find(&Method::GET, "/user").is_none()); } /// Test for segment parameter value @@ -205,12 +210,10 @@ mod tests { })); let mut core = Core::new().unwrap(); - - // TODO: It should much easier to make a test context - // Perhaps `Request::build ( .. )` should be a thing? - // Proxied as `Context::build ( .. )` ? - let (request, data) = ::service::from_hyper_request(hyper::Request::new(hyper::Method::Get, "/user/3289".parse().unwrap())); - let context = Context::new(core.handle(), request, State::default(), data); + let context = Context::builder(core.handle()) + .uri("/user/3289") + .finalize() + .unwrap(); let work = router.call(context); @@ -223,30 +226,35 @@ mod tests { let mut router = Router::new(); router.add((Method::GET, "/static/{file: .+}", empty_handler)); - assert!(router.find(&hyper::Method::Get, "/static").is_none()); - assert!(router.find(&hyper::Method::Get, "/static/").is_none()); - assert!(router.find(&hyper::Method::Get, "/static/blah").is_some()); - assert!(router.find(&hyper::Method::Get, "/static/rahrahrah").is_some()); + assert!(router.find(&Method::GET, "/static").is_none()); + assert!(router.find(&Method::GET, "/static/").is_none()); + assert!(router.find(&Method::GET, "/static/blah").is_some()); + assert!(router.find(&Method::GET, "/static/rahrahrah").is_some()); } /// Test for segment parameter value #[test] fn test_param_get_custom() { let mut router = Router::new(); - router.add((Method::GET, "/static/{filename: .*}", |context: Context| { - // FIXME: We should have an assert that we got here - assert_eq!( - &context.get::()["filename"], - "path/to/file/is/here" - ); - - Response::with(StatusCode::NoContent) - })); + router.add(( + Method::GET, + "/static/{filename: .*}", + |context: Context| { + // FIXME: We should have an assert that we got here + assert_eq!( + &context.get::()["filename"], + "path/to/file/is/here" + ); + + Response::with(StatusCode::NoContent) + }, + )); let mut core = Core::new().unwrap(); - - let (request, data) = ::service::from_hyper_request(hyper::Request::new(hyper::Method::Get, "/static/path/to/file/is/here".parse().unwrap())); - let context = Context::new(core.handle(), request, State::default(), data); + let context = Context::builder(core.handle()) + .uri("/static/path/to/file/is/here") + .finalize() + .unwrap(); let work = router.call(context); diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index 9c772a1..ad41358 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -21,7 +21,8 @@ impl Route { /// Constructs a new `Route` which matches against the provided information. /// /// ```rust - /// # use shio::{Response, Method}; + /// # use shio::Response; + /// # use shio::http::Method; /// # use shio::router::Route; /// Route::new(Method::Post, "/inbox", |_| { /// // [...] diff --git a/lib/src/service.rs b/lib/src/service.rs index 935c01e..b596d6d 100644 --- a/lib/src/service.rs +++ b/lib/src/service.rs @@ -14,6 +14,7 @@ use state::State; use util::typemap::TypeMap; use ext::BoxFuture; use Data; +use http::Method; // FIXME: Why does #[derive(Clone)] not work here? This _seems_ like a implementation that // should be auto-derived. @@ -61,7 +62,7 @@ where pub(crate) fn from_hyper_request(request: hyper::Request) -> (Request, Data) { let (method, uri, version, header, body) = request.deconstruct(); ( - Request::new((method, uri, version, header)), + Request::new((Method::from_hyper_method(&method), uri, version, header)), Data::new(body), ) } diff --git a/lib/src/state.rs b/lib/src/state.rs index 206fe36..4e75543 100644 --- a/lib/src/state.rs +++ b/lib/src/state.rs @@ -10,7 +10,7 @@ pub struct State { request: TypeMap, /// State shared across all requests. - shared: Arc>, + pub(crate) shared: Arc>, } impl Default for State {