1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-05-09 08:00:41 +00:00

Replace methods with more flexible endpoints

This commit is contained in:
msrd0 2021-01-18 00:05:30 +00:00
parent 0ac0f0f504
commit b807ae2796
87 changed files with 1497 additions and 1512 deletions

View file

@ -136,7 +136,7 @@ struct AuthData {
exp: u64
}
#[read_all(AuthResource)]
#[read_all]
fn read_all(auth : &AuthStatus<AuthData>) -> Success<String> {
format!("{:?}", auth).into()
}

View file

@ -246,7 +246,7 @@ where
fn cors(&mut self, path: &str, method: Method);
}
fn cors_preflight_handler(state: State) -> (State, Response<Body>) {
pub(crate) fn cors_preflight_handler(state: State) -> (State, Response<Body>) {
let config = CorsConfig::try_borrow_from(&state);
// prepare the response

105
src/endpoint.rs Normal file
View file

@ -0,0 +1,105 @@
use crate::{RequestBody, ResourceResult};
use futures_util::future::BoxFuture;
use gotham::{
extractor::{PathExtractor, QueryStringExtractor},
hyper::{Body, Method},
state::State
};
use std::borrow::Cow;
// TODO: Specify default types once https://github.com/rust-lang/rust/issues/29661 lands.
#[_private_openapi_trait(EndpointWithSchema)]
pub trait Endpoint {
/// The HTTP Verb of this endpoint.
fn http_method() -> Method;
/// The URI that this endpoint listens on in gotham's format.
fn uri() -> Cow<'static, str>;
/// The output type that provides the response.
type Output: ResourceResult + Send;
/// Returns `true` _iff_ the URI contains placeholders. `false` by default.
fn has_placeholders() -> bool {
false
}
/// The type that parses the URI placeholders. Use [gotham::extractor::NoopPathExtractor]
/// if `has_placeholders()` returns `false`.
#[openapi_bound("Placeholders: crate::OpenapiType")]
type Placeholders: PathExtractor<Body> + Sync;
/// Returns `true` _iff_ the request parameters should be parsed. `false` by default.
fn needs_params() -> bool {
false
}
/// The type that parses the request parameters. Use [gotham::extractor::NoopQueryStringExtractor]
/// if `needs_params()` returns `false`.
#[openapi_bound("Params: crate::OpenapiType")]
type Params: QueryStringExtractor<Body> + Sync;
/// Returns `true` _iff_ the request body should be parsed. `false` by default.
fn needs_body() -> bool {
false
}
/// The type to parse the body into. Use `()` if `needs_body()` returns `false`.
type Body: RequestBody + Send;
/// Returns `true` if the request wants to know the auth status of the client. `false` by default.
fn wants_auth() -> bool {
false
}
/// Replace the automatically generated operation id with a custom one. Only relevant for the
/// OpenAPI Specification.
#[openapi_only]
fn operation_id() -> Option<String> {
None
}
/// The handler for this endpoint.
fn handle(
state: &mut State,
placeholders: Self::Placeholders,
params: Self::Params,
body: Option<Self::Body>
) -> BoxFuture<'static, Self::Output>;
}
#[cfg(feature = "openapi")]
impl<E: EndpointWithSchema> Endpoint for E {
fn http_method() -> Method {
E::http_method()
}
fn uri() -> Cow<'static, str> {
E::uri()
}
type Output = E::Output;
fn has_placeholders() -> bool {
E::has_placeholders()
}
type Placeholders = E::Placeholders;
fn needs_params() -> bool {
E::needs_params()
}
type Params = E::Params;
fn needs_body() -> bool {
E::needs_body()
}
type Body = E::Body;
fn wants_auth() -> bool {
E::wants_auth()
}
fn handle(
state: &mut State,
placeholders: Self::Placeholders,
params: Self::Params,
body: Option<Self::Body>
) -> BoxFuture<'static, Self::Output> {
E::handle(state, placeholders, params, body)
}
}

View file

