diff --git a/gotham_restful/src/result.rs b/gotham_restful/src/result.rs index 86c10be..6774473 100644 --- a/gotham_restful/src/result.rs +++ b/gotham_restful/src/result.rs @@ -1,6 +1,7 @@ use crate::{ResourceType, StatusCode}; #[cfg(feature = "openapi")] use crate::{OpenapiSchema, OpenapiType}; +use mime::{Mime, APPLICATION_JSON}; use serde::Serialize; use serde_json::error::Error as SerdeJsonError; use std::error::Error; @@ -8,7 +9,15 @@ use std::error::Error; /// A trait provided to convert a resource's result to json. pub trait ResourceResult { - fn to_json(&self) -> Result<(StatusCode, String), SerdeJsonError>; + /// Turn this into a response that can be returned to the browser. This api will likely + /// change in the future. + fn to_response(&self) -> Result<(StatusCode, String), SerdeJsonError>; + + /// Return a list of supported mime types. + fn accepted_types() -> Option> + { + None + } #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema; @@ -50,7 +59,7 @@ impl From for ResourceError impl ResourceResult for Result { - fn to_json(&self) -> Result<(StatusCode, String), SerdeJsonError> + fn to_response(&self) -> Result<(StatusCode, String), SerdeJsonError> { Ok(match self { Ok(r) => (StatusCode::OK, serde_json::to_string(r)?), @@ -61,6 +70,11 @@ impl ResourceResult for Result }) } + fn accepted_types() -> Option> + { + Some(vec![APPLICATION_JSON]) + } + #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { @@ -105,11 +119,16 @@ impl From for Success impl ResourceResult for Success { - fn to_json(&self) -> Result<(StatusCode, String), SerdeJsonError> + fn to_response(&self) -> Result<(StatusCode, String), SerdeJsonError> { Ok((StatusCode::OK, serde_json::to_string(&self.0)?)) } + fn accepted_types() -> Option> + { + Some(vec![APPLICATION_JSON]) + } + #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { @@ -150,7 +169,7 @@ impl From<()> for NoContent impl ResourceResult for NoContent { /// This will always be a _204 No Content_ together with an empty string. - fn to_json(&self) -> Result<(StatusCode, String), SerdeJsonError> + fn to_response(&self) -> Result<(StatusCode, String), SerdeJsonError> { Ok((Self::default_status(), "".to_string())) } @@ -172,7 +191,7 @@ impl ResourceResult for NoContent impl ResourceResult for Result { - fn to_json(&self) -> Result<(StatusCode, String), SerdeJsonError> + fn to_response(&self) -> Result<(StatusCode, String), SerdeJsonError> { Ok(match self { Ok(_) => (Self::default_status(), "".to_string()), @@ -217,7 +236,7 @@ mod test fn resource_result_ok() { let ok : Result = Ok(Msg::default()); - let (status, json) = ok.to_json().expect("didn't expect error response"); + let (status, json) = ok.to_response().expect("didn't expect error response"); assert_eq!(status, StatusCode::OK); assert_eq!(json, r#"{"msg":""}"#); } @@ -226,7 +245,7 @@ mod test fn resource_result_err() { let err : Result = Err(MsgError::default()); - let (status, json) = err.to_json().expect("didn't expect error response"); + let (status, json) = err.to_response().expect("didn't expect error response"); assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(json, format!(r#"{{"error":true,"message":"{}"}}"#, err.unwrap_err())); } @@ -235,7 +254,7 @@ mod test fn success_always_successfull() { let success : Success = Msg::default().into(); - let (status, json) = success.to_json().expect("didn't expect error response"); + let (status, json) = success.to_response().expect("didn't expect error response"); assert_eq!(status, StatusCode::OK); assert_eq!(json, r#"{"msg":""}"#); } @@ -244,7 +263,7 @@ mod test fn no_content_has_empty_json() { let no_content = NoContent::default(); - let (status, json) = no_content.to_json().expect("didn't expect error response"); + let (status, json) = no_content.to_response().expect("didn't expect error response"); assert_eq!(status, StatusCode::NO_CONTENT); assert_eq!(json, ""); } @@ -253,8 +272,8 @@ mod test fn no_content_result() { let no_content = NoContent::default(); - let res_def = no_content.to_json().expect("didn't expect error response"); - let res_err = Result::::Ok(no_content).to_json().expect("didn't expect error response"); + let res_def = no_content.to_response().expect("didn't expect error response"); + let res_err = Result::::Ok(no_content).to_response().expect("didn't expect error response"); assert_eq!(res_def, res_err); } } diff --git a/gotham_restful/src/routing.rs b/gotham_restful/src/routing.rs index 8907541..7e60201 100644 --- a/gotham_restful/src/routing.rs +++ b/gotham_restful/src/routing.rs @@ -16,11 +16,15 @@ use gotham::{ handler::{HandlerFuture, IntoHandlerError}, helpers::http::response::create_response, pipeline::chain::PipelineHandleChain, - router::builder::*, + router::{ + builder::*, + non_match::RouteNonMatch, + route::matcher::{AcceptHeaderRouteMatcher, RouteMatcher} + }, state::{FromState, State} }; use hyper::Body; -use mime::APPLICATION_JSON; +use mime::{Mime, APPLICATION_JSON}; use serde::de::DeserializeOwned; use std::panic::RefUnwindSafe; @@ -109,7 +113,7 @@ where F : FnOnce(&mut State) -> R, R : ResourceResult { - let res = get_result(&mut state).to_json(); + let res = get_result(&mut state).to_response(); match res { Ok((status, body)) => { let res = create_response(&state, status, APPLICATION_JSON, body); @@ -148,7 +152,7 @@ where } }; - let res = get_result(&mut state, body).to_json(); + let res = get_result(&mut state, body).to_response(); match res { Ok((status, body)) => { let res = create_response(&state, status, APPLICATION_JSON, body); @@ -246,6 +250,33 @@ where to_handler_future(state, |state| Handler::delete(state, id)) } +#[derive(Clone)] +struct MaybeMatchAcceptHeader +{ + matcher : Option +} + +impl RouteMatcher for MaybeMatchAcceptHeader +{ + fn is_match(&self, state : &State) -> Result<(), RouteNonMatch> + { + match &self.matcher { + Some(matcher) => matcher.is_match(state), + None => Ok(()) + } + } +} + +impl From>> for MaybeMatchAcceptHeader +{ + fn from(types : Option>) -> Self + { + Self { + matcher: types.map(AcceptHeaderRouteMatcher::new) + } + } +} + macro_rules! implDrawResourceRoutes { ($implType:ident) => { @@ -289,7 +320,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceReadAll { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.get(&self.1) + .extend_route_matcher(matcher) .to(|state| read_all_handler::(state)); } @@ -299,7 +332,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceRead { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.get(&format!("{}/:id", self.1)) + .extend_route_matcher(matcher) .with_path_extractor::>() .to(|state| read_handler::(state)); } @@ -310,7 +345,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceSearch { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.get(&format!("{}/search", self.1)) + .extend_route_matcher(matcher) .with_query_string_extractor::() .to(|state| search_handler::(state)); } @@ -321,7 +358,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceCreate { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.post(&self.1) + .extend_route_matcher(matcher) .to(|state| create_handler::(state)); } @@ -331,7 +370,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceUpdateAll { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.put(&self.1) + .extend_route_matcher(matcher) .to(|state| update_all_handler::(state)); } @@ -342,7 +383,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceUpdate { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.put(&format!("{}/:id", self.1)) + .extend_route_matcher(matcher) .with_path_extractor::>() .to(|state| update_handler::(state)); } @@ -352,7 +395,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceDeleteAll { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.delete(&self.1) + .extend_route_matcher(matcher) .to(|state| delete_all_handler::(state)); } @@ -362,7 +407,9 @@ macro_rules! implDrawResourceRoutes { Res : ResourceResult, Handler : ResourceDelete { + let matcher : MaybeMatchAcceptHeader = Res::accepted_types().into(); self.0.delete(&format!("{}/:id", self.1)) + .extend_route_matcher(matcher) .with_path_extractor::>() .to(|state| delete_handler::(state)); }