mirror of
https://github.com/zerotier/ratpack.git
synced 2026-05-22 16:27:23 -07:00
The great transient state patch. Tests forthcoming.
Signed-off-by: Erik Hollensbe <linux@hollensbe.org>
This commit is contained in:
@@ -4,8 +4,9 @@ async fn validate_authtoken(
|
||||
req: Request<Body>,
|
||||
resp: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
app: App<State>,
|
||||
) -> HTTPResult {
|
||||
app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let token = req.headers().get("X-AuthToken");
|
||||
if token.is_none() {
|
||||
return Err(Error::StatusCode(StatusCode::UNAUTHORIZED));
|
||||
@@ -24,21 +25,23 @@ async fn validate_authtoken(
|
||||
return Err(Error::StatusCode(StatusCode::UNAUTHORIZED));
|
||||
}
|
||||
|
||||
return Ok((req, resp));
|
||||
return Ok((req, resp, NoState {}));
|
||||
}
|
||||
|
||||
async fn hello(
|
||||
req: Request<Body>,
|
||||
_resp: Option<Response<Body>>,
|
||||
params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let name = params.get("name").unwrap();
|
||||
let bytes = Body::from(format!("hello, {}!\n", name));
|
||||
|
||||
return Ok((
|
||||
req,
|
||||
Some(Response::builder().status(200).body(bytes).unwrap()),
|
||||
NoState {},
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ async fn validate_authtoken(
|
||||
req: Request<Body>,
|
||||
resp: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<()>,
|
||||
) -> HTTPResult {
|
||||
_app: App<(), NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let token = req.headers().get("X-AuthToken");
|
||||
if token.is_none() {
|
||||
return Err(Error::StatusCode(StatusCode::UNAUTHORIZED));
|
||||
@@ -28,21 +29,23 @@ async fn validate_authtoken(
|
||||
return Err(Error::StatusCode(StatusCode::UNAUTHORIZED));
|
||||
}
|
||||
|
||||
return Ok((req, resp));
|
||||
return Ok((req, resp, NoState {}));
|
||||
}
|
||||
|
||||
async fn hello(
|
||||
req: Request<Body>,
|
||||
_resp: Option<Response<Body>>,
|
||||
params: Params,
|
||||
_app: App<()>,
|
||||
) -> HTTPResult {
|
||||
_app: App<(), NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let name = params.get("name").unwrap();
|
||||
let bytes = Body::from(format!("hello, {}!\n", name));
|
||||
|
||||
return Ok((
|
||||
req,
|
||||
Some(Response::builder().status(200).body(bytes).unwrap()),
|
||||
NoState {},
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,16 @@ async fn hello(
|
||||
req: Request<Body>,
|
||||
_resp: Option<Response<Body>>,
|
||||
params: Params,
|
||||
_app: App<()>,
|
||||
) -> HTTPResult {
|
||||
_app: App<(), NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let name = params.get("name").unwrap();
|
||||
let bytes = Body::from(format!("hello, {}!\n", name));
|
||||
|
||||
return Ok((
|
||||
req,
|
||||
Some(Response::builder().status(200).body(bytes).unwrap()),
|
||||
NoState {},
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -5,18 +5,20 @@ async fn log(
|
||||
req: Request<Body>,
|
||||
resp: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<()>,
|
||||
) -> HTTPResult {
|
||||
_app: App<(), NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
log::trace!("New request: {}", req.uri().path());
|
||||
Ok((req, resp))
|
||||
Ok((req, resp, NoState {}))
|
||||
}
|
||||
|
||||
async fn hello(
|
||||
req: Request<Body>,
|
||||
_resp: Option<Response<Body>>,
|
||||
params: Params,
|
||||
_app: App<()>,
|
||||
) -> HTTPResult {
|
||||
_app: App<(), NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let name = params.get("name").unwrap();
|
||||
log::info!("Saying hello to {}", name);
|
||||
let bytes = Body::from(format!("hello, {}!\n", name));
|
||||
@@ -24,6 +26,7 @@ async fn hello(
|
||||
return Ok((
|
||||
req,
|
||||
Some(Response::builder().status(200).body(bytes).unwrap()),
|
||||
NoState,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -4,7 +4,7 @@ use http::{Method, Request, Response, StatusCode};
|
||||
use hyper::{server::conn::Http, service::service_fn, Body};
|
||||
use tokio::{net::TcpListener, sync::Mutex};
|
||||
|
||||
use crate::{handler::Handler, router::Router, Error, ServerError};
|
||||
use crate::{handler::Handler, router::Router, Error, ServerError, TransientState};
|
||||
|
||||
/// App is used to define application-level functionality and initialize the server. Routes are
|
||||
/// typically programmed here.
|
||||
@@ -49,12 +49,12 @@ use crate::{handler::Handler, router::Router, Error, ServerError};
|
||||
///
|
||||
/// Requests are routed through paths to [crate::handler::HandlerFunc]s.
|
||||
#[derive(Clone)]
|
||||
pub struct App<S: Clone + Send> {
|
||||
router: Router<S>,
|
||||
pub struct App<S: Clone + Send, T: TransientState + 'static + Clone + Send> {
|
||||
router: Router<S, T>,
|
||||
global_state: Option<Arc<Mutex<S>>>,
|
||||
}
|
||||
|
||||
impl<S: 'static + Clone + Send> App<S> {
|
||||
impl<S: 'static + Clone + Send, T: TransientState + 'static + Clone + Send> App<S, T> {
|
||||
/// Construct a new App with no state; it will be passed to handlers as `App<()>`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -84,55 +84,55 @@ impl<S: 'static + Clone + Send> App<S> {
|
||||
|
||||
/// Create a route for a GET request. See App's docs and [crate::handler::Handler] for
|
||||
/// more information.
|
||||
pub fn get(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn get(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::GET, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for a POST request. See App's docs and [crate::handler::Handler] for
|
||||
/// more information.
|
||||
pub fn post(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn post(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::POST, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for a DELETE request. See App's docs and [crate::handler::Handler] for
|
||||
/// more information.
|
||||
pub fn delete(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn delete(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::DELETE, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for a PUT request. See App's docs and [crate::handler::Handler] for
|
||||
/// more information.
|
||||
pub fn put(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn put(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::PUT, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for an OPTIONS request. See App's docs and
|
||||
/// [crate::handler::Handler] for more information.
|
||||
pub fn options(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn options(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::OPTIONS, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for a PATCH request. See App's docs and
|
||||
/// [crate::handler::Handler] for more information.
|
||||
pub fn patch(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn patch(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::PATCH, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for a HEAD request. See App's docs and
|
||||
/// [crate::handler::Handler] for more information.
|
||||
pub fn head(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn head(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::HEAD, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for a CONNECT request. See App's docs and
|
||||
/// [crate::handler::Handler] for more information.
|
||||
pub fn connect(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn connect(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::CONNECT, path.to_string(), ch);
|
||||
}
|
||||
|
||||
/// Create a route for a TRACE request. See App's docs and
|
||||
/// [crate::handler::Handler] for more information.
|
||||
pub fn trace(&mut self, path: &str, ch: Handler<S>) {
|
||||
pub fn trace(&mut self, path: &str, ch: Handler<S, T>) {
|
||||
self.router.add(Method::TRACE, path.to_string(), ch);
|
||||
}
|
||||
|
||||
|
||||
+48
-32
@@ -1,6 +1,6 @@
|
||||
use std::future::Future;
|
||||
|
||||
use crate::{app::App, HTTPResult, PinBox};
|
||||
use crate::{app::App, HTTPResult, PinBox, TransientState};
|
||||
use async_recursion::async_recursion;
|
||||
|
||||
use http::{Request, Response};
|
||||
@@ -18,43 +18,46 @@ use hyper::Body;
|
||||
/// req: Request<Body>,
|
||||
/// _resp: Option<Response<Body>>,
|
||||
/// params: Params,
|
||||
/// _app: App<()>,
|
||||
/// ) -> HTTPResult {
|
||||
/// _app: App<(), NoState>,
|
||||
/// _state: NoState,
|
||||
/// ) -> HTTPResult<NoState> {
|
||||
/// let name = params.get("name").unwrap();
|
||||
/// let bytes = Body::from(format!("hello, {}!\n", name));
|
||||
///
|
||||
/// return Ok((
|
||||
/// req,
|
||||
/// Some(Response::builder().status(200).body(bytes).unwrap()),
|
||||
/// NoState{},
|
||||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub type HandlerFunc<S> = fn(
|
||||
pub type HandlerFunc<S, T> = fn(
|
||||
req: Request<Body>,
|
||||
response: Option<Response<Body>>,
|
||||
params: crate::Params,
|
||||
app: App<S>,
|
||||
) -> PinBox<dyn Future<Output = HTTPResult> + Send>;
|
||||
app: App<S, T>,
|
||||
state: T,
|
||||
) -> PinBox<dyn Future<Output = HTTPResult<T>> + Send>;
|
||||
|
||||
/// Handler is the structure of the handler. Typically, you will not use this directly, and instead
|
||||
/// interact with the [crate::compose_handler!] macro. That said, if you wanted to define your own
|
||||
/// macros or otherwise compose more complicated structures for your handlers, this is available to
|
||||
/// you.
|
||||
#[derive(Clone)]
|
||||
pub struct Handler<S: Clone + Send> {
|
||||
handler: HandlerFunc<S>,
|
||||
next: Box<Option<Handler<S>>>,
|
||||
pub struct Handler<S: Clone + Send, T: TransientState + 'static> {
|
||||
handler: HandlerFunc<S, T>,
|
||||
next: Box<Option<Handler<S, T>>>,
|
||||
}
|
||||
|
||||
impl<S> Handler<S>
|
||||
impl<S: Clone + Send, T: TransientState> Handler<S, T>
|
||||
where
|
||||
Self: Send,
|
||||
S: Clone + Send,
|
||||
{
|
||||
/// Construct a new handler composed of a HandlerFunc with state, and an optional next handler
|
||||
/// in the chain.
|
||||
pub fn new(handler: HandlerFunc<S>, next: Option<Handler<S>>) -> Self {
|
||||
pub fn new(handler: HandlerFunc<S, T>, next: Option<Handler<S, T>>) -> Self {
|
||||
Self {
|
||||
handler,
|
||||
next: Box::new(next),
|
||||
@@ -68,30 +71,35 @@ where
|
||||
req: Request<hyper::Body>,
|
||||
response: Option<Response<hyper::Body>>,
|
||||
params: crate::Params,
|
||||
app: App<S>,
|
||||
) -> HTTPResult {
|
||||
let (req, response) = (self.handler)(req, response, params.clone(), app.clone()).await?;
|
||||
app: App<S, T>,
|
||||
state: T,
|
||||
) -> HTTPResult<T> {
|
||||
let (req, response, state) =
|
||||
(self.handler)(req, response, params.clone(), app.clone(), state).await?;
|
||||
if self.next.is_some() {
|
||||
return Ok((*self.clone().next)
|
||||
.unwrap()
|
||||
.perform(req, response, params, app)
|
||||
.perform(req, response, params, app, state)
|
||||
.await?);
|
||||
}
|
||||
|
||||
Ok((req, response))
|
||||
Ok((req, response, state))
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_handler_basic() {
|
||||
use crate::{app::App, Error, HTTPResult, Params};
|
||||
use crate::{app::App, Error, HTTPResult, NoState, Params};
|
||||
use http::{HeaderValue, Request, Response, StatusCode};
|
||||
use hyper::Body;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TransientState;
|
||||
|
||||
// this method adds a header:
|
||||
// wakka: wakka wakka
|
||||
// to the request. that's it!
|
||||
@@ -99,11 +107,12 @@ mod tests {
|
||||
mut req: Request<Body>,
|
||||
_response: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let headers = req.headers_mut();
|
||||
headers.insert("wakka", HeaderValue::from_str("wakka wakka").unwrap());
|
||||
Ok((req, None))
|
||||
Ok((req, None, NoState {}))
|
||||
}
|
||||
|
||||
// this method returns an OK status when the wakka header exists.
|
||||
@@ -111,22 +120,23 @@ mod tests {
|
||||
req: Request<Body>,
|
||||
mut response: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
if let Some(header) = req.headers().get("wakka") {
|
||||
if header != "wakka wakka" {
|
||||
return Err(Error::new("invalid header value"));
|
||||
}
|
||||
|
||||
if response.is_some() {
|
||||
return Ok((req, response));
|
||||
return Ok((req, response, NoState {}));
|
||||
} else {
|
||||
let resp = Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.body(Body::default())?;
|
||||
response.replace(resp);
|
||||
|
||||
return Ok((req, response));
|
||||
return Ok((req, response, NoState {}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,12 +145,12 @@ mod tests {
|
||||
|
||||
// single stage handler that never yields a response
|
||||
let bh = super::Handler::new(
|
||||
|req, resp, params, app| Box::pin(one(req, resp, params, app)),
|
||||
|req, resp, params, app, state| Box::pin(one(req, resp, params, app, state)),
|
||||
None,
|
||||
);
|
||||
let req = Request::default();
|
||||
let (req, response) = bh
|
||||
.perform(req, None, Params::new(), App::new())
|
||||
let (req, response, _) = bh
|
||||
.perform(req, None, Params::new(), App::new(), NoState {})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -149,22 +159,28 @@ mod tests {
|
||||
|
||||
// two-stage handler; yields a response if the first one was good.
|
||||
let bh_two = super::Handler::new(
|
||||
|req, resp, params, app| Box::pin(two(req, resp, params, app)),
|
||||
|req, resp, params, app, state| Box::pin(two(req, resp, params, app, state)),
|
||||
None,
|
||||
);
|
||||
let bh = super::Handler::new(
|
||||
|req, resp, params, app| Box::pin(one(req, resp, params, app)),
|
||||
|req, resp, params, app, state| Box::pin(one(req, resp, params, app, state)),
|
||||
Some(bh_two.clone()),
|
||||
);
|
||||
let (_, response) = bh
|
||||
.perform(req, None, Params::new(), App::new())
|
||||
let (_, response, _) = bh
|
||||
.perform(req, None, Params::new(), App::new(), NoState {})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(response.is_some() && response.unwrap().status() == StatusCode::OK);
|
||||
|
||||
assert!(bh_two
|
||||
.perform(Request::default(), None, Params::new(), App::new())
|
||||
.perform(
|
||||
Request::default(),
|
||||
None,
|
||||
Params::new(),
|
||||
App::new(),
|
||||
NoState {}
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
|
||||
+31
-2
@@ -74,7 +74,34 @@ where
|
||||
/// returned. If you wish to return Err(), a [http::StatusCode] or [std::string::String] can be
|
||||
/// returned, the former is resolved to its status with an empty body, and the latter corresponds
|
||||
/// to a 500 Internal Server Error with the body set to the string.
|
||||
pub type HTTPResult = Result<(Request<hyper::Body>, Option<Response<hyper::Body>>), Error>;
|
||||
pub type HTTPResult<TransientState> = Result<
|
||||
(
|
||||
Request<hyper::Body>,
|
||||
Option<Response<hyper::Body>>,
|
||||
TransientState,
|
||||
),
|
||||
Error,
|
||||
>;
|
||||
|
||||
/// TransientState must be implemented to use state between handlers.
|
||||
pub trait TransientState
|
||||
where
|
||||
Self: Clone + Send,
|
||||
{
|
||||
/// initial prescribes an initial state for the trait, allowing it to be constructed at
|
||||
/// dispatch time.
|
||||
fn initial() -> Self;
|
||||
}
|
||||
|
||||
/// NoState is an empty [crate::TransientState].
|
||||
#[derive(Clone)]
|
||||
pub struct NoState;
|
||||
|
||||
impl TransientState for NoState {
|
||||
fn initial() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience import to gather all of `ratpack`'s dependencies in one easy place.
|
||||
/// To use:
|
||||
@@ -83,7 +110,9 @@ pub type HTTPResult = Result<(Request<hyper::Body>, Option<Response<hyper::Body>
|
||||
/// use ratpack::prelude::*;
|
||||
/// ```
|
||||
pub mod prelude {
|
||||
pub use crate::{app::App, compose_handler, Error, HTTPResult, Params, ServerError};
|
||||
pub use crate::{
|
||||
app::App, compose_handler, Error, HTTPResult, NoState, Params, ServerError, TransientState,
|
||||
};
|
||||
pub use http::{Request, Response, StatusCode};
|
||||
pub use hyper::Body;
|
||||
}
|
||||
|
||||
+36
-16
@@ -10,10 +10,10 @@ macro_rules! compose_handler {
|
||||
{
|
||||
use $crate::handler::{HandlerFunc, Handler};
|
||||
{
|
||||
let mut funcs: Vec<HandlerFunc<_>> = Vec::new();
|
||||
let mut funcs: Vec<HandlerFunc<_, _>> = Vec::new();
|
||||
|
||||
$(
|
||||
funcs.push(|req, resp, params, app| Box::pin($x(req, resp, params, app)));
|
||||
funcs.push(|req, resp, params, app, state| Box::pin($x(req, resp, params, app, state)));
|
||||
)*
|
||||
|
||||
if funcs.len() == 0 {
|
||||
@@ -22,7 +22,7 @@ macro_rules! compose_handler {
|
||||
|
||||
|
||||
let mut handlers = Vec::new();
|
||||
let mut last: Option<Handler<_>> = None;
|
||||
let mut last: Option<Handler<_, _>> = None;
|
||||
funcs.reverse();
|
||||
|
||||
for func in funcs {
|
||||
@@ -42,7 +42,7 @@ mod tests {
|
||||
use http::{HeaderValue, Request, Response, StatusCode};
|
||||
use hyper::Body;
|
||||
|
||||
use crate::{app::App, Error, HTTPResult, Params};
|
||||
use crate::{app::App, Error, HTTPResult, NoState, Params};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State;
|
||||
@@ -54,11 +54,12 @@ mod tests {
|
||||
mut req: Request<Body>,
|
||||
_response: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
let headers = req.headers_mut();
|
||||
headers.insert("wakka", HeaderValue::from_str("wakka wakka").unwrap());
|
||||
Ok((req, None))
|
||||
Ok((req, None, NoState {}))
|
||||
}
|
||||
|
||||
// this method returns an OK status when the wakka header exists.
|
||||
@@ -66,22 +67,23 @@ mod tests {
|
||||
req: Request<Body>,
|
||||
mut response: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
if let Some(header) = req.headers().get("wakka") {
|
||||
if header != "wakka wakka" {
|
||||
return Err(Error::new("invalid header value"));
|
||||
}
|
||||
|
||||
if response.is_some() {
|
||||
return Ok((req, response));
|
||||
return Ok((req, response, NoState {}));
|
||||
} else {
|
||||
let resp = Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.body(Body::default())?;
|
||||
response.replace(resp);
|
||||
|
||||
return Ok((req, response));
|
||||
return Ok((req, response, NoState {}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +92,14 @@ mod tests {
|
||||
|
||||
let handler = compose_handler!(one, two);
|
||||
|
||||
let (req, response) = handler
|
||||
.perform(Request::default(), None, Params::new(), App::new())
|
||||
let (req, response, _) = handler
|
||||
.perform(
|
||||
Request::default(),
|
||||
None,
|
||||
Params::new(),
|
||||
App::new(),
|
||||
NoState {},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -100,8 +108,14 @@ mod tests {
|
||||
|
||||
let handler = compose_handler!(one);
|
||||
|
||||
let (req, response) = handler
|
||||
.perform(Request::default(), None, Params::new(), App::new())
|
||||
let (req, response, _) = handler
|
||||
.perform(
|
||||
Request::default(),
|
||||
None,
|
||||
Params::new(),
|
||||
App::new(),
|
||||
NoState {},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -111,7 +125,13 @@ mod tests {
|
||||
let handler = compose_handler!(two);
|
||||
|
||||
assert!(handler
|
||||
.perform(Request::default(), None, Params::new(), App::new())
|
||||
.perform(
|
||||
Request::default(),
|
||||
None,
|
||||
Params::new(),
|
||||
App::new(),
|
||||
NoState {}
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
|
||||
+81
-41
@@ -1,30 +1,30 @@
|
||||
use http::{Request, Response};
|
||||
use hyper::Body;
|
||||
|
||||
use crate::{app::App, handler::Handler, path::Path, Error, HTTPResult};
|
||||
use crate::{app::App, handler::Handler, path::Path, Error, HTTPResult, TransientState};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Route<S: Clone + Send> {
|
||||
pub(crate) struct Route<S: Clone + Send, T: TransientState + 'static> {
|
||||
method: http::Method,
|
||||
path: Path,
|
||||
handler: Handler<S>,
|
||||
handler: Handler<S, T>,
|
||||
}
|
||||
|
||||
impl<S: Clone + Send> PartialEq for Route<S> {
|
||||
impl<S: Clone + Send, T: TransientState> PartialEq for Route<S, T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.method.to_string() == other.method.to_string() && self.path.eq(&other.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Clone + Send> Eq for Route<S> {}
|
||||
impl<S: Clone + Send, T: TransientState> Eq for Route<S, T> {}
|
||||
|
||||
impl<S: Clone + Send> PartialOrd for Route<S> {
|
||||
impl<S: Clone + Send, T: TransientState> PartialOrd for Route<S, T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Clone + Send> Ord for Route<S> {
|
||||
impl<S: Clone + Send, T: TransientState> Ord for Route<S, T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let left = self.method.to_string() + " " + &self.path.to_string();
|
||||
let right = other.method.to_string() + " " + &other.path.to_string();
|
||||
@@ -33,8 +33,8 @@ impl<S: Clone + Send> Ord for Route<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Clone + Send> Route<S> {
|
||||
fn new(method: http::Method, path: String, handler: Handler<S>) -> Self {
|
||||
impl<S: Clone + Send, T: TransientState> Route<S, T> {
|
||||
fn new(method: http::Method, path: String, handler: Handler<S, T>) -> Self {
|
||||
Self {
|
||||
method,
|
||||
handler,
|
||||
@@ -46,27 +46,28 @@ impl<S: Clone + Send> Route<S> {
|
||||
&self,
|
||||
provided: String,
|
||||
req: Request<hyper::Body>,
|
||||
app: App<S>,
|
||||
) -> HTTPResult {
|
||||
app: App<S, T>,
|
||||
state: T,
|
||||
) -> HTTPResult<T> {
|
||||
let params = self.path.extract(provided)?;
|
||||
|
||||
if self.method != req.method() {
|
||||
return Err(Error::StatusCode(http::StatusCode::NOT_FOUND));
|
||||
}
|
||||
|
||||
self.handler.perform(req, None, params, app).await
|
||||
self.handler.perform(req, None, params, app, state).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Router<S: Clone + Send>(Vec<Route<S>>);
|
||||
pub(crate) struct Router<S: Clone + Send, T: TransientState + 'static>(Vec<Route<S, T>>);
|
||||
|
||||
impl<S: Clone + Send> Router<S> {
|
||||
impl<S: Clone + Send, T: TransientState + Clone + Send> Router<S, T> {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, method: http::Method, path: String, ch: Handler<S>) -> Self {
|
||||
pub(crate) fn add(&mut self, method: http::Method, path: String, ch: Handler<S, T>) -> Self {
|
||||
self.0.push(Route::new(method, path, ch));
|
||||
self.clone()
|
||||
}
|
||||
@@ -74,13 +75,15 @@ impl<S: Clone + Send> Router<S> {
|
||||
pub(crate) async fn dispatch(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
app: App<S>,
|
||||
app: App<S, T>,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
let path = req.uri().path().to_string();
|
||||
|
||||
for route in self.0.clone() {
|
||||
if route.path.matches(path.to_string()) && route.method.eq(req.method()) {
|
||||
let (_, response) = route.dispatch(path.to_string(), req, app).await?;
|
||||
let (_, response, _) = route
|
||||
.dispatch(path.to_string(), req, app, T::initial())
|
||||
.await?;
|
||||
if response.is_none() {
|
||||
return Err(Error::StatusCode(http::StatusCode::INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
@@ -94,13 +97,12 @@ impl<S: Clone + Send> Router<S> {
|
||||
}
|
||||
|
||||
mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_route_dynamic() {
|
||||
use http::{Method, Request, Response};
|
||||
use hyper::Body;
|
||||
|
||||
use crate::{app::App, handler::Handler, HTTPResult, Params};
|
||||
use crate::{app::App, handler::Handler, HTTPResult, NoState, Params};
|
||||
|
||||
use super::Route;
|
||||
|
||||
@@ -111,14 +113,16 @@ mod tests {
|
||||
req: Request<Body>,
|
||||
_response: Option<Response<Body>>,
|
||||
params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
return Ok((
|
||||
req,
|
||||
Some(Response::builder().status(400).body(Body::from(format!(
|
||||
"hello, {}",
|
||||
*params.get("name").unwrap()
|
||||
)))?),
|
||||
NoState {},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -126,13 +130,15 @@ mod tests {
|
||||
Method::GET,
|
||||
"/a/:name/c".to_string(),
|
||||
Handler::new(
|
||||
|req, resp, params, app| Box::pin(handler_dynamic(req, resp, params, app)),
|
||||
|req, resp, params, app, state| {
|
||||
Box::pin(handler_dynamic(req, resp, params, app, state))
|
||||
},
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
assert!(route
|
||||
.dispatch("/a".to_string(), Request::default(), App::new())
|
||||
.dispatch("/a".to_string(), Request::default(), App::new(), NoState {})
|
||||
.await
|
||||
.is_err());
|
||||
assert!(route
|
||||
@@ -143,6 +149,7 @@ mod tests {
|
||||
.body(Body::from("one=two".as_bytes()))
|
||||
.unwrap(),
|
||||
App::new(),
|
||||
NoState {},
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
@@ -151,7 +158,12 @@ mod tests {
|
||||
"erik", "adam", "sean", "travis", "joseph", "grant", "joy", "steve", "marc",
|
||||
] {
|
||||
assert!(route
|
||||
.dispatch("/a/:name/c".to_string(), Request::default(), App::new())
|
||||
.dispatch(
|
||||
"/a/:name/c".to_string(),
|
||||
Request::default(),
|
||||
App::new(),
|
||||
NoState {}
|
||||
)
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
@@ -159,7 +171,7 @@ mod tests {
|
||||
|
||||
let body = hyper::body::to_bytes(
|
||||
route
|
||||
.dispatch(path.clone(), Request::default(), App::new())
|
||||
.dispatch(path.clone(), Request::default(), App::new(), NoState {})
|
||||
.await
|
||||
.unwrap()
|
||||
.1
|
||||
@@ -172,7 +184,7 @@ mod tests {
|
||||
assert_eq!(body, format!("hello, {}", name).as_bytes());
|
||||
|
||||
let status = route
|
||||
.dispatch(path, Request::default(), App::new())
|
||||
.dispatch(path, Request::default(), App::new(), NoState {})
|
||||
.await
|
||||
.unwrap()
|
||||
.1
|
||||
@@ -188,7 +200,7 @@ mod tests {
|
||||
use http::{Method, Request, Response};
|
||||
use hyper::Body;
|
||||
|
||||
use crate::{app::App, handler::Handler, HTTPResult, Params};
|
||||
use crate::{app::App, handler::Handler, HTTPResult, NoState, Params};
|
||||
|
||||
use super::Route;
|
||||
|
||||
@@ -199,8 +211,9 @@ mod tests {
|
||||
req: Request<Body>,
|
||||
_response: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
return Ok((
|
||||
req,
|
||||
Some(
|
||||
@@ -208,6 +221,7 @@ mod tests {
|
||||
.status(400)
|
||||
.body(Body::from("hello, world".as_bytes()))?,
|
||||
),
|
||||
NoState {},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -215,13 +229,15 @@ mod tests {
|
||||
Method::GET,
|
||||
"/a/b/c".to_string(),
|
||||
Handler::new(
|
||||
|req, resp, params, app| Box::pin(handler_static(req, resp, params, app)),
|
||||
|req, resp, params, app, state| {
|
||||
Box::pin(handler_static(req, resp, params, app, state))
|
||||
},
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
assert!(route
|
||||
.dispatch("/a".to_string(), Request::default(), App::new())
|
||||
.dispatch("/a".to_string(), Request::default(), App::new(), NoState {})
|
||||
.await
|
||||
.is_err());
|
||||
assert!(route
|
||||
@@ -232,18 +248,29 @@ mod tests {
|
||||
.body(Body::from("one=two".as_bytes()))
|
||||
.unwrap(),
|
||||
App::new(),
|
||||
NoState {},
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
assert!(route
|
||||
.dispatch("/a/b/c".to_string(), Request::default(), App::new())
|
||||
.dispatch(
|
||||
"/a/b/c".to_string(),
|
||||
Request::default(),
|
||||
App::new(),
|
||||
NoState {}
|
||||
)
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
let body = hyper::body::to_bytes(
|
||||
route
|
||||
.dispatch("/a/b/c".to_string(), Request::default(), App::new())
|
||||
.dispatch(
|
||||
"/a/b/c".to_string(),
|
||||
Request::default(),
|
||||
App::new(),
|
||||
NoState {},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.1
|
||||
@@ -256,7 +283,12 @@ mod tests {
|
||||
assert_eq!(body, "hello, world".as_bytes());
|
||||
|
||||
let status = route
|
||||
.dispatch("/a/b/c".to_string(), Request::default(), App::new())
|
||||
.dispatch(
|
||||
"/a/b/c".to_string(),
|
||||
Request::default(),
|
||||
App::new(),
|
||||
NoState {},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.1
|
||||
@@ -269,7 +301,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_router() {
|
||||
use super::Router;
|
||||
use crate::{app::App, handler::Handler, HTTPResult, Params};
|
||||
use crate::{app::App, handler::Handler, HTTPResult, NoState, Params};
|
||||
use http::{Method, Request, Response};
|
||||
use hyper::Body;
|
||||
|
||||
@@ -280,14 +312,16 @@ mod tests {
|
||||
req: Request<Body>,
|
||||
_response: Option<Response<Body>>,
|
||||
params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
return Ok((
|
||||
req,
|
||||
Some(Response::builder().status(400).body(Body::from(format!(
|
||||
"hello, {}",
|
||||
*params.get("name").unwrap()
|
||||
)))?),
|
||||
NoState {},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -295,8 +329,9 @@ mod tests {
|
||||
req: Request<Body>,
|
||||
_response: Option<Response<Body>>,
|
||||
_params: Params,
|
||||
_app: App<State>,
|
||||
) -> HTTPResult {
|
||||
_app: App<State, NoState>,
|
||||
_state: NoState,
|
||||
) -> HTTPResult<NoState> {
|
||||
return Ok((
|
||||
req,
|
||||
Some(
|
||||
@@ -304,6 +339,7 @@ mod tests {
|
||||
.status(400)
|
||||
.body(Body::from("hello, world".as_bytes()))?,
|
||||
),
|
||||
NoState {},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -313,7 +349,9 @@ mod tests {
|
||||
Method::GET,
|
||||
"/a/b/c".to_string(),
|
||||
Handler::new(
|
||||
|req, resp, params, app| Box::pin(handler_static(req, resp, params, app)),
|
||||
|req, resp, params, app, state| {
|
||||
Box::pin(handler_static(req, resp, params, app, state))
|
||||
},
|
||||
None,
|
||||
),
|
||||
);
|
||||
@@ -322,7 +360,9 @@ mod tests {
|
||||
Method::GET,
|
||||
"/c/b/a/:name".to_string(),
|
||||
Handler::new(
|
||||
|req, resp, params, app| Box::pin(handler_dynamic(req, resp, params, app)),
|
||||
|req, resp, params, app, state| {
|
||||
Box::pin(handler_dynamic(req, resp, params, app, state))
|
||||
},
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user