diff --git a/gotham_restful/src/result.rs b/gotham_restful/src/result.rs index d6ff755..3108749 100644 --- a/gotham_restful/src/result.rs +++ b/gotham_restful/src/result.rs @@ -1,6 +1,8 @@ use crate::{ResponseBody, StatusCode}; #[cfg(feature = "openapi")] use crate::{OpenapiSchema, OpenapiType}; +use futures_core::future::Future; +use futures_util::{future, future::FutureExt}; use gotham::hyper::Body; #[cfg(feature = "errorlog")] use log::error; @@ -11,7 +13,8 @@ use serde::Serialize; use serde_json::error::Error as SerdeJsonError; use std::{ error::Error, - fmt::Debug + fmt::Debug, + pin::Pin }; /// A response, used to create the final gotham response from. @@ -75,12 +78,15 @@ impl Response } } + /// A trait provided to convert a resource's result to json. pub trait ResourceResult { + type Err : Error + Send + 'static; + /// Turn this into a response that can be returned to the browser. This api will likely /// change in the future. - fn into_response(self) -> Result; + fn into_response(self) -> Pin> + Send>>; /// Return a list of supported mime types. fn accepted_types() -> Option> @@ -126,6 +132,15 @@ impl From for ResourceError } } +fn into_response_helper(create_response : F) -> Pin> + Send>> +where + Err : Send + 'static, + F : FnOnce() -> Result +{ + let res = create_response(); + async move { res }.boxed() +} + #[cfg(feature = "errorlog")] fn errorlog(e : E) { @@ -137,15 +152,19 @@ fn errorlog(_e : E) {} impl ResourceResult for Result { - fn into_response(self) -> Result + type Err = SerdeJsonError; + + fn into_response(self) -> Pin> + Send>> { - Ok(match self { - Ok(r) => Response::json(StatusCode::OK, serde_json::to_string(&r)?), - Err(e) => { - errorlog(&e); - let err : ResourceError = e.into(); - Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?) - } + into_response_helper(|| { + Ok(match self { + Ok(r) => Response::json(StatusCode::OK, serde_json::to_string(&r)?), + Err(e) => { + errorlog(&e); + let err : ResourceError = e.into(); + Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?) + } + }) }) } @@ -161,6 +180,40 @@ impl ResourceResult for Result } } + +impl ResourceResult for Pin + Send>> +where + Res : ResourceResult + 'static, + dyn Future> : Send +{ + type Err = Res::Err; + + fn into_response(self) -> Pin> + Send>> + { + self.then(|result| { + result.into_response() + }).boxed() + } + + fn accepted_types() -> Option> + { + Res::accepted_types() + } + + #[cfg(feature = "openapi")] + fn schema() -> OpenapiSchema + { + Res::schema() + } + + #[cfg(feature = "openapi")] + fn default_status() -> StatusCode + { + Res::default_status() + } +} + + /** This can be returned from a resource when there is no cause of an error. For example: @@ -215,9 +268,11 @@ impl Debug for Success impl ResourceResult for Success { - fn into_response(self) -> Result + type Err = SerdeJsonError; + + fn into_response(self) -> Pin> + Send>> { - Ok(Response::json(StatusCode::OK, serde_json::to_string(&self.0)?)) + into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(&self.0)?))) } fn accepted_types() -> Option> @@ -318,12 +373,14 @@ impl Debug for AuthResult impl ResourceResult for AuthResult { - fn into_response(self) -> Result + type Err = T::Err; + + fn into_response(self) -> Pin> + Send>> { match self { Self::Ok(res) => res.into_response(), - Self::AuthErr => Ok(Response::forbidden()) + Self::AuthErr => future::ok(Response::forbidden()).boxed() } } @@ -379,10 +436,12 @@ impl From<()> for NoContent impl ResourceResult for NoContent { + type Err = SerdeJsonError; // just for easier handling of `Result` + /// This will always be a _204 No Content_ together with an empty string. - fn into_response(self) -> Result + fn into_response(self) -> Pin> + Send>> { - Ok(Response::no_content()) + future::ok(Response::no_content()).boxed() } /// Returns the schema of the `()` type. @@ -402,14 +461,16 @@ impl ResourceResult for NoContent impl ResourceResult for Result { - fn into_response(self) -> Result + type Err = SerdeJsonError; + + fn into_response(self) -> Pin> + Send>> { match self { Ok(nc) => nc.into_response(), - Err(e) => { + Err(e) => into_response_helper(|| { let err : ResourceError = e.into(); Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?)) - } + }) } } @@ -460,9 +521,11 @@ impl Debug for Raw impl> ResourceResult for Raw { - fn into_response(self) -> Result + type Err = SerdeJsonError; // just for easier handling of `Result, E>` + + fn into_response(self) -> Pin> + Send>> { - Ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))) + future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed() } fn accepted_types() -> Option> @@ -482,16 +545,18 @@ impl> ResourceResult for Raw impl ResourceResult for Result, E> where - Raw : ResourceResult + Raw : ResourceResult { - fn into_response(self) -> Result + type Err = SerdeJsonError; + + fn into_response(self) -> Pin> + Send>> { match self { Ok(raw) => raw.into_response(), - Err(e) => { + Err(e) => into_response_helper(|| { let err : ResourceError = e.into(); Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?)) - } + }) } } @@ -512,6 +577,7 @@ where mod test { use super::*; + use futures_executor::block_on; use mime::TEXT_PLAIN; use thiserror::Error; @@ -530,7 +596,7 @@ mod test fn resource_result_ok() { let ok : Result = Ok(Msg::default()); - let res = ok.into_response().expect("didn't expect error response"); + let res = block_on(ok.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.mime, Some(APPLICATION_JSON)); assert_eq!(res.full_body().unwrap(), r#"{"msg":""}"#.as_bytes()); @@ -540,7 +606,7 @@ mod test fn resource_result_err() { let err : Result = Err(MsgError::default()); - let res = err.into_response().expect("didn't expect error response"); + let res = block_on(err.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(res.mime, Some(APPLICATION_JSON)); assert_eq!(res.full_body().unwrap(), format!(r#"{{"error":true,"message":"{}"}}"#, MsgError::default()).as_bytes()); @@ -550,7 +616,7 @@ mod test fn success_always_successfull() { let success : Success = Msg::default().into(); - let res = success.into_response().expect("didn't expect error response"); + let res = block_on(success.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.mime, Some(APPLICATION_JSON)); assert_eq!(res.full_body().unwrap(), r#"{"msg":""}"#.as_bytes()); @@ -560,7 +626,7 @@ mod test fn no_content_has_empty_response() { let no_content = NoContent::default(); - let res = no_content.into_response().expect("didn't expect error response"); + let res = block_on(no_content.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::NO_CONTENT); assert_eq!(res.mime, None); assert_eq!(res.full_body().unwrap(), &[] as &[u8]); @@ -570,7 +636,7 @@ mod test fn no_content_result() { let no_content : Result = Ok(NoContent::default()); - let res = no_content.into_response().expect("didn't expect error response"); + let res = block_on(no_content.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::NO_CONTENT); assert_eq!(res.mime, None); assert_eq!(res.full_body().unwrap(), &[] as &[u8]); @@ -581,7 +647,7 @@ mod test { let msg = "Test"; let raw = Raw::new(msg, TEXT_PLAIN); - let res = raw.into_response().expect("didn't expect error response"); + let res = block_on(raw.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.mime, Some(TEXT_PLAIN)); assert_eq!(res.full_body().unwrap(), msg.as_bytes()); diff --git a/gotham_restful/src/routing.rs b/gotham_restful/src/routing.rs index 6385300..8aac5ea 100644 --- a/gotham_restful/src/routing.rs +++ b/gotham_restful/src/routing.rs @@ -7,9 +7,10 @@ use crate::{ #[cfg(feature = "openapi")] use crate::OpenapiRouter; +use futures_core::future::Future; use futures_util::{future, future::FutureExt}; use gotham::{ - handler::{HandlerFuture, IntoHandlerError, IntoHandlerFuture}, + handler::{HandlerError, HandlerFuture, IntoHandlerError, IntoHandlerFuture}, helpers::http::response::{create_empty_response, create_response}, pipeline::chain::PipelineHandleChain, router::{ @@ -65,14 +66,40 @@ pub trait DrawResources /// `Resource::setup` method. pub trait DrawResourceRoutes { - fn read_all(&mut self); - fn read(&mut self); - fn search(&mut self); - fn create(&mut self); - fn update_all(&mut self); - fn update(&mut self); - fn delete_all(&mut self); - fn delete(&mut self); + fn read_all(&mut self) + where + dyn Future::Err>> : Send; + + fn read(&mut self) + where + dyn Future::Err>> : Send; + + fn search(&mut self) + where + dyn Future::Err>> : Send; + + fn create(&mut self) + where + Handler::Res : Send + 'static, + Handler::Body : 'static; + + fn update_all(&mut self) + where + Handler::Res : Send + 'static, + Handler::Body : 'static; + + fn update(&mut self) + where + Handler::Res : Send + 'static, + Handler::Body : 'static; + + fn delete_all(&mut self) + where + dyn Future::Err>> : Send; + + fn delete(&mut self) + where + dyn Future::Err>> : Send; } fn response_from(res : Response, state : &State) -> hyper::Response @@ -92,74 +119,93 @@ fn response_from(res : Response, state : &State) -> hyper::Response fn to_handler_future(mut state : State, get_result : F) -> Pin> where F : FnOnce(&mut State) -> R, - R : ResourceResult + R : ResourceResult, + dyn Future> : Send { - let res = get_result(&mut state).into_response(); - match res { - Ok(res) => { - let r = response_from(res, &state); - (state, r).into_handler_future() - }, - Err(e) => future::err((state, e.into_handler_error())).boxed() - } -} - -fn handle_with_body(mut state : State, get_result : F) -> Pin> -where - Body : RequestBody, - F : FnOnce(&mut State, Body) -> R + Send + 'static, - R : ResourceResult -{ - let f = to_bytes(gotham::hyper::Body::take_from(&mut state)) - .then(|body| { - - let body = match body { - Ok(body) => body, - Err(e) => return future::err((state, e.into_handler_error())) - }; - - let content_type : Mime = match HeaderMap::borrow_from(&state).get(CONTENT_TYPE) { - Some(content_type) => content_type.to_str().unwrap().parse().unwrap(), - None => { - let res = create_empty_response(&state, StatusCode::UNSUPPORTED_MEDIA_TYPE); - return future::ok((state, res)) - } - }; - - let body = match Body::from_body(body, content_type) { - Ok(body) => body, - Err(e) => return { - let error : ResourceError = e.into(); - match serde_json::to_string(&error) { - Ok(json) => { - let res = create_response(&state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json); - future::ok((state, res)) - }, - Err(e) => future::err((state, e.into_handler_error())) - } - } - }; - - let res = get_result(&mut state, body).into_response(); + get_result(&mut state).into_response() + .then(|res| match res { Ok(res) => { let r = response_from(res, &state); - future::ok((state, r)) + (state, r).into_handler_future() }, - Err(e) => future::err((state, e.into_handler_error())) + Err(e) => future::err((state, e.into_handler_error())).boxed() } - - }); + ).boxed() +} - f.boxed() +async fn body_to_res(state : &mut State, get_result : F) -> Result, HandlerError> +where + B : RequestBody, + F : FnOnce(&mut State, B) -> R, + R : ResourceResult +{ + let body = to_bytes(Body::take_from(&mut state)).await; + + let body = match body { + Ok(body) => body, + Err(e) => return Err(e.into_handler_error()) + }; + + let content_type : Mime = match HeaderMap::borrow_from(&state).get(CONTENT_TYPE) { + Some(content_type) => content_type.to_str().unwrap().parse().unwrap(), + None => { + let res = create_empty_response(&state, StatusCode::UNSUPPORTED_MEDIA_TYPE); + return Ok(res) + } + }; + + let res = { + let body = match B::from_body(body, content_type) { + Ok(body) => body, + Err(e) => return { + let error : ResourceError = e.into(); + match serde_json::to_string(&error) { + Ok(json) => { + let res = create_response(&state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json); + Ok(res) + }, + Err(e) => Err(e.into_handler_error()) + } + } + }; + get_result(&mut state, body) + }; + + let res = res.into_response().await; + match res { + Ok(res) => { + let r = response_from(res, &state); + Ok(r) + }, + Err(e) => Err(e.into_handler_error()) + } +} + +fn handle_with_body(mut state : State, get_result : F) -> Pin> +where + B : RequestBody + 'static, + F : FnOnce(&mut State, B) -> R + Send + 'static, + R : ResourceResult + Send + 'static +{ + body_to_res(&mut state, get_result) + .then(|res| match res { + Ok(ok) => future::ok((state, ok)), + Err(err) => future::err((state, err)) + }) + .boxed() } fn read_all_handler(state : State) -> Pin> +where + dyn Future::Err>> : Send { to_handler_future(state, |state| Handler::read_all(state)) } fn read_handler(state : State) -> Pin> +where + dyn Future::Err>> : Send { let id = { let path : &PathExtractor = PathExtractor::borrow_from(&state); @@ -169,22 +215,33 @@ fn read_handler(state : State) -> Pin } fn search_handler(mut state : State) -> Pin> +where + dyn Future::Err>> : Send { let query = Handler::Query::take_from(&mut state); to_handler_future(state, |state| Handler::search(state, query)) } fn create_handler(state : State) -> Pin> +where + Handler::Res : Send + 'static, + Handler::Body : 'static { handle_with_body::(state, |state, body| Handler::create(state, body)) } fn update_all_handler(state : State) -> Pin> +where + Handler::Res : Send + 'static, + Handler::Body : 'static { handle_with_body::(state, |state, body| Handler::update_all(state, body)) } fn update_handler(state : State) -> Pin> +where + Handler::Res : Send + 'static, + Handler::Body : 'static { let id = { let path : &PathExtractor = PathExtractor::borrow_from(&state); @@ -194,11 +251,15 @@ fn update_handler(state : State) -> Pin(state : State) -> Pin> +where + dyn Future::Err>> : Send { to_handler_future(state, |state| Handler::delete_all(state)) } fn delete_handler(state : State) -> Pin> +where + dyn Future::Err>> : Send { let id = { let path : &PathExtractor = PathExtractor::borrow_from(&state); @@ -297,6 +358,8 @@ macro_rules! implDrawResourceRoutes { P : RefUnwindSafe + Send + Sync + 'static { fn read_all(&mut self) + where + dyn Future::Err>> : Send { let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0.get(&self.1) @@ -305,6 +368,8 @@ macro_rules! implDrawResourceRoutes { } fn read(&mut self) + where + dyn Future::Err>> : Send { let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0.get(&format!("{}/:id", self.1)) @@ -314,6 +379,8 @@ macro_rules! implDrawResourceRoutes { } fn search(&mut self) + where + dyn Future::Err>> : Send { let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0.get(&format!("{}/search", self.1)) @@ -323,6 +390,9 @@ macro_rules! implDrawResourceRoutes { } fn create(&mut self) + where + Handler::Res : Send + 'static, + Handler::Body : 'static { let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); @@ -333,6 +403,9 @@ macro_rules! implDrawResourceRoutes { } fn update_all(&mut self) + where + Handler::Res : Send + 'static, + Handler::Body : 'static { let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); @@ -343,6 +416,9 @@ macro_rules! implDrawResourceRoutes { } fn update(&mut self) + where + Handler::Res : Send + 'static, + Handler::Body : 'static { let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); @@ -354,6 +430,8 @@ macro_rules! implDrawResourceRoutes { } fn delete_all(&mut self) + where + dyn Future::Err>> : Send { let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0.delete(&self.1) @@ -362,6 +440,8 @@ macro_rules! implDrawResourceRoutes { } fn delete(&mut self) + where + dyn Future::Err>> : Send { let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0.delete(&format!("{}/:id", self.1))