2019-09-29 21:15:22 +02:00
|
|
|
#[cfg(feature = "openapi")]
|
2020-05-01 14:48:11 +00:00
|
|
|
use crate::openapi::{
|
2020-05-03 19:17:55 +02:00
|
|
|
builder::{OpenapiBuilder, OpenapiInfo},
|
2020-05-01 14:48:11 +00:00
|
|
|
router::OpenapiRouter
|
|
|
|
};
|
2020-09-15 15:10:41 +02:00
|
|
|
use crate::{
|
|
|
|
result::{ResourceError, ResourceResult},
|
2021-02-03 21:22:46 +00:00
|
|
|
Endpoint, FromBody, Resource, Response
|
2020-09-15 15:10:41 +02:00
|
|
|
};
|
2019-09-29 21:15:22 +02:00
|
|
|
|
2021-02-03 22:58:08 +01:00
|
|
|
#[cfg(feature = "cors")]
|
|
|
|
use gotham::router::route::matcher::AccessControlRequestMethodMatcher;
|
2019-09-26 17:24:40 +02:00
|
|
|
use gotham::{
|
2021-01-18 00:05:30 +00:00
|
|
|
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::{
|
2021-01-18 00:05:30 +00:00
|
|
|
builder::{DefineSingleRoute, DrawRoutes, RouterBuilder, ScopeBuilder},
|
2019-10-20 14:49:53 +00:00
|
|
|
non_match::RouteNonMatch,
|
2021-02-03 22:58:08 +01:00
|
|
|
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};
|
2021-01-18 00:05:30 +00:00
|
|
|
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.
|
2021-01-18 00:05:30 +00:00
|
|
|
#[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")]
|
2020-09-15 15:10:41 +02:00
|
|
|
pub trait WithOpenapi<D> {
|
|
|
|
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
|
2019-09-29 21:15:22 +02:00
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
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.
|
2021-01-18 00:05:30 +00:00
|
|
|
#[_private_openapi_trait(DrawResourcesWithSchema)]
|
2020-09-15 15:10:41 +02:00
|
|
|
pub trait DrawResources {
|
2021-01-18 00:05:30 +00:00
|
|
|
#[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.
|
2021-01-18 00:05:30 +00:00
|
|
|
#[_private_openapi_trait(DrawResourceRoutesWithSchema)]
|
2020-09-15 15:10:41 +02:00
|
|
|
pub trait DrawResourceRoutes {
|
2021-01-18 00:05:30 +00:00
|
|
|
#[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
|
|
|
}
|
|
|
|
|
2020-09-15 15:10:41 +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();
|
2020-09-15 15:10:41 +02:00
|
|
|
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-09-15 15:10:41 +02:00
|
|
|
|
2020-05-13 19:10:53 +02:00
|
|
|
let method = Method::borrow_from(state);
|
2020-09-15 15:10:41 +02:00
|
|
|
if method != Method::HEAD {
|
2019-10-20 14:49:53 +00:00
|
|
|
*r.body_mut() = res.body;
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-05-13 19:10:53 +02:00
|
|
|
#[cfg(feature = "cors")]
|
|
|
|
crate::cors::handle_cors(state, &mut r);
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2019-10-20 14:49:53 +00:00
|
|
|
r
|
|
|
|
}
|
|
|
|
|
2021-02-03 22:58:08 +01:00
|
|
|
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>
|
|
|
|
{
|
2021-01-18 00:05:30 +00:00
|
|
|
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
|
|
|
},
|
2021-01-18 00:05:30 +00:00
|
|
|
false => None
|
2019-09-27 16:36:38 +02:00
|
|
|
};
|
2019-09-27 17:43:01 +02:00
|
|
|
|
2021-01-18 00:05:30 +00:00
|
|
|
let out = E::handle(state, placeholders, params, body).await;
|
2021-02-03 22:58:08 +01:00
|
|
|
let res = out.into_response().await.map_err(Into::into)?;
|
2021-01-18 00:05:30 +00:00
|
|
|
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)]
|
2020-09-15 15:10:41 +02:00
|
|
|
struct MaybeMatchAcceptHeader {
|
|
|
|
matcher: Option<AcceptHeaderRouteMatcher>
|
2019-10-20 14:49:53 +00:00
|
|
|
}
|
|
|
|
|
2020-09-15 15:10:41 +02: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(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-18 00:05:30 +00:00
|
|
|
impl MaybeMatchAcceptHeader {
|
|
|
|
fn new(types: Option<Vec<Mime>>) -> Self {
|
2020-04-22 11:46:15 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-18 00:05:30 +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)]
|
2020-09-15 15:10:41 +02:00
|
|
|
struct MaybeMatchContentTypeHeader {
|
|
|
|
matcher: Option<ContentTypeHeaderRouteMatcher>
|
2019-10-20 14:49:53 +00:00
|
|
|
}
|
|
|
|
|
2020-09-15 15:10:41 +02: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(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-18 00:05:30 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-18 00:05:30 +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
|
2020-09-15 15:10:41 +02:00
|
|
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
|
|
|
P: RefUnwindSafe + Send + Sync + 'static
|
2019-09-29 21:15:22 +02:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
|
2019-09-29 21:15:22 +02:00
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
F: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)
|
2019-09-29 21:15:22 +02:00
|
|
|
{
|
2020-04-29 19:22:32 +02:00
|
|
|
let router = OpenapiRouter {
|
|
|
|
router: self,
|
2020-05-05 00:34:19 +02:00
|
|
|
scope: None,
|
2020-05-03 19:17:55 +02:00
|
|
|
openapi_builder: &mut OpenapiBuilder::new(info)
|
2020-04-29 19:22:32 +02:00
|
|
|
};
|
|
|
|
block(router);
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2019-09-26 17:42:28 +02:00
|
|
|
impl<'a, C, P> DrawResources for $implType<'a, C, P>
|
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
|
|
|
P: RefUnwindSafe + Send + Sync + 'static
|
2019-09-26 17:42:28 +02:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
fn resource<R: Resource>(&mut self, path: &str) {
|
2020-03-30 22:42:22 +02:00
|
|
|
R::setup((self, path));
|
2019-09-26 17:42:28 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-03-30 22:42:22 +02:00
|
|
|
impl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str)
|
2019-09-26 17:24:40 +02:00
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
|
|
|
P: RefUnwindSafe + Send + Sync + 'static
|
2019-09-26 17:24:40 +02:00
|
|
|
{
|
2021-01-18 00:05:30 +00: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
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
};
|
2019-09-26 17:24:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
implDrawResourceRoutes!(RouterBuilder);
|
|
|
|
implDrawResourceRoutes!(ScopeBuilder);
|