#[cfg(feature = "openapi")] use crate::openapi::{ builder::{OpenapiBuilder, OpenapiInfo}, router::OpenapiRouter }; #[cfg(feature = "cors")] use crate::CorsRoute; use crate::{ resource::{ Resource, ResourceChange, ResourceChangeAll, ResourceCreate, ResourceRead, ResourceReadAll, ResourceRemove, ResourceRemoveAll, ResourceSearch }, result::{ResourceError, ResourceResult}, RequestBody, Response, StatusCode }; use futures_util::{future, future::FutureExt}; use gotham::{ handler::{HandlerError, HandlerFuture}, helpers::http::response::{create_empty_response, create_response}, hyper::{body::to_bytes, header::CONTENT_TYPE, Body, HeaderMap, Method}, pipeline::chain::PipelineHandleChain, router::{ builder::{DefineSingleRoute, DrawRoutes, ExtendRouteMatcher, RouterBuilder, ScopeBuilder}, non_match::RouteNonMatch, route::matcher::{AcceptHeaderRouteMatcher, ContentTypeHeaderRouteMatcher, RouteMatcher} }, state::{FromState, State} }; use mime::{Mime, APPLICATION_JSON}; use std::{future::Future, panic::RefUnwindSafe, pin::Pin}; /// Allow us to extract an id from a path. #[derive(Deserialize, StateData, StaticResponseExtender)] struct PathExtractor { id: ID } /// This trait adds the `with_openapi` method to gotham's routing. It turns the default /// router into one that will only allow RESTful resources, but record them and generate /// an OpenAPI specification on request. #[cfg(feature = "openapi")] pub trait WithOpenapi { fn with_openapi(&mut self, info: OpenapiInfo, block: F) where F: FnOnce(OpenapiRouter<'_, D>); } /// This trait adds the `resource` method to gotham's routing. It allows you to register /// any RESTful [Resource] with a path. pub trait DrawResources { fn resource(&mut self, path: &str); } /// This trait allows to draw routes within an resource. Use this only inside the /// [Resource::setup] method. pub trait DrawResourceRoutes { fn read_all(&mut self); fn read(&mut self); fn search(&mut self); fn create(&mut self) where Handler::Res: 'static, Handler::Body: 'static; fn change_all(&mut self) where Handler::Res: 'static, Handler::Body: 'static; fn change(&mut self) where Handler::Res: 'static, Handler::Body: 'static; fn remove_all(&mut self); fn remove(&mut self); } fn response_from(res: Response, state: &State) -> gotham::hyper::Response { let mut r = create_empty_response(state, res.status); let headers = r.headers_mut(); if let Some(mime) = res.mime { headers.insert(CONTENT_TYPE, mime.as_ref().parse().unwrap()); } let mut last_name = None; for (name, value) in res.headers { if name.is_some() { last_name = name; } // this unwrap is safe: the first item will always be Some let name = last_name.clone().unwrap(); headers.insert(name, value); } let method = Method::borrow_from(state); if method != Method::HEAD { *r.body_mut() = res.body; } #[cfg(feature = "cors")] crate::cors::handle_cors(state, &mut r); r } async fn to_handler_future( state: State, get_result: F ) -> Result<(State, gotham::hyper::Response), (State, HandlerError)> where F: FnOnce(State) -> Pin + Send>>, R: ResourceResult { let (state, res) = get_result(state).await; let res = res.into_response().await; match res { Ok(res) => { let r = response_from(res, &state); Ok((state, r)) }, Err(e) => Err((state, e.into())) } } async fn body_to_res( mut state: State, get_result: F ) -> (State, Result, HandlerError>) where B: RequestBody, F: FnOnce(State, B) -> Pin + Send>>, R: ResourceResult { let body = to_bytes(Body::take_from(&mut state)).await; let body = match body { Ok(body) => body, Err(e) => return (state, Err(e.into())) }; 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 (state, Ok(res)); } }; let res = { let body = match B::from_body(body, content_type) { Ok(body) => body, Err(e) => { let error: ResourceError = e.into(); let res = 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()) }; return (state, res); } }; get_result(state, body) }; let (state, res) = res.await; let res = res.into_response().await; let res = match res { Ok(res) => { let r = response_from(res, &state); Ok(r) }, Err(e) => Err(e.into()) }; (state, res) } fn handle_with_body(state: State, get_result: F) -> Pin> where B: RequestBody + 'static, F: FnOnce(State, B) -> Pin + Send>> + Send + 'static, R: ResourceResult + Send + 'static { body_to_res(state, get_result) .then(|(state, res)| match res { Ok(ok) => future::ok((state, ok)), Err(err) => future::err((state, err)) }) .boxed() } fn read_all_handler(state: State) -> Pin> { to_handler_future(state, |state| Handler::read_all(state)).boxed() } fn read_handler(state: State) -> Pin> { let id = { let path: &PathExtractor = PathExtractor::borrow_from(&state); path.id.clone() }; to_handler_future(state, |state| Handler::read(state, id)).boxed() } fn search_handler(mut state: State) -> Pin> { let query = Handler::Query::take_from(&mut state); to_handler_future(state, |state| Handler::search(state, query)).boxed() } fn create_handler(state: State) -> Pin> where Handler::Res: 'static, Handler::Body: 'static { handle_with_body::(state, |state, body| Handler::create(state, body)) } fn change_all_handler(state: State) -> Pin> where Handler::Res: 'static, Handler::Body: 'static { handle_with_body::(state, |state, body| Handler::change_all(state, body)) } fn change_handler(state: State) -> Pin> where Handler::Res: 'static, Handler::Body: 'static { let id = { let path: &PathExtractor = PathExtractor::borrow_from(&state); path.id.clone() }; handle_with_body::(state, |state, body| Handler::change(state, id, body)) } fn remove_all_handler(state: State) -> Pin> { to_handler_future(state, |state| Handler::remove_all(state)).boxed() } fn remove_handler(state: State) -> Pin> { let id = { let path: &PathExtractor = PathExtractor::borrow_from(&state); path.id.clone() }; to_handler_future(state, |state| Handler::remove(state, id)).boxed() } #[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 { let types = match types { Some(types) if types.is_empty() => None, types => types }; Self { matcher: types.map(AcceptHeaderRouteMatcher::new) } } } #[derive(Clone)] struct MaybeMatchContentTypeHeader { matcher: Option } impl RouteMatcher for MaybeMatchContentTypeHeader { fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> { match &self.matcher { Some(matcher) => matcher.is_match(state), None => Ok(()) } } } impl From>> for MaybeMatchContentTypeHeader { fn from(types: Option>) -> Self { Self { matcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type()) } } } macro_rules! implDrawResourceRoutes { ($implType:ident) => { #[cfg(feature = "openapi")] impl<'a, C, P> WithOpenapi for $implType<'a, C, P> where C: PipelineHandleChain

