1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-04-19 06:24:45 +00:00
deprecated-gotham-restful/src/routing.rs

254 lines
7.2 KiB
Rust
Raw Normal View History

2019-09-29 21:15:22 +02:00
#[cfg(feature = "openapi")]
use crate::openapi::{
2020-05-03 19:17:55 +02:00
builder::{OpenapiBuilder, OpenapiInfo},
router::OpenapiRouter
};
use crate::{
result::{ResourceError, ResourceResult},
2021-02-03 21:22:46 +00:00
Endpoint, FromBody, Resource, Response
};
2019-09-29 21:15:22 +02:00
#[cfg(feature = "cors")]
use gotham::router::route::matcher::AccessControlRequestMethodMatcher;
2019-09-26 17:24:40 +02:00
use gotham::{
handler::HandlerError,
2019-10-20 14:49:53 +00:00
helpers::http::response::{create_empty_response, create_response},
2021-02-03 21:22:46 +00:00
hyper::{body::to_bytes, header::CONTENT_TYPE, Body, HeaderMap, Method, StatusCode},
2019-09-26 17:24:40 +02:00
pipeline::chain::PipelineHandleChain,
2019-10-20 14:49:53 +00:00
router::{
builder::{DefineSingleRoute, DrawRoutes, RouterBuilder, ScopeBuilder},
2019-10-20 14:49:53 +00:00
non_match::RouteNonMatch,
route::matcher::{AcceptHeaderRouteMatcher, ContentTypeHeaderRouteMatcher, RouteMatcher}
2019-10-20 14:49:53 +00:00
},
2019-09-27 16:36:38 +02:00
state::{FromState, State}
2019-09-26 17:24:40 +02:00
};
2019-10-20 14:49:53 +00:00
use mime::{Mime, APPLICATION_JSON};
use std::panic::RefUnwindSafe;
2019-09-26 17:24:40 +02:00
2019-09-27 16:36:38 +02:00
/// Allow us to extract an id from a path.
#[derive(Debug, Deserialize, StateData, StaticResponseExtender)]
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
pub struct PathExtractor<ID: RefUnwindSafe + Send + 'static> {
pub id: ID
2019-09-27 16:36:38 +02:00
}
2019-09-29 21:15:22 +02:00
/// 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<D> {
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
2019-09-29 21:15:22 +02:00
where
F: FnOnce(OpenapiRouter<'_, D>);
2019-09-29 21:15:22 +02:00
}
2019-09-26 17:42:28 +02:00
/// This trait adds the `resource` method to gotham's routing. It allows you to register
2020-11-22 23:55:52 +01:00
/// any RESTful [Resource] with a path.
#[_private_openapi_trait(DrawResourcesWithSchema)]
pub trait DrawResources {
#[openapi_bound("R: crate::ResourceWithSchema")]
#[non_openapi_bound("R: crate::Resource")]
fn resource<R>(&mut self, path: &str);
2019-09-26 17:42:28 +02:00
}
/// This trait allows to draw routes within an resource. Use this only inside the
2020-11-22 23:55:52 +01:00
/// [Resource::setup] method.
#[_private_openapi_trait(DrawResourceRoutesWithSchema)]
pub trait DrawResourceRoutes {
#[openapi_bound("E: crate::EndpointWithSchema")]
#[non_openapi_bound("E: crate::Endpoint")]
fn endpoint<E: 'static>(&mut self);
2019-09-26 17:24:40 +02:00
}
fn response_from(res: Response, state: &State) -> gotham::hyper::Response<Body> {
2019-10-20 14:49:53 +00:00
let mut r = create_empty_response(state, res.status);
2021-01-14 18:37:51 +01:00
let headers = r.headers_mut();
if let Some(mime) = res.mime {
2021-01-14 18:37:51 +01:00
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);
2019-10-20 14:49:53 +00:00
}
2020-05-13 19:10:53 +02:00
let method = Method::borrow_from(state);
if method != Method::HEAD {
2019-10-20 14:49:53 +00:00
*r.body_mut() = res.body;
}
2020-05-13 19:10:53 +02:00
#[cfg(feature = "cors")]
crate::cors::handle_cors(state, &mut r);
2019-10-20 14:49:53 +00:00
r
}
async fn endpoint_handler<E: Endpoint>(state: &mut State) -> Result<gotham::hyper::Response<Body>, HandlerError>
where
E: Endpoint,
<E::Output as ResourceResult>::Err: Into<HandlerError>
{
trace!("entering endpoint_handler");
let placeholders = E::Placeholders::take_from(state);
let params = E::Params::take_from(state);
let body = match E::needs_body() {
true => {
let body = to_bytes(Body::take_from(state)).await?;
let content_type: Mime = match HeaderMap::borrow_from(state).get(CONTENT_TYPE) {
Some(content_type) => content_type.to_str().unwrap().parse().unwrap(),
None => {
debug!("Missing Content-Type: Returning 415 Response");
let res = create_empty_response(state, StatusCode::UNSUPPORTED_MEDIA_TYPE);
return Ok(res);
}
};
match E::Body::from_body(body, content_type) {
Ok(body) => Some(body),
Err(e) => {
debug!("Invalid Body: Returning 400 Response");
let error: ResourceError = e.into();
let json = serde_json::to_string(&error)?;
let res = create_response(state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json);
return Ok(res);
}
2020-04-14 21:17:12 +02:00
}
2019-09-26 17:24:40 +02:00
},
false => None
2019-09-27 16:36:38 +02:00
};
2019-09-27 17:43:01 +02:00
let out = E::handle(state, placeholders, params, body).await;
let res = out.into_response().await.map_err(Into::into)?;
debug!("Returning response {:?}", res);
Ok(response_from(res, state))
2019-09-29 19:19:38 +02:00
}
2019-10-20 14:49:53 +00:00
#[derive(Clone)]
struct MaybeMatchAcceptHeader {
matcher: Option<AcceptHeaderRouteMatcher>
2019-10-20 14:49:53 +00:00
}
impl RouteMatcher for MaybeMatchAcceptHeader {
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
2019-10-20 14:49:53 +00:00
match &self.matcher {
Some(matcher) => matcher.is_match(state),
None => Ok(())
}
}
}
impl MaybeMatchAcceptHeader {
fn new(types: Option<Vec<Mime>>) -> Self {
let types = match types {
Some(types) if types.is_empty() => None,
types => types
};
2019-10-20 14:49:53 +00:00
Self {
2020-05-20 09:33:12 +02:00
matcher: types.map(AcceptHeaderRouteMatcher::new)
2019-10-20 14:49:53 +00:00
}
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
Self::new(types)
}
}
2019-10-20 14:49:53 +00:00
#[derive(Clone)]
struct MaybeMatchContentTypeHeader {
matcher: Option<ContentTypeHeaderRouteMatcher>
2019-10-20 14:49:53 +00:00
}
impl RouteMatcher for MaybeMatchContentTypeHeader {
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
2019-10-20 14:49:53 +00:00
match &self.matcher {
Some(matcher) => matcher.is_match(state),
None => Ok(())
}
}
}
impl MaybeMatchContentTypeHeader {
fn new(types: Option<Vec<Mime>>) -> Self {
2019-10-20 14:49:53 +00:00
Self {
2020-05-20 09:33:12 +02:00
matcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type())
2019-10-20 14:49:53 +00:00
}
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
Self::new(types)
}
}
2019-09-26 17:24:40 +02:00
macro_rules! implDrawResourceRoutes {
($implType:ident) => {
2019-09-29 21:15:22 +02:00
#[cfg(feature = "openapi")]
impl<'a, C, P> WithOpenapi<Self> for $implType<'a, C, P>
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
2019-09-29 21:15:22 +02:00
{
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
2019-09-29 21:15:22 +02:00
where
F: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)
2019-09-29 21:15:22 +02:00
{
let router = OpenapiRouter {
router: self,
scope: None,
2020-05-03 19:17:55 +02:00
openapi_builder: &mut OpenapiBuilder::new(info)
};
block(router);
2019-09-29 21:15:22 +02:00
}
}
2019-09-26 17:42:28 +02:00
impl<'a, C, P> DrawResources for $implType<'a, C, P>
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
2019-09-26 17:42:28 +02:00
{
fn resource<R: Resource>(&mut self, path: &str) {
R::setup((self, path));
2019-09-26 17:42:28 +02:00
}
}
impl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str)
2019-09-26 17:24:40 +02:00
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
2019-09-26 17:24:40 +02:00
{
fn endpoint<E: Endpoint + 'static>(&mut self) {
let uri = format!("{}/{}", self.1, E::uri());
debug!("Registering endpoint for {}", uri);
self.0.associate(&uri, |assoc| {
assoc
.request(vec![E::http_method()])
.add_route_matcher(MaybeMatchAcceptHeader::new(E::Output::accepted_types()))
.with_path_extractor::<E::Placeholders>()
.with_query_string_extractor::<E::Params>()
.to_async_borrowing(endpoint_handler::<E>);
#[cfg(feature = "cors")]
if E::http_method() != Method::GET {
assoc
.options()
.add_route_matcher(AccessControlRequestMethodMatcher::new(E::http_method()))
.to(crate::cors::cors_preflight_handler);
}
});
2019-09-29 19:19:38 +02:00
}
2019-09-26 17:24:40 +02:00
}
};
2019-09-26 17:24:40 +02:00
}
implDrawResourceRoutes!(RouterBuilder);
implDrawResourceRoutes!(ScopeBuilder);