@ -9,7 +9,7 @@
#![forbid(unsafe_code)]
/*!
This crate is an extension to the popular [gotham web framework][gotham] for Rust. It allows you to
create resources with assigned methods that aim to be a more convenient way of creating handlers
create resources with assigned endpoints that aim to be a more convenient way of creating handlers
for requests.
# Features
@ -27,23 +27,23 @@ for requests.
This crate is just as safe as you'd expect from anything written in safe Rust - and
`#![forbid(unsafe_code)]` ensures that no unsafe was used.
# Methods
# Endpoints
Assuming you assign `/foobar` to your resource, you can implement the following methods:
Assuming you assign `/foobar` to your resource, the following pre-defined endpoints exist:
| Method Name | Required Arguments | HTTP Verb | HTTP Path |
| ----------- | ------------------ | --------- | ----------- |
| read_all | | GET | /foobar |
| read | id | GET | /foobar/:id |
| search | query | GET | /foobar/search |
| create | body | POST | /foobar |
| change_all | body | PUT | /foobar |
| change | id, body | PUT | /foobar/:id |
| remove_all | | DELETE | /foobar |
| remove | id | DELETE | /foobar/:id |
| Endpoint Name | Required Arguments | HTTP Verb | HTTP Path |
| ------------- | ------------------ | --------- | -------------- |
| read_all | | GET | /foobar |
| read | id | GET | /foobar/:id |
| search | query | GET | /foobar/search |
| create | body | POST | /foobar |
| change_all | body | PUT | /foobar |
| change | id, body | PUT | /foobar/:id |
| remove_all | | DELETE | /foobar |
| remove | id | DELETE | /foobar/:id |
Each of those methods has a macro that creates the neccessary boilerplate for the Resource. A
simple example could look like this:
Each of those endpoints has a macro that creates the neccessary boilerplate for the Resource. A
simple example looks like this:
```rust,no_run
# #[macro_use] extern crate gotham_restful_derive;
@ -55,15 +55,15 @@ simple example could look like this:
#[resource(read)]
struct FooResource;
/// The return type of the foo read method.
/// The return type of the foo read endpoint.
#[derive(Serialize)]
# #[cfg_attr(feature = "openapi", derive(OpenapiType))]
struct Foo {
id: u64
}
/// The foo read method handler.
#[read(FooResource)]
/// The foo read endpoint.
#[read]
fn read(id: u64) -> Success<Foo> {
Foo { id }.into()
}
@ -76,17 +76,16 @@ fn read(id: u64) -> Success<Foo> {
# Arguments
Some methods require arguments. Those should be
* **id** Should be a deserializable json-primitive like `i64` or `String`.
Some endpoints require arguments. Those should be
* **id** Should be a deserializable json-primitive like [`i64`] or [`String`].
* **body** Should be any deserializable object, or any type implementing [`RequestBody`].
* **query** Should be any deserializable object whose variables are json-primitives. It will
however not be parsed from json, but from HTTP GET parameters like in `search?id=1`. The
type needs to implement [`QueryStringExtractor`].
type needs to implement [`QueryStringExtractor`](gotham::extractor::QueryStringExtractor).
Additionally, non-async handlers may take a reference to gotham's [`State`]. If you need to
have an async handler (that is, the function that the method macro is invoked on is declared
as `async fn`), consider returning the boxed future instead. Since [`State`] does not implement
`Sync` there is unfortunately no more convenient way.
Additionally, all handlers may take a reference to gotham's [`State`]. Please note that for async
handlers, it needs to be a mutable reference until rustc's lifetime checks across await bounds
improve.
# Uploads and Downloads
@ -110,7 +109,7 @@ struct RawImage {
content_type: Mime
}
#[create(ImageResource)]
#[create]
fn create(body : RawImage) -> Raw<Vec<u8>> {
Raw::new(body.content, body.content_type)
}
@ -127,21 +126,23 @@ To make life easier for common use-cases, this create offers a few features that
when you implement your web server. The complete feature list is
- [`auth`](#authentication-feature) Advanced JWT middleware
- `chrono` openapi support for chrono types
- [`cors`](#cors-feature) CORS handling for all method handlers
- `full` enables all features except `without-openapi`
- [`cors`](#cors-feature) CORS handling for all endpoint handlers
- [`database`](#database-feature) diesel middleware support
- `errorlog` log errors returned from method handlers
- `errorlog` log errors returned from endpoint handlers
- [`openapi`](#openapi-feature) router additions to generate an openapi spec
- `uuid` openapi support for uuid
- `without-openapi` (**default**) disables `openapi` support.
## Authentication Feature
In order to enable authentication support, enable the `auth` feature gate. This allows you to
register a middleware that can automatically check for the existence of an JWT authentication
token. Besides being supported by the method macros, it supports to lookup the required JWT secret
token. Besides being supported by the endpoint macros, it supports to lookup the required JWT secret
with the JWT data, hence you can use several JWT secrets and decide on the fly which secret to use.
None of this is currently supported by gotham's own JWT middleware.
A simple example that uses only a single secret could look like this:
A simple example that uses only a single secret looks like this:
```rust,no_run
# #[macro_use] extern crate gotham_restful_derive;
@ -167,7 +168,7 @@ struct AuthData {
exp: u64
}
#[read(SecretResource)]
#[read]
fn read(auth: AuthStatus<AuthData>, id: u64) -> AuthSuccess<Secret> {
let intended_for = auth.ok()?.sub;
Ok(Secret { id, intended_for })
@ -194,7 +195,7 @@ the `Access-Control-Allow-Methods` header is touched. To change the behaviour, a
configuration as a middleware.
A simple example that allows authentication from every origin (note that `*` always disallows
authentication), and every content type, could look like this:
authentication), and every content type, looks like this:
```rust,no_run
# #[macro_use] extern crate gotham_restful_derive;
@ -207,7 +208,7 @@ authentication), and every content type, could look like this:
#[resource(read_all)]
struct FooResource;
#[read_all(FooResource)]
#[read_all]
fn read_all() {
// your handler
}
@ -237,7 +238,7 @@ note however that due to the way gotham's diesel middleware implementation, it i
to run async code while holding a database connection. If you need to combine async and database,
you'll need to borrow the connection from the [`State`] yourself and return a boxed future.
A simple non-async example could look like this:
A simple non-async example looks like this:
```rust,no_run
# #[macro_use] extern crate diesel;
@ -267,7 +268,7 @@ struct Foo {
value: String
}
#[read_all(FooResource)]
#[read_all]
fn read_all(conn: &PgConnection) -> QueryResult<Vec<Foo>> {
foo::table.load(conn)
}
@ -295,7 +296,7 @@ In order to automatically create an openapi specification, gotham-restful needs
all routes and the types returned. `serde` does a great job at serialization but doesn't give
enough type information, so all types used in the router need to implement `OpenapiType`. This
can be derived for almoust any type and there should be no need to implement it manually. A simple
example could look like this:
example looks like this:
```rust,no_run
# #[macro_use] extern crate gotham_restful_derive;
@ -313,7 +314,7 @@ struct Foo {
bar: String
}
#[read_all(FooResource)]
#[read_all]
fn read_all() -> Success<Foo> {
Foo { bar: "Hello World".to_owned() }.into()
}
@ -334,48 +335,37 @@ fn main() {
# }
```
Above example adds the resource as before, but adds another endpoint that we specified as `/openapi`
that will return the generated openapi specification. This allows you to easily write clients
in different languages without worying to exactly replicate your api in each of those languages.
Above example adds the resource as before, but adds another endpoint that we specified as `/openapi`.
It will return the generated openapi specification in JSON format. This allows you to easily write
clients in different languages without worying to exactly replicate your api in each of those
languages.
However, as of right now there is one caveat. If you wrote code before enabling the openapi feature,
it is likely to break. This is because of the new requirement of `OpenapiType` for all types used
with resources, even outside of the `with_openapi` scope. This issue will eventually be resolved.
If you are writing a library that uses gotham-restful, make sure that you expose an openapi feature.
In other words, put
However, please note that by default, the `without-openapi` feature of this crate is enabled.
Disabling it in favour of the `openapi` feature will add an additional type bound, [`OpenapiType`],
on some of the types in [`Endpoint`] and related traits. This means that some code might only
compile on either feature, but not on both. If you are writing a library that uses gotham-restful,
it is strongly recommended to pass both features through and conditionally enable the openapi
code, like this:
```toml
[features]
openapi = ["gotham-restful/openapi"]
```
into your libraries `Cargo.toml` and use the following for all types used with handlers:
```
# #[cfg(feature = "openapi")]
# mod openapi_feature_enabled {
# use gotham_restful::OpenapiType;
```rust
# #[macro_use] extern crate gotham_restful;
# use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
struct Foo;
# }
```
# Examples
There is a lack of good examples, but there is currently a collection of code in the [example]
directory, that might help you. Any help writing more examples is highly appreciated.
This readme and the crate documentation contain some of example. In addition to that, there is
a collection of code in the [example] directory that might help you. Any help writing more
examples is highly appreciated.
[diesel]: https://diesel.rs/
[example]: https://gitlab.com/msrd0/gotham-restful/tree/master/example
[gotham]: https://gotham.rs/
[serde_json]: https://github.com/serde-rs/json#serde-json----
[`CorsRoute`]: trait.CorsRoute.html
[`QueryStringExtractor`]: ../gotham/extractor/trait.QueryStringExtractor.html
[`RequestBody`]: trait.RequestBody.html
[`State`]: ../gotham/state/struct.State.html
*/
#[cfg(all(feature = "openapi", feature = "without-openapi"))]
@ -390,6 +380,8 @@ extern crate self as gotham_restful;
#[macro_use]
extern crate gotham_derive;
#[macro_use]
extern crate gotham_restful_derive;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde;
@ -409,7 +401,9 @@ pub use gotham_restful_derive::*;
/// Not public API
#[doc(hidden)]
pub mod export {
pub use futures_util::future::FutureExt;
pub use crate::routing::PathExtractor as IdPlaceholder;
pub use futures_util::future::{BoxFuture, FutureExt};
pub use serde_json;
@ -441,11 +435,10 @@ pub use openapi::{
types::{OpenapiSchema, OpenapiType}
};
mod resource;
pub use resource::{
Resource, ResourceChange, ResourceChangeAll, ResourceCreate, ResourceMethod, ResourceRead, ResourceReadAll,
ResourceRemove, ResourceRemoveAll, ResourceSearch
};
mod endpoint;
pub use endpoint::Endpoint;
#[cfg(feature = "openapi")]
pub use endpoint::EndpointWithSchema;
mod response;
pub use response::Response;
@ -457,9 +450,21 @@ pub use result::{
};
mod routing;
#[cfg(feature = "openapi")]
pub use routing::WithOpenapi;
pub use routing::{DrawResourceRoutes, DrawResources};
#[cfg(feature = "openapi")]
pub use routing::{DrawResourceRoutesWithSchema, DrawResourcesWithSchema, WithOpenapi};
mod types;
pub use types::*;
/// This trait must be implemented for every resource. It allows you to register the different
/// endpoints that can be handled by this resource to be registered with the underlying router.
///
/// It is not recommended to implement this yourself, rather just use `#[derive(Resource)]`.
#[_private_openapi_trait(ResourceWithSchema)]
pub trait Resource {
/// Register all methods handled by this resource with the underlying router.
#[openapi_bound("D: crate::DrawResourceRoutesWithSchema")]
#[non_openapi_bound("D: crate::DrawResourceRoutes")]
fn setup<D>(route: D);
}

View file

@ -1,5 +1,5 @@
use super::SECURITY_NAME;
use crate::{resource::*, result::*, OpenapiSchema, RequestBody};
use crate::{result::*, EndpointWithSchema, OpenapiSchema, RequestBody};
use indexmap::IndexMap;
use mime::Mime;
use openapiv3::{
@ -8,31 +8,40 @@ use openapiv3::{
};
#[derive(Default)]
struct OperationParams<'a> {
path_params: Vec<(&'a str, ReferenceOr<Schema>)>,
struct OperationParams {
path_params: Option<OpenapiSchema>,
query_params: Option<OpenapiSchema>
}
impl<'a> OperationParams<'a> {
fn add_path_params(&self, params: &mut Vec<ReferenceOr<Parameter>>) {
for param in &self.path_params {
impl OperationParams {
fn add_path_params(path_params: Option<OpenapiSchema>, params: &mut Vec<ReferenceOr<Parameter>>) {
let path_params = match path_params {
Some(pp) => pp.schema,
None => return
};
let path_params = match path_params {
SchemaKind::Type(Type::Object(ty)) => ty,
_ => panic!("Path Parameters needs to be a plain struct")
};
for (name, schema) in path_params.properties {
let required = path_params.required.contains(&name);
params.push(Item(Parameter::Path {
parameter_data: ParameterData {
name: (*param).0.to_string(),
name,
description: None,
required: true,
required,
deprecated: None,
format: ParameterSchemaOrContent::Schema((*param).1.clone()),
format: ParameterSchemaOrContent::Schema(schema.unbox()),
example: None,
examples: IndexMap::new()
},
style: Default::default()
}));
}))
}
}
fn add_query_params(self, params: &mut Vec<ReferenceOr<Parameter>>) {
let query_params = match self.query_params {
fn add_query_params(query_params: Option<OpenapiSchema>, params: &mut Vec<ReferenceOr<Parameter>>) {
let query_params = match query_params {
Some(qp) => qp.schema,
None => return
};
@ -61,51 +70,48 @@ impl<'a> OperationParams<'a> {
fn into_params(self) -> Vec<ReferenceOr<Parameter>> {
let mut params: Vec<ReferenceOr<Parameter>> = Vec::new();
self.add_path_params(&mut params);
self.add_query_params(&mut params);
Self::add_path_params(self.path_params, &mut params);
Self::add_query_params(self.query_params, &mut params);
params
}
}
pub struct OperationDescription<'a> {
pub struct OperationDescription {
operation_id: Option<String>,
default_status: crate::StatusCode,
accepted_types: Option<Vec<Mime>>,
schema: ReferenceOr<Schema>,
params: OperationParams<'a>,
params: OperationParams,
body_schema: Option<ReferenceOr<Schema>>,
supported_types: Option<Vec<Mime>>,
requires_auth: bool
}
impl<'a> OperationDescription<'a> {
pub fn new<Handler: ResourceMethod>(schema: ReferenceOr<Schema>) -> Self {
impl OperationDescription {
pub fn new<E: EndpointWithSchema>(schema: ReferenceOr<Schema>) -> Self {
Self {
operation_id: Handler::operation_id(),
default_status: Handler::Res::default_status(),
accepted_types: Handler::Res::accepted_types(),
operation_id: E::operation_id(),
default_status: E::Output::default_status(),
accepted_types: E::Output::accepted_types(),
schema,
params: Default::default(),
body_schema: None,
supported_types: None,
requires_auth: Handler::wants_auth()
requires_auth: E::wants_auth()
}
}
pub fn add_path_param(mut self, name: &'a str, schema: ReferenceOr<Schema>) -> Self {
self.params.path_params.push((name, schema));
self
pub fn set_path_params(&mut self, params: OpenapiSchema) {
self.params.path_params = Some(params);
}
pub fn with_query_params(mut self, params: OpenapiSchema) -> Self {
pub fn set_query_params(&mut self, params: OpenapiSchema) {
self.params.query_params = Some(params);
self
}
pub fn with_body<Body: RequestBody>(mut self, schema: ReferenceOr<Schema>) -> Self {
pub fn set_body<Body: RequestBody>(&mut self, schema: ReferenceOr<Schema>) {
self.body_schema = Some(schema);
self.supported_types = Body::supported_types();
self
}
fn schema_to_content(types: Vec<Mime>, schema: ReferenceOr<Schema>) -> IndexMap<String, MediaType> {

View file

@ -1,6 +1,8 @@
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription};
use crate::{resource::*, routing::*, OpenapiType};
use gotham::{pipeline::chain::PipelineHandleChain, router::builder::*};
use crate::{routing::*, EndpointWithSchema, OpenapiType, ResourceWithSchema};
use gotham::{hyper::Method, pipeline::chain::PipelineHandleChain, router::builder::*};
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use std::panic::RefUnwindSafe;
/// This trait adds the `get_openapi` method to an OpenAPI-aware router.
@ -51,150 +53,62 @@ macro_rules! implOpenapiRouter {
}
}
impl<'a, 'b, C, P> DrawResources for OpenapiRouter<'a, $implType<'b, C, P>>
impl<'a, 'b, C, P> DrawResourcesWithSchema for OpenapiRouter<'a, $implType<'b, C, P>>
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn resource<R: Resource>(&mut self, path: &str) {
fn resource<R: ResourceWithSchema>(&mut self, path: &str) {
R::setup((self, path));
}
}
impl<'a, 'b, C, P> DrawResourceRoutes for (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)
impl<'a, 'b, C, P> DrawResourceRoutesWithSchema for (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn read_all<Handler: ResourceReadAll>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
fn endpoint<E: EndpointWithSchema + 'static>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<E::Output>();
let mut descr = OperationDescription::new::<E>(schema);
if E::has_placeholders() {
descr.set_path_params(E::Placeholders::schema());
}
if E::needs_params() {
descr.set_query_params(E::Params::schema());
}
if E::needs_body() {
let body_schema = (self.0).openapi_builder.add_schema::<E::Body>();
descr.set_body::<E::Body>(body_schema);
}
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
static URI_PLACEHOLDER_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(^|/):(?P<name>[^/]+)(/|$)"#).unwrap());
let uri: &str = &E::uri();
let uri =
URI_PLACEHOLDER_REGEX.replace_all(uri, |captures: &Captures<'_>| format!("{{{}}}", &captures["name"]));
let path = if uri.is_empty() {
format!("{}/{}", self.0.scope.unwrap_or_default(), self.1)
} else {
format!("{}/{}/{}", self.0.scope.unwrap_or_default(), self.1, uri)
};
let op = descr.into_operation();
let mut item = (self.0).openapi_builder.remove_path(&path);
item.get = Some(OperationDescription::new::<Handler>(schema).into_operation());
match E::http_method() {
Method::GET => item.get = Some(op),
Method::PUT => item.put = Some(op),
Method::POST => item.post = Some(op),
Method::DELETE => item.delete = Some(op),
Method::OPTIONS => item.options = Some(op),
Method::HEAD => item.head = Some(op),
Method::PATCH => item.patch = Some(op),
Method::TRACE => item.trace = Some(op),
method => warn!("Ignoring unsupported method '{}' in OpenAPI Specification", method)
};
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).read_all::<Handler>()
}
fn read<Handler: ResourceRead>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.get = Some(
OperationDescription::new::<Handler>(schema)
.add_path_param("id", id_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).read::<Handler>()
}
fn search<Handler: ResourceSearch>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let path = format!("{}/{}/search", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.get = Some(
OperationDescription::new::<Handler>(schema)
.with_query_params(Handler::Query::schema())
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).search::<Handler>()
}
fn create<Handler: ResourceCreate>(&mut self)
where
Handler::Res: 'static,
Handler::Body: 'static
{
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.post = Some(
OperationDescription::new::<Handler>(schema)
.with_body::<Handler::Body>(body_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).create::<Handler>()
}
fn change_all<Handler: ResourceChangeAll>(&mut self)
where
Handler::Res: 'static,
Handler::Body: 'static
{
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.put = Some(
OperationDescription::new::<Handler>(schema)
.with_body::<Handler::Body>(body_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).change_all::<Handler>()
}
fn change<Handler: ResourceChange>(&mut self)
where
Handler::Res: 'static,
Handler::Body: 'static
{
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.put = Some(
OperationDescription::new::<Handler>(schema)
.add_path_param("id", id_schema)
.with_body::<Handler::Body>(body_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).change::<Handler>()
}
fn remove_all<Handler: ResourceRemoveAll>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.delete = Some(OperationDescription::new::<Handler>(schema).into_operation());
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).remove_all::<Handler>()
}
fn remove<Handler: ResourceRemove>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.delete = Some(
OperationDescription::new::<Handler>(schema)
.add_path_param("id", id_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).remove::<Handler>()
(&mut *(self.0).router, self.1).endpoint::<E>()
}
}
};

View file

@ -1,5 +1,6 @@
#[cfg(feature = "chrono")]
use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc};
use gotham::extractor::{NoopPathExtractor, NoopQueryStringExtractor};
use indexmap::IndexMap;
use openapiv3::{
AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType,
@ -86,6 +87,20 @@ impl OpenapiType for () {
}
}
impl OpenapiType for NoopPathExtractor {
fn schema() -> OpenapiSchema {
warn!("You're asking for the OpenAPI Schema for gotham::extractor::NoopPathExtractor. This is probably not what you want.");
<()>::schema()
}
}
impl OpenapiType for NoopQueryStringExtractor {
fn schema() -> OpenapiSchema {
warn!("You're asking for the OpenAPI Schema for gotham::extractor::NoopQueryStringExtractor. This is probably not what you want.");
<()>::schema()
}
}
impl OpenapiType for bool {
fn schema() -> OpenapiSchema {
OpenapiSchema::new(SchemaKind::Type(Type::Boolean {}))

View file

@ -1,99 +0,0 @@
use crate::{DrawResourceRoutes, RequestBody, ResourceID, ResourceResult, ResourceType};
use gotham::{extractor::QueryStringExtractor, hyper::Body, state::State};
use std::{future::Future, pin::Pin};
/// This trait must be implemented for every resource. It allows you to register the different
/// methods that can be handled by this resource to be registered with the underlying router.
///
/// It is not recommended to implement this yourself, rather just use `#[derive(Resource)]`.
pub trait Resource {
/// Register all methods handled by this resource with the underlying router.
fn setup<D: DrawResourceRoutes>(route: D);
}
/// A common trait for every resource method. It defines the return type as well as some general
/// information about a resource method.
///
/// It is not recommended to implement this yourself. Rather, just write your handler method and
/// annotate it with `#[<method>(YourResource)]`, where `<method>` is one of the supported
/// resource methods.
pub trait ResourceMethod {
type Res: ResourceResult + Send + 'static;
#[cfg(feature = "openapi")]
fn operation_id() -> Option<String> {
None
}
fn wants_auth() -> bool {
false
}
}
/// The read_all [ResourceMethod].
pub trait ResourceReadAll: ResourceMethod {
/// Handle a GET request on the Resource root.
fn read_all(state: State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}
/// The read [ResourceMethod].
pub trait ResourceRead: ResourceMethod {
/// The ID type to be parsed from the request path.
type ID: ResourceID + 'static;
/// Handle a GET request on the Resource with an id.
fn read(state: State, id: Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}
/// The search [ResourceMethod].
pub trait ResourceSearch: ResourceMethod {
/// The Query type to be parsed from the request parameters.
type Query: ResourceType + QueryStringExtractor<Body> + Sync;
/// Handle a GET request on the Resource with additional search parameters.
fn search(state: State, query: Self::Query) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}
/// The create [ResourceMethod].
pub trait ResourceCreate: ResourceMethod {
/// The Body type to be parsed from the request body.
type Body: RequestBody;
/// Handle a POST request on the Resource root.
fn create(state: State, body: Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}
/// The change_all [ResourceMethod].
pub trait ResourceChangeAll: ResourceMethod {
/// The Body type to be parsed from the request body.
type Body: RequestBody;
/// Handle a PUT request on the Resource root.
fn change_all(state: State, body: Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}
/// The change [ResourceMethod].
pub trait ResourceChange: ResourceMethod {
/// The Body type to be parsed from the request body.
type Body: RequestBody;
/// The ID type to be parsed from the request path.
type ID: ResourceID + 'static;
/// Handle a PUT request on the Resource with an id.
fn change(state: State, id: Self::ID, body: Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}
/// The remove_all [ResourceMethod].
pub trait ResourceRemoveAll: ResourceMethod {
/// Handle a DELETE request on the Resource root.
fn remove_all(state: State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}
/// The remove [ResourceMethod].
pub trait ResourceRemove: ResourceMethod {
/// The ID type to be parsed from the request path.
type ID: ResourceID + 'static;
/// Handle a DELETE request on the Resource with an id.
fn remove(state: State, id: Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
}

View file

@ -33,7 +33,7 @@ Use can look something like this (assuming the `auth` feature is enabled):
# #[derive(Clone, Deserialize)]
# struct MyAuthData { exp : u64 }
#
#[read_all(MyResource)]
#[read_all]
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthSuccess<NoContent> {
let auth_data = match auth {
AuthStatus::Authenticated(data) => data,
@ -102,7 +102,7 @@ Use can look something like this (assuming the `auth` feature is enabled):
# #[derive(Clone, Deserialize)]
# struct MyAuthData { exp : u64 }
#
#[read_all(MyResource)]
#[read_all]
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthResult<NoContent, io::Error> {
let auth_data = match auth {
AuthStatus::Authenticated(data) => data,

View file

@ -21,8 +21,8 @@ the function attributes:
# #[resource(read_all)]
# struct MyResource;
#
#[read_all(MyResource)]
fn read_all(_state: &mut State) {
#[read_all]
fn read_all() {
// do something
}
# }

View file

@ -25,7 +25,7 @@ example that simply returns its body:
#[resource(create)]
struct ImageResource;
#[create(ImageResource)]
#[create]
fn create(body : Raw<Vec<u8>>) -> Raw<Vec<u8>> {
body
}

View file

@ -34,8 +34,8 @@ struct MyResponse {
message: &'static str
}
#[read_all(MyResource)]
fn read_all(_state: &mut State) -> Success<MyResponse> {
#[read_all]
fn read_all() -> Success<MyResponse> {
let res = MyResponse { message: "I'm always happy" };
res.into()
}

View file

@ -3,37 +3,33 @@ 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
Endpoint, FromBody, Resource, Response, StatusCode
};
use futures_util::{future, future::FutureExt};
use gotham::{
handler::{HandlerError, HandlerFuture},
handler::HandlerError,
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},
builder::{DefineSingleRoute, DrawRoutes, RouterBuilder, ScopeBuilder},
non_match::RouteNonMatch,
route::matcher::{AcceptHeaderRouteMatcher, ContentTypeHeaderRouteMatcher, RouteMatcher}
route::matcher::{
AcceptHeaderRouteMatcher, AccessControlRequestMethodMatcher, ContentTypeHeaderRouteMatcher, RouteMatcher
}
},
state::{FromState, State}
};
use mime::{Mime, APPLICATION_JSON};
use std::{future::Future, panic::RefUnwindSafe, pin::Pin};
use std::panic::RefUnwindSafe;
/// Allow us to extract an id from a path.
#[derive(Deserialize, StateData, StaticResponseExtender)]
struct PathExtractor<ID: RefUnwindSafe + Send + 'static> {
id: ID
#[derive(Debug, Deserialize, StateData, StaticResponseExtender)]
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
pub struct PathExtractor<ID: RefUnwindSafe + Send + 'static> {
pub id: ID
}
/// This trait adds the `with_openapi` method to gotham's routing. It turns the default
@ -48,37 +44,20 @@ pub trait WithOpenapi<D> {
/// This trait adds the `resource` method to gotham's routing. It allows you to register
/// any RESTful [Resource] with a path.
#[_private_openapi_trait(DrawResourcesWithSchema)]
pub trait DrawResources {
fn resource<R: Resource>(&mut self, path: &str);
#[openapi_bound("R: crate::ResourceWithSchema")]
#[non_openapi_bound("R: crate::Resource")]
fn resource<R>(&mut self, path: &str);
}
/// This trait allows to draw routes within an resource. Use this only inside the
/// [Resource::setup] method.
#[_private_openapi_trait(DrawResourceRoutesWithSchema)]
pub trait DrawResourceRoutes {
fn read_all<Handler: ResourceReadAll>(&mut self);
fn read<Handler: ResourceRead>(&mut self);
fn search<Handler: ResourceSearch>(&mut self);
fn create<Handler: ResourceCreate>(&mut self)
where
Handler::Res: 'static,
Handler::Body: 'static;
fn change_all<Handler: ResourceChangeAll>(&mut self)
where
Handler::Res: 'static,
Handler::Body: 'static;
fn change<Handler: ResourceChange>(&mut self)
where
Handler::Res: 'static,
Handler::Body: 'static;
fn remove_all<Handler: ResourceRemoveAll>(&mut self);
fn remove<Handler: ResourceRemove>(&mut self);
#[openapi_bound("E: crate::EndpointWithSchema")]
#[non_openapi_bound("E: crate::Endpoint")]
fn endpoint<E: 'static>(&mut self);
}
fn response_from(res: Response, state: &State) -> gotham::hyper::Response<Body> {
@ -108,149 +87,42 @@ fn response_from(res: Response, state: &State) -> gotham::hyper::Response<Body>
r
}
async fn to_handler_future<F, R>(
state: State,
get_result: F
) -> Result<(State, gotham::hyper::Response<Body>), (State, HandlerError)>
where
F: FnOnce(State) -> Pin<Box<dyn Future<Output = (State, R)> + 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 endpoint_handler<E: Endpoint>(state: &mut State) -> Result<gotham::hyper::Response<Body>, HandlerError> {
trace!("entering endpoint_handler");
let placeholders = E::Placeholders::take_from(state);
let params = E::Params::take_from(state);
async fn body_to_res<B, F, R>(
mut state: State,
get_result: F
) -> (State, Result<gotham::hyper::Response<Body>, HandlerError>)
where
B: RequestBody,
F: FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
R: ResourceResult
{
let body = to_bytes(Body::take_from(&mut state)).await;
let body = match E::needs_body() {
true => {
let body = to_bytes(Body::take_from(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 => {
debug!("Missing Content-Type: Returning 415 Response");
let res = create_empty_response(state, StatusCode::UNSUPPORTED_MEDIA_TYPE);
return Ok(res);
}
};
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);
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);
}
}
};
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())
false => None
};
(state, res)
}
fn handle_with_body<B, F, R>(state: State, get_result: F) -> Pin<Box<HandlerFuture>>
where
B: RequestBody + 'static,
F: FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + 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<Handler: ResourceReadAll>(state: State) -> Pin<Box<HandlerFuture>> {
to_handler_future(state, |state| Handler::read_all(state)).boxed()
}
fn read_handler<Handler: ResourceRead>(state: State) -> Pin<Box<HandlerFuture>> {
let id = {
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
path.id.clone()
};
to_handler_future(state, |state| Handler::read(state, id)).boxed()
}
fn search_handler<Handler: ResourceSearch>(mut state: State) -> Pin<Box<HandlerFuture>> {
let query = Handler::Query::take_from(&mut state);
to_handler_future(state, |state| Handler::search(state, query)).boxed()
}
fn create_handler<Handler: ResourceCreate>(state: State) -> Pin<Box<HandlerFuture>>
where
Handler::Res: 'static,
Handler::Body: 'static
{
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::create(state, body))
}
fn change_all_handler<Handler: ResourceChangeAll>(state: State) -> Pin<Box<HandlerFuture>>
where
Handler::Res: 'static,
Handler::Body: 'static
{
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change_all(state, body))
}
fn change_handler<Handler: ResourceChange>(state: State) -> Pin<Box<HandlerFuture>>
where
Handler::Res: 'static,
Handler::Body: 'static
{
let id = {
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
path.id.clone()
};
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change(state, id, body))
}
fn remove_all_handler<Handler: ResourceRemoveAll>(state: State) -> Pin<Box<HandlerFuture>> {
to_handler_future(state, |state| Handler::remove_all(state)).boxed()
}
fn remove_handler<Handler: ResourceRemove>(state: State) -> Pin<Box<HandlerFuture>> {
let id = {
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
path.id.clone()
};
to_handler_future(state, |state| Handler::remove(state, id)).boxed()
let out = E::handle(state, placeholders, params, body).await;
let res = out.into_response().await?;
debug!("Returning response {:?}", res);
Ok(response_from(res, state))
}
#[derive(Clone)]
@ -267,8 +139,8 @@ impl RouteMatcher for MaybeMatchAcceptHeader {
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
impl MaybeMatchAcceptHeader {
fn new(types: Option<Vec<Mime>>) -> Self {
let types = match types {
Some(types) if types.is_empty() => None,
types => types
@ -279,6 +151,12 @@ impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader {
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
Self::new(types)
}
}
#[derive(Clone)]
struct MaybeMatchContentTypeHeader {
matcher: Option<ContentTypeHeaderRouteMatcher>
@ -293,14 +171,20 @@ impl RouteMatcher for MaybeMatchContentTypeHeader {
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
impl MaybeMatchContentTypeHeader {
fn new(types: Option<Vec<Mime>>) -> Self {
Self {
matcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type())
}
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
Self::new(types)
}
}
macro_rules! implDrawResourceRoutes {
($implType:ident) => {
#[cfg(feature = "openapi")]
@ -332,108 +216,30 @@ macro_rules! implDrawResourceRoutes {
}
}
#[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<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn read_all<Handler: ResourceReadAll>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0
.get(&self.1)
.extend_route_matcher(matcher)
.to(|state| read_all_handler::<Handler>(state));
}
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>);
fn read<Handler: ResourceRead>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0
.get(&format!("{}/:id", self.1))
.extend_route_matcher(matcher)
.with_path_extractor::<PathExtractor<Handler::ID>>()
.to(|state| read_handler::<Handler>(state));
}
fn search<Handler: ResourceSearch>(&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::<Handler::Query>()
.to(|state| search_handler::<Handler>(state));
}
fn create<Handler: ResourceCreate>(&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::<Handler>(state));
#[cfg(feature = "cors")]
self.0.cors(&self.1, Method::POST);
}
fn change_all<Handler: ResourceChangeAll>(&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::<Handler>(state));
#[cfg(feature = "cors")]
self.0.cors(&self.1, Method::PUT);
}
fn change<Handler: ResourceChange>(&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::<PathExtractor<Handler::ID>>()
.to(|state| change_handler::<Handler>(state));
#[cfg(feature = "cors")]
self.0.cors(&path, Method::PUT);
}
fn remove_all<Handler: ResourceRemoveAll>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0
.delete(&self.1)
.extend_route_matcher(matcher)
.to(|state| remove_all_handler::<Handler>(state));
#[cfg(feature = "cors")]
self.0.cors(&self.1, Method::DELETE);
}
fn remove<Handler: ResourceRemove>(&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::<PathExtractor<Handler::ID>>()
.to(|state| remove_handler::<Handler>(state));
#[cfg(feature = "cors")]
self.0.cors(&path, Method::POST);
#[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);
}
});
}
}
};

View file

@ -4,7 +4,7 @@ use crate::OpenapiType;
use gotham::hyper::body::Bytes;
use mime::{Mime, APPLICATION_JSON};
use serde::{de::DeserializeOwned, Serialize};
use std::{error::Error, panic::RefUnwindSafe};
use std::error::Error;
#[cfg(not(feature = "openapi"))]
pub trait ResourceType {}
@ -98,12 +98,3 @@ impl<T: ResourceType + DeserializeOwned> RequestBody for T {
Some(vec![APPLICATION_JSON])
}
}
/// A type than can be used as a parameter to a resource method. Implemented for every type
/// that is deserialize and thread-safe. If the `openapi` feature is used, it must also be of
/// type [OpenapiType].
///
/// [OpenapiType]: trait.OpenapiType.html
pub trait ResourceID: ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync {}
impl<T: ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync> ResourceID for T {}