+ Copy + Send + Sync + 'static, P: RefUnwindSafe + Send + Sync + 'static { fn with_openapi(&mut self, info: OpenapiInfo, block: F) where F: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>) { let router = OpenapiRouter { router: self, scope: None, openapi_builder: &mut OpenapiBuilder::new(info) }; block(router); } } impl<'a, C, P> DrawResources for $implType<'a, C, P> where C: PipelineHandleChain

+ Copy + Send + Sync + 'static, P: RefUnwindSafe + Send + Sync + 'static { fn resource(&mut self, path: &str) { R::setup((self, path)); } } #[allow(clippy::redundant_closure)] // doesn't work because of type parameters impl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str) where C: PipelineHandleChain

+ Copy + Send + Sync + 'static, P: RefUnwindSafe + Send + Sync + 'static { fn read_all(&mut self) { let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0 .get(&self.1) .extend_route_matcher(matcher) .to(|state| read_all_handler::(state)); } fn read(&mut self) { let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0 .get(&format!("{}/:id", self.1)) .extend_route_matcher(matcher) .with_path_extractor::>() .to(|state| read_handler::(state)); } fn search(&mut self) { let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0 .get(&format!("{}/search", self.1)) .extend_route_matcher(matcher) .with_query_string_extractor::() .to(|state| search_handler::(state)); } 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(); self.0 .post(&self.1) .extend_route_matcher(accept_matcher) .extend_route_matcher(content_matcher) .to(|state| create_handler::(state)); #[cfg(feature = "cors")] self.0.cors(&self.1, Method::POST); } fn change_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(); self.0 .put(&self.1) .extend_route_matcher(accept_matcher) .extend_route_matcher(content_matcher) .to(|state| change_all_handler::(state)); #[cfg(feature = "cors")] self.0.cors(&self.1, Method::PUT); } fn change(&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(); let path = format!("{}/:id", self.1); self.0 .put(&path) .extend_route_matcher(accept_matcher) .extend_route_matcher(content_matcher) .with_path_extractor::>() .to(|state| change_handler::(state)); #[cfg(feature = "cors")] self.0.cors(&path, Method::PUT); } fn remove_all(&mut self) { let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); self.0 .delete(&self.1) .extend_route_matcher(matcher) .to(|state| remove_all_handler::(state)); #[cfg(feature = "cors")] self.0.cors(&self.1, Method::DELETE); } fn remove(&mut self) { let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); let path = format!("{}/:id", self.1); self.0 .delete(&path) .extend_route_matcher(matcher) .with_path_extractor::>() .to(|state| remove_handler::(state)); #[cfg(feature = "cors")] self.0.cors(&path, Method::POST); } } }; } implDrawResourceRoutes!(RouterBuilder); implDrawResourceRoutes!(ScopeBuilder);