mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-23 04:52:28 +00:00
Merge branch 'rename-update-and-delete' into 'master'
Rename update and delete methods Closes #17 See merge request msrd0/gotham-restful!13
This commit is contained in:
commit
d8c4215cc2
15 changed files with 433 additions and 199 deletions
|
@ -13,7 +13,6 @@ test-default:
|
||||||
- cargo -V
|
- cargo -V
|
||||||
script:
|
script:
|
||||||
- cargo test --workspace --lib
|
- cargo test --workspace --lib
|
||||||
- cargo test --workspace --doc
|
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- cargo/
|
- cargo/
|
||||||
|
@ -23,6 +22,7 @@ test-all:
|
||||||
stage: test
|
stage: test
|
||||||
image: msrd0/rust:alpine-tarpaulin
|
image: msrd0/rust:alpine-tarpaulin
|
||||||
before_script:
|
before_script:
|
||||||
|
- apk add --no-cache postgresql-dev
|
||||||
- cargo -V
|
- cargo -V
|
||||||
script:
|
script:
|
||||||
- cargo test --workspace --all-features --doc
|
- cargo test --workspace --all-features --doc
|
||||||
|
|
196
README.md
196
README.md
|
@ -23,58 +23,71 @@
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
This crate is an extension to the popular [gotham web framework][gotham] for Rust. The idea is to
|
|
||||||
have several RESTful resources that can be added to the gotham router. This crate will take care
|
|
||||||
of everything else, like parsing path/query parameters, request bodies, and writing response
|
|
||||||
bodies, relying on [`serde`][serde] and [`serde_json`][serde_json] for (de)serializing. If you
|
|
||||||
enable the `openapi` feature, you can also generate an OpenAPI Specification from your RESTful
|
|
||||||
resources.
|
|
||||||
|
|
||||||
**Note:** The `stable` branch contains some bugfixes against the last release. The `master`
|
**Note:** The `stable` branch contains some bugfixes against the last release. The `master`
|
||||||
branch currently tracks gotham's master branch and the next release will use gotham 0.5.0 and be
|
branch currently tracks gotham's master branch and the next release will use gotham 0.5.0 and be
|
||||||
compatible with the new future / async stuff.
|
compatible with the new future / async stuff.
|
||||||
|
|
||||||
## Usage
|
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
|
||||||
|
for requests. Assuming you assign `/foobar` to your resource, you can implement the following
|
||||||
|
methods:
|
||||||
|
|
||||||
A basic server with only one resource, handling a simple `GET` request, could look like this:
|
| 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 |
|
||||||
|
|
||||||
|
Each of those methods has a macro that creates the neccessary boilerplate for the Resource. A
|
||||||
|
simple example could look like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
/// Our RESTful Resource.
|
/// Our RESTful resource.
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[rest_resource(read_all)]
|
#[resource(read)]
|
||||||
struct UsersResource;
|
struct FooResource;
|
||||||
|
|
||||||
/// Our return type.
|
/// The return type of the foo read method.
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Serialize)]
|
||||||
struct User {
|
struct Foo {
|
||||||
id: i64,
|
id: u64
|
||||||
username: String,
|
|
||||||
email: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Our handler method.
|
/// The foo read method handler.
|
||||||
#[rest_read_all(UsersResource)]
|
#[read(FooResource)]
|
||||||
fn read_all(_state: &mut State) -> Success<Vec<User>> {
|
fn read(id: u64) -> Success<Foo> {
|
||||||
vec![User {
|
Foo { id }.into()
|
||||||
id: 1,
|
|
||||||
username: "h4ck3r".to_string(),
|
|
||||||
email: "h4ck3r@example.org".to_string()
|
|
||||||
}].into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Our main method.
|
|
||||||
fn main() {
|
|
||||||
gotham::start("127.0.0.1:8080", build_simple_router(|route| {
|
|
||||||
route.resource::<UsersResource>("users");
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Uploads and Downloads can also be handled:
|
## Arguments
|
||||||
|
|
||||||
|
Some methods 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`].
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Uploads and Downloads
|
||||||
|
|
||||||
|
By default, every request body is parsed from json, and every respone is converted to json using
|
||||||
|
[serde_json]. However, you may also use raw bodies. This is an example where the request body
|
||||||
|
is simply returned as the response again, no json parsing involved:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[rest_resource(create)]
|
#[resource(create)]
|
||||||
struct ImageResource;
|
struct ImageResource;
|
||||||
|
|
||||||
#[derive(FromBody, RequestBody)]
|
#[derive(FromBody, RequestBody)]
|
||||||
|
@ -84,23 +97,105 @@ struct RawImage {
|
||||||
content_type: Mime
|
content_type: Mime
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_create(ImageResource)]
|
#[create(ImageResource)]
|
||||||
fn create(_state : &mut State, body : RawImage) -> Raw<Vec<u8>> {
|
fn create(body : RawImage) -> Raw<Vec<u8>> {
|
||||||
Raw::new(body.content, body.content_type)
|
Raw::new(body.content, body.content_type)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Look at the [example] for more methods and usage with the `openapi` feature.
|
## Features
|
||||||
|
|
||||||
## Known Issues
|
To make life easier for common use-cases, this create offers a few features that might be helpful
|
||||||
|
when you implement your web server.
|
||||||
|
|
||||||
These are currently known major issues. For a complete list please see
|
### Authentication Feature
|
||||||
[the issue tracker](https://gitlab.com/msrd0/gotham-restful/issues).
|
|
||||||
If you encounter any issues that aren't yet reported, please report them
|
|
||||||
[here](https://gitlab.com/msrd0/gotham-restful/issues/new).
|
|
||||||
|
|
||||||
- Enabling the `openapi` feature might break code ([#4](https://gitlab.com/msrd0/gotham-restful/issues/4))
|
In order to enable authentication support, enable the `auth` feature gate. This allows you to
|
||||||
- For `chrono`'s `DateTime` types, the format is `date-time` instead of `datetime` ([openapiv3#14](https://github.com/glademiller/openapiv3/pull/14))
|
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
|
||||||
|
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:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Resource)]
|
||||||
|
#[resource(read)]
|
||||||
|
struct SecretResource;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Secret {
|
||||||
|
id: u64,
|
||||||
|
intended_for: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
struct AuthData {
|
||||||
|
sub: String,
|
||||||
|
exp: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[read(SecretResource)]
|
||||||
|
fn read(auth: AuthStatus<AuthData>, id: u64) -> AuthSuccess<Secret> {
|
||||||
|
let intended_for = auth.ok()?.sub;
|
||||||
|
Ok(Secret { id, intended_for })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let auth: AuthMiddleware<AuthData, _> = AuthMiddleware::new(
|
||||||
|
AuthSource::AuthorizationHeader,
|
||||||
|
AuthValidation::default(),
|
||||||
|
StaticAuthHandler::from_array(b"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc")
|
||||||
|
);
|
||||||
|
let (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());
|
||||||
|
gotham::start("127.0.0.1:8080", build_router(chain, pipelines, |route| {
|
||||||
|
route.resource::<SecretResource>("secret");
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Feature
|
||||||
|
|
||||||
|
The database feature allows an easy integration of [diesel] into your handler functions. Please
|
||||||
|
note however that due to the way gotham's diesel middleware implementation, it is not possible
|
||||||
|
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:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Resource)]
|
||||||
|
#[resource(read_all)]
|
||||||
|
struct FooResource;
|
||||||
|
|
||||||
|
#[derive(Queryable, Serialize)]
|
||||||
|
struct Foo {
|
||||||
|
id: i64,
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[read_all(FooResource)]
|
||||||
|
fn read_all(conn: &PgConnection) -> QueryResult<Vec<Foo>> {
|
||||||
|
foo::table.load(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo = gotham_middleware_diesel::Repo<PgConnection>;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let repo = Repo::new(&env::var("DATABASE_URL").unwrap());
|
||||||
|
let diesel = DieselMiddleware::new(repo);
|
||||||
|
|
||||||
|
let (chain, pipelines) = single_pipeline(new_pipeline().add(diesel).build());
|
||||||
|
gotham::start("127.0.0.1:8080", build_router(chain, pipelines, |route| {
|
||||||
|
route.resource::<FooResource>("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.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -109,7 +204,10 @@ Licensed under your option of:
|
||||||
- [Eclipse Public License Version 2.0](https://gitlab.com/msrd0/gotham-restful/blob/master/LICENSE-EPL)
|
- [Eclipse Public License Version 2.0](https://gitlab.com/msrd0/gotham-restful/blob/master/LICENSE-EPL)
|
||||||
|
|
||||||
|
|
||||||
[example]: https://gitlab.com/msrd0/gotham-restful/tree/master/example
|
[diesel]: https://diesel.rs/
|
||||||
[gotham]: https://gotham.rs/
|
[example]: https://gitlab.com/msrd0/gotham-restful/tree/master/example
|
||||||
[serde]: https://github.com/serde-rs/serde#serde-----
|
[gotham]: https://gotham.rs/
|
||||||
[serde_json]: https://github.com/serde-rs/json#serde-json----
|
[serde_json]: https://github.com/serde-rs/json#serde-json----
|
||||||
|
[`QueryStringExtractor`]: ../gotham/extractor/trait.QueryStringExtractor.html
|
||||||
|
[`RequestBody`]: trait.RequestBody.html
|
||||||
|
[`State`]: ../gotham/state/struct.State.html
|
||||||
|
|
|
@ -18,13 +18,13 @@ use log4rs::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[rest_resource(ReadAll, Read, Search, Create, DeleteAll, Delete, Update, UpdateAll)]
|
#[resource(read_all, read, search, create, change_all, change, remove, remove_all)]
|
||||||
struct Users
|
struct Users
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[rest_resource(ReadAll)]
|
#[resource(ReadAll)]
|
||||||
struct Auth
|
struct Auth
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ struct User
|
||||||
username : String
|
username : String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_read_all(Users)]
|
#[read_all(Users)]
|
||||||
fn read_all() -> Success<Vec<Option<User>>>
|
fn read_all() -> Success<Vec<Option<User>>>
|
||||||
{
|
{
|
||||||
vec![Username().fake(), Username().fake()]
|
vec![Username().fake(), Username().fake()]
|
||||||
|
@ -45,50 +45,50 @@ fn read_all() -> Success<Vec<Option<User>>>
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_read(Users)]
|
#[read(Users)]
|
||||||
fn read(id : u64) -> Success<User>
|
fn read(id : u64) -> Success<User>
|
||||||
{
|
{
|
||||||
let username : String = Username().fake();
|
let username : String = Username().fake();
|
||||||
User { username: format!("{}{}", username, id) }.into()
|
User { username: format!("{}{}", username, id) }.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_search(Users)]
|
#[search(Users)]
|
||||||
fn search(query : User) -> Success<User>
|
fn search(query : User) -> Success<User>
|
||||||
{
|
{
|
||||||
query.into()
|
query.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_create(Users)]
|
#[create(Users)]
|
||||||
fn create(body : User)
|
fn create(body : User)
|
||||||
{
|
{
|
||||||
info!("Created User: {}", body.username);
|
info!("Created User: {}", body.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_update_all(Users)]
|
#[change_all(Users)]
|
||||||
fn update_all(body : Vec<User>)
|
fn update_all(body : Vec<User>)
|
||||||
{
|
{
|
||||||
info!("Changing all Users to {:?}", body.into_iter().map(|u| u.username).collect::<Vec<String>>());
|
info!("Changing all Users to {:?}", body.into_iter().map(|u| u.username).collect::<Vec<String>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_update(Users)]
|
#[change(Users)]
|
||||||
fn update(id : u64, body : User)
|
fn update(id : u64, body : User)
|
||||||
{
|
{
|
||||||
info!("Change User {} to {}", id, body.username);
|
info!("Change User {} to {}", id, body.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_delete_all(Users)]
|
#[delete_all(Users)]
|
||||||
fn delete_all()
|
fn delete_all()
|
||||||
{
|
{
|
||||||
info!("Delete all Users");
|
info!("Delete all Users");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_delete(Users)]
|
#[delete(Users)]
|
||||||
fn delete(id : u64)
|
fn delete(id : u64)
|
||||||
{
|
{
|
||||||
info!("Delete User {}", id);
|
info!("Delete User {}", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_read_all(Auth)]
|
#[read_all(Auth)]
|
||||||
fn auth_read_all(auth : AuthStatus<()>) -> AuthSuccess<String>
|
fn auth_read_all(auth : AuthStatus<()>) -> AuthSuccess<String>
|
||||||
{
|
{
|
||||||
match auth {
|
match auth {
|
||||||
|
|
|
@ -36,11 +36,12 @@ thiserror = "1.0.15"
|
||||||
uuid = { version = ">= 0.1, < 0.9", optional = true }
|
uuid = { version = ">= 0.1, < 0.9", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
diesel = { version = "1.4.4", features = ["postgres"] }
|
||||||
futures-executor = "0.3.4"
|
futures-executor = "0.3.4"
|
||||||
paste = "0.1.10"
|
paste = "0.1.10"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["errorlog"]
|
||||||
auth = ["gotham_restful_derive/auth", "base64", "cookie", "jsonwebtoken"]
|
auth = ["gotham_restful_derive/auth", "base64", "cookie", "jsonwebtoken"]
|
||||||
errorlog = []
|
errorlog = []
|
||||||
database = ["gotham_restful_derive/database", "gotham_middleware_diesel"]
|
database = ["gotham_restful_derive/database", "gotham_middleware_diesel"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::HeaderName;
|
use crate::{AuthError, Forbidden, HeaderName};
|
||||||
use cookie::CookieJar;
|
use cookie::CookieJar;
|
||||||
use futures_util::{future, future::{FutureExt, TryFutureExt}};
|
use futures_util::{future, future::{FutureExt, TryFutureExt}};
|
||||||
use gotham::{
|
use gotham::{
|
||||||
|
@ -58,6 +58,17 @@ where
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T : Send + 'static> AuthStatus<T>
|
||||||
|
{
|
||||||
|
pub fn ok(self) -> Result<T, AuthError>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Authenticated(data) => Ok(data),
|
||||||
|
_ => Err(Forbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The source of the authentication token in the request.
|
/// The source of the authentication token in the request.
|
||||||
#[derive(Clone, Debug, StateData)]
|
#[derive(Clone, Debug, StateData)]
|
||||||
pub enum AuthSource
|
pub enum AuthSource
|
||||||
|
@ -134,7 +145,7 @@ simply add it to your pipeline and request it inside your handler:
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
#
|
#
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[rest_resource(read_all)]
|
#[resource(read_all)]
|
||||||
struct AuthResource;
|
struct AuthResource;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
@ -143,7 +154,7 @@ struct AuthData {
|
||||||
exp: u64
|
exp: u64
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_read_all(AuthResource)]
|
#[read_all(AuthResource)]
|
||||||
fn read_all(auth : &AuthStatus<AuthData>) -> Success<String> {
|
fn read_all(auth : &AuthStatus<AuthData>) -> Success<String> {
|
||||||
format!("{:?}", auth).into()
|
format!("{:?}", auth).into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,67 +2,85 @@
|
||||||
#![warn(missing_debug_implementations, rust_2018_idioms)]
|
#![warn(missing_debug_implementations, rust_2018_idioms)]
|
||||||
#![deny(intra_doc_link_resolution_failure)]
|
#![deny(intra_doc_link_resolution_failure)]
|
||||||
/*!
|
/*!
|
||||||
This crate is an extension to the popular [gotham web framework][gotham] for Rust. The idea is to
|
|
||||||
have several RESTful resources that can be added to the gotham router. This crate will take care
|
|
||||||
of everything else, like parsing path/query parameters, request bodies, and writing response
|
|
||||||
bodies, relying on [`serde`][serde] and [`serde_json`][serde_json] for (de)serializing. If you
|
|
||||||
enable the `openapi` feature, you can also generate an OpenAPI Specification from your RESTful
|
|
||||||
resources.
|
|
||||||
|
|
||||||
**Note:** The `stable` branch contains some bugfixes against the last release. The `master`
|
**Note:** The `stable` branch contains some bugfixes against the last release. The `master`
|
||||||
branch currently tracks gotham's master branch and the next release will use gotham 0.5.0 and be
|
branch currently tracks gotham's master branch and the next release will use gotham 0.5.0 and be
|
||||||
compatible with the new future / async stuff.
|
compatible with the new future / async stuff.
|
||||||
|
|
||||||
# Usage
|
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
|
||||||
|
for requests. Assuming you assign `/foobar` to your resource, you can implement the following
|
||||||
|
methods:
|
||||||
|
|
||||||
A basic server with only one resource, handling a simple `GET` request, could look like this:
|
| 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 |
|
||||||
|
|
||||||
|
Each of those methods has a macro that creates the neccessary boilerplate for the Resource. A
|
||||||
|
simple example could look like this:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
# #[macro_use] extern crate gotham_restful_derive;
|
# #[macro_use] extern crate gotham_restful_derive;
|
||||||
# use gotham::{router::builder::*, state::State};
|
# use gotham::router::builder::*;
|
||||||
# use gotham_restful::{DrawResources, Resource, Success};
|
# use gotham_restful::*;
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
/// Our RESTful Resource.
|
/// Our RESTful resource.
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[rest_resource(read_all)]
|
#[resource(read)]
|
||||||
struct UsersResource;
|
struct FooResource;
|
||||||
|
|
||||||
/// Our return type.
|
/// The return type of the foo read method.
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Serialize)]
|
||||||
# #[derive(OpenapiType)]
|
# #[derive(OpenapiType)]
|
||||||
struct User {
|
struct Foo {
|
||||||
id: i64,
|
id: u64
|
||||||
username: String,
|
|
||||||
email: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Our handler method.
|
/// The foo read method handler.
|
||||||
#[rest_read_all(UsersResource)]
|
#[read(FooResource)]
|
||||||
fn read_all(_state: &mut State) -> Success<Vec<User>> {
|
fn read(id: u64) -> Success<Foo> {
|
||||||
vec![User {
|
Foo { id }.into()
|
||||||
id: 1,
|
|
||||||
username: "h4ck3r".to_string(),
|
|
||||||
email: "h4ck3r@example.org".to_string()
|
|
||||||
}].into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Our main method.
|
|
||||||
fn main() {
|
|
||||||
gotham::start("127.0.0.1:8080", build_simple_router(|route| {
|
|
||||||
route.resource::<UsersResource>("users");
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
# fn main() {
|
||||||
|
# gotham::start("127.0.0.1:8080", build_simple_router(|route| {
|
||||||
|
# route.resource::<FooResource>("foo");
|
||||||
|
# }));
|
||||||
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
Uploads and Downloads can also be handled:
|
# Arguments
|
||||||
|
|
||||||
|
Some methods 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`].
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
# Uploads and Downloads
|
||||||
|
|
||||||
|
By default, every request body is parsed from json, and every respone is converted to json using
|
||||||
|
[serde_json]. However, you may also use raw bodies. This is an example where the request body
|
||||||
|
is simply returned as the response again, no json parsing involved:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
# #[macro_use] extern crate gotham_restful_derive;
|
# #[macro_use] extern crate gotham_restful_derive;
|
||||||
# use gotham::{router::builder::*, state::State};
|
# use gotham::router::builder::*;
|
||||||
# use gotham_restful::{DrawResources, Mime, Raw, Resource, Success};
|
# use gotham_restful::*;
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[rest_resource(create)]
|
#[resource(create)]
|
||||||
struct ImageResource;
|
struct ImageResource;
|
||||||
|
|
||||||
#[derive(FromBody, RequestBody)]
|
#[derive(FromBody, RequestBody)]
|
||||||
|
@ -72,8 +90,8 @@ struct RawImage {
|
||||||
content_type: Mime
|
content_type: Mime
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_create(ImageResource)]
|
#[create(ImageResource)]
|
||||||
fn create(_state : &mut State, body : RawImage) -> Raw<Vec<u8>> {
|
fn create(body : RawImage) -> Raw<Vec<u8>> {
|
||||||
Raw::new(body.content, body.content_type)
|
Raw::new(body.content, body.content_type)
|
||||||
}
|
}
|
||||||
# fn main() {
|
# fn main() {
|
||||||
|
@ -83,17 +101,119 @@ fn create(_state : &mut State, body : RawImage) -> Raw<Vec<u8>> {
|
||||||
# }
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
Look at the [example] for more methods and usage with the `openapi` feature.
|
# Features
|
||||||
|
|
||||||
# Known Issues
|
To make life easier for common use-cases, this create offers a few features that might be helpful
|
||||||
|
when you implement your web server.
|
||||||
|
|
||||||
These are currently known major issues. For a complete list please see
|
## Authentication Feature
|
||||||
[the issue tracker](https://gitlab.com/msrd0/gotham-restful/issues).
|
|
||||||
If you encounter any issues that aren't yet reported, please report them
|
|
||||||
[here](https://gitlab.com/msrd0/gotham-restful/issues/new).
|
|
||||||
|
|
||||||
- Enabling the `openapi` feature might break code ([#4](https://gitlab.com/msrd0/gotham-restful/issues/4))
|
In order to enable authentication support, enable the `auth` feature gate. This allows you to
|
||||||
- For `chrono`'s `DateTime` types, the format is `date-time` instead of `datetime` ([openapiv3#14](https://github.com/glademiller/openapiv3/pull/14))
|
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
|
||||||
|
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:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
# #[macro_use] extern crate gotham_restful_derive;
|
||||||
|
# use gotham::{router::builder::*, pipeline::{new_pipeline, single::single_pipeline}, state::State};
|
||||||
|
# use gotham_restful::*;
|
||||||
|
# use serde::{Deserialize, Serialize};
|
||||||
|
#[derive(Resource)]
|
||||||
|
#[resource(read)]
|
||||||
|
struct SecretResource;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
# #[derive(OpenapiType)]
|
||||||
|
struct Secret {
|
||||||
|
id: u64,
|
||||||
|
intended_for: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
struct AuthData {
|
||||||
|
sub: String,
|
||||||
|
exp: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[read(SecretResource)]
|
||||||
|
fn read(auth: AuthStatus<AuthData>, id: u64) -> AuthSuccess<Secret> {
|
||||||
|
let intended_for = auth.ok()?.sub;
|
||||||
|
Ok(Secret { id, intended_for })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let auth: AuthMiddleware<AuthData, _> = AuthMiddleware::new(
|
||||||
|
AuthSource::AuthorizationHeader,
|
||||||
|
AuthValidation::default(),
|
||||||
|
StaticAuthHandler::from_array(b"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc")
|
||||||
|
);
|
||||||
|
let (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());
|
||||||
|
gotham::start("127.0.0.1:8080", build_router(chain, pipelines, |route| {
|
||||||
|
route.resource::<SecretResource>("secret");
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Feature
|
||||||
|
|
||||||
|
The database feature allows an easy integration of [diesel] into your handler functions. Please
|
||||||
|
note however that due to the way gotham's diesel middleware implementation, it is not possible
|
||||||
|
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:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
# #[macro_use] extern crate diesel;
|
||||||
|
# #[macro_use] extern crate gotham_restful_derive;
|
||||||
|
# use diesel::{table, PgConnection, QueryResult, RunQueryDsl};
|
||||||
|
# use gotham::{router::builder::*, pipeline::{new_pipeline, single::single_pipeline}, state::State};
|
||||||
|
# use gotham_middleware_diesel::DieselMiddleware;
|
||||||
|
# use gotham_restful::*;
|
||||||
|
# use serde::{Deserialize, Serialize};
|
||||||
|
# use std::env;
|
||||||
|
# table! {
|
||||||
|
# foo (id) {
|
||||||
|
# id -> Int8,
|
||||||
|
# value -> Text,
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#[derive(Resource)]
|
||||||
|
#[resource(read_all)]
|
||||||
|
struct FooResource;
|
||||||
|
|
||||||
|
#[derive(Queryable, Serialize)]
|
||||||
|
# #[derive(OpenapiType)]
|
||||||
|
struct Foo {
|
||||||
|
id: i64,
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[read_all(FooResource)]
|
||||||
|
fn read_all(conn: &PgConnection) -> QueryResult<Vec<Foo>> {
|
||||||
|
foo::table.load(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo = gotham_middleware_diesel::Repo<PgConnection>;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let repo = Repo::new(&env::var("DATABASE_URL").unwrap());
|
||||||
|
let diesel = DieselMiddleware::new(repo);
|
||||||
|
|
||||||
|
let (chain, pipelines) = single_pipeline(new_pipeline().add(diesel).build());
|
||||||
|
gotham::start("127.0.0.1:8080", build_router(chain, pipelines, |route| {
|
||||||
|
route.resource::<FooResource>("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.
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
@ -102,10 +222,13 @@ Licensed under your option of:
|
||||||
- [Eclipse Public License Version 2.0](https://gitlab.com/msrd0/gotham-restful/blob/master/LICENSE-EPL)
|
- [Eclipse Public License Version 2.0](https://gitlab.com/msrd0/gotham-restful/blob/master/LICENSE-EPL)
|
||||||
|
|
||||||
|
|
||||||
[example]: https://gitlab.com/msrd0/gotham-restful/tree/master/example
|
[diesel]: https://diesel.rs/
|
||||||
[gotham]: https://gotham.rs/
|
[example]: https://gitlab.com/msrd0/gotham-restful/tree/master/example
|
||||||
[serde]: https://github.com/serde-rs/serde#serde-----
|
[gotham]: https://gotham.rs/
|
||||||
[serde_json]: https://github.com/serde-rs/json#serde-json----
|
[serde_json]: https://github.com/serde-rs/json#serde-json----
|
||||||
|
[`QueryStringExtractor`]: ../gotham/extractor/trait.QueryStringExtractor.html
|
||||||
|
[`RequestBody`]: trait.RequestBody.html
|
||||||
|
[`State`]: ../gotham/state/struct.State.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// weird proc macro issue
|
// weird proc macro issue
|
||||||
|
@ -175,10 +298,10 @@ pub use resource::{
|
||||||
ResourceRead,
|
ResourceRead,
|
||||||
ResourceSearch,
|
ResourceSearch,
|
||||||
ResourceCreate,
|
ResourceCreate,
|
||||||
ResourceUpdateAll,
|
ResourceChangeAll,
|
||||||
ResourceUpdate,
|
ResourceChange,
|
||||||
ResourceDeleteAll,
|
ResourceRemoveAll,
|
||||||
ResourceDelete
|
ResourceRemove
|
||||||
};
|
};
|
||||||
|
|
||||||
mod response;
|
mod response;
|
||||||
|
|
|
@ -106,7 +106,7 @@ macro_rules! implOpenapiRouter {
|
||||||
(&mut *(self.0).router, self.1).create::<Handler>()
|
(&mut *(self.0).router, self.1).create::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_all<Handler : ResourceUpdateAll>(&mut self)
|
fn change_all<Handler : ResourceChangeAll>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res : 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body : 'static
|
||||||
|
@ -119,10 +119,10 @@ macro_rules! implOpenapiRouter {
|
||||||
item.put = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
|
item.put = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).update_all::<Handler>()
|
(&mut *(self.0).router, self.1).change_all::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update<Handler : ResourceUpdate>(&mut self)
|
fn change<Handler : ResourceChange>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res : 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body : 'static
|
||||||
|
@ -136,10 +136,10 @@ macro_rules! implOpenapiRouter {
|
||||||
item.put = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).with_body::<Handler::Body>(body_schema).into_operation());
|
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);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).update::<Handler>()
|
(&mut *(self.0).router, self.1).change::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_all<Handler : ResourceDeleteAll>(&mut self)
|
fn remove_all<Handler : ResourceRemoveAll>(&mut self)
|
||||||
{
|
{
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
|
|
||||||
|
@ -148,10 +148,10 @@ macro_rules! implOpenapiRouter {
|
||||||
item.delete = Some(OperationDescription::new::<Handler>(schema).into_operation());
|
item.delete = Some(OperationDescription::new::<Handler>(schema).into_operation());
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).delete_all::<Handler>()
|
(&mut *(self.0).router, self.1).remove_all::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete<Handler : ResourceDelete>(&mut self)
|
fn remove<Handler : ResourceRemove>(&mut self)
|
||||||
{
|
{
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
||||||
|
@ -161,7 +161,7 @@ macro_rules! implOpenapiRouter {
|
||||||
item.delete = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).into_operation());
|
item.delete = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).into_operation());
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).delete::<Handler>()
|
(&mut *(self.0).router, self.1).remove::<Handler>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,32 +68,32 @@ pub trait ResourceCreate : ResourceMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a PUT request on the Resource root.
|
/// Handle a PUT request on the Resource root.
|
||||||
pub trait ResourceUpdateAll : ResourceMethod
|
pub trait ResourceChangeAll : ResourceMethod
|
||||||
{
|
{
|
||||||
type Body : RequestBody;
|
type Body : RequestBody;
|
||||||
|
|
||||||
fn update_all(state : State, body : Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn change_all(state : State, body : Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a PUT request on the Resource with an id.
|
/// Handle a PUT request on the Resource with an id.
|
||||||
pub trait ResourceUpdate : ResourceMethod
|
pub trait ResourceChange : ResourceMethod
|
||||||
{
|
{
|
||||||
type Body : RequestBody;
|
type Body : RequestBody;
|
||||||
type ID : ResourceID + 'static;
|
type ID : ResourceID + 'static;
|
||||||
|
|
||||||
fn update(state : State, id : Self::ID, body : Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn change(state : State, id : Self::ID, body : Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a DELETE request on the Resource root.
|
/// Handle a DELETE request on the Resource root.
|
||||||
pub trait ResourceDeleteAll : ResourceMethod
|
pub trait ResourceRemoveAll : ResourceMethod
|
||||||
{
|
{
|
||||||
fn delete_all(state : State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn remove_all(state : State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a DELETE request on the Resource with an id.
|
/// Handle a DELETE request on the Resource with an id.
|
||||||
pub trait ResourceDelete : ResourceMethod
|
pub trait ResourceRemove : ResourceMethod
|
||||||
{
|
{
|
||||||
type ID : ResourceID + 'static;
|
type ID : ResourceID + 'static;
|
||||||
|
|
||||||
fn delete(state : State, id : Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn remove(state : State, id : Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,13 @@ look something like this (assuming the `auth` feature is enabled):
|
||||||
# use serde::Deserialize;
|
# use serde::Deserialize;
|
||||||
#
|
#
|
||||||
# #[derive(Resource)]
|
# #[derive(Resource)]
|
||||||
|
# #[resource(read_all)]
|
||||||
# struct MyResource;
|
# struct MyResource;
|
||||||
#
|
#
|
||||||
# #[derive(Clone, Deserialize)]
|
# #[derive(Clone, Deserialize)]
|
||||||
# struct MyAuthData { exp : u64 }
|
# struct MyAuthData { exp : u64 }
|
||||||
#
|
#
|
||||||
#[rest_read_all(MyResource)]
|
#[read_all(MyResource)]
|
||||||
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthSuccess<NoContent> {
|
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthSuccess<NoContent> {
|
||||||
let auth_data = match auth {
|
let auth_data = match auth {
|
||||||
AuthStatus::Authenticated(data) => data,
|
AuthStatus::Authenticated(data) => data,
|
||||||
|
@ -88,12 +89,13 @@ look something like this (assuming the `auth` feature is enabled):
|
||||||
# use std::io;
|
# use std::io;
|
||||||
#
|
#
|
||||||
# #[derive(Resource)]
|
# #[derive(Resource)]
|
||||||
|
# #[resource(read_all)]
|
||||||
# struct MyResource;
|
# struct MyResource;
|
||||||
#
|
#
|
||||||
# #[derive(Clone, Deserialize)]
|
# #[derive(Clone, Deserialize)]
|
||||||
# struct MyAuthData { exp : u64 }
|
# struct MyAuthData { exp : u64 }
|
||||||
#
|
#
|
||||||
#[rest_read_all(MyResource)]
|
#[read_all(MyResource)]
|
||||||
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthResult<NoContent, io::Error> {
|
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthResult<NoContent, io::Error> {
|
||||||
let auth_data = match auth {
|
let auth_data = match auth {
|
||||||
AuthStatus::Authenticated(data) => data,
|
AuthStatus::Authenticated(data) => data,
|
||||||
|
|
|
@ -22,9 +22,10 @@ the function attributes:
|
||||||
# use gotham_restful::*;
|
# use gotham_restful::*;
|
||||||
#
|
#
|
||||||
# #[derive(Resource)]
|
# #[derive(Resource)]
|
||||||
|
# #[resource(read_all)]
|
||||||
# struct MyResource;
|
# struct MyResource;
|
||||||
#
|
#
|
||||||
#[rest_read_all(MyResource)]
|
#[read_all(MyResource)]
|
||||||
fn read_all(_state: &mut State) {
|
fn read_all(_state: &mut State) {
|
||||||
// do something
|
// do something
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ Usage example:
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
#
|
#
|
||||||
# #[derive(Resource)]
|
# #[derive(Resource)]
|
||||||
|
# #[resource(read_all)]
|
||||||
# struct MyResource;
|
# struct MyResource;
|
||||||
#
|
#
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
@ -33,7 +34,7 @@ struct MyResponse {
|
||||||
message: &'static str
|
message: &'static str
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rest_read_all(MyResource)]
|
#[read_all(MyResource)]
|
||||||
fn read_all(_state: &mut State) -> Success<MyResponse> {
|
fn read_all(_state: &mut State) -> Success<MyResponse> {
|
||||||
let res = MyResponse { message: "I'm always happy" };
|
let res = MyResponse { message: "I'm always happy" };
|
||||||
res.into()
|
res.into()
|
||||||
|
|
|
@ -78,19 +78,19 @@ pub trait DrawResourceRoutes
|
||||||
Handler::Res : 'static,
|
Handler::Res : 'static,
|
||||||
Handler::Body : 'static;
|
Handler::Body : 'static;
|
||||||
|
|
||||||
fn update_all<Handler : ResourceUpdateAll>(&mut self)
|
fn change_all<Handler : ResourceChangeAll>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res : 'static,
|
||||||
Handler::Body : 'static;
|
Handler::Body : 'static;
|
||||||
|
|
||||||
fn update<Handler : ResourceUpdate>(&mut self)
|
fn change<Handler : ResourceChange>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res : 'static,
|
||||||
Handler::Body : 'static;
|
Handler::Body : 'static;
|
||||||
|
|
||||||
fn delete_all<Handler : ResourceDeleteAll>(&mut self);
|
fn remove_all<Handler : ResourceRemoveAll>(&mut self);
|
||||||
|
|
||||||
fn delete<Handler : ResourceDelete>(&mut self);
|
fn remove<Handler : ResourceRemove>(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_from(res : Response, state : &State) -> gotham::hyper::Response<Body>
|
fn response_from(res : Response, state : &State) -> gotham::hyper::Response<Body>
|
||||||
|
@ -217,15 +217,15 @@ where
|
||||||
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::create(state, body))
|
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::create(state, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_all_handler<Handler : ResourceUpdateAll>(state : State) -> Pin<Box<HandlerFuture>>
|
fn change_all_handler<Handler : ResourceChangeAll>(state : State) -> Pin<Box<HandlerFuture>>
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res : 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body : 'static
|
||||||
{
|
{
|
||||||
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::update_all(state, body))
|
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change_all(state, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_handler<Handler : ResourceUpdate>(state : State) -> Pin<Box<HandlerFuture>>
|
fn change_handler<Handler : ResourceChange>(state : State) -> Pin<Box<HandlerFuture>>
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res : 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body : 'static
|
||||||
|
@ -234,21 +234,21 @@ where
|
||||||
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
||||||
path.id.clone()
|
path.id.clone()
|
||||||
};
|
};
|
||||||
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::update(state, id, body))
|
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change(state, id, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_all_handler<Handler : ResourceDeleteAll>(state : State) -> Pin<Box<HandlerFuture>>
|
fn remove_all_handler<Handler : ResourceRemoveAll>(state : State) -> Pin<Box<HandlerFuture>>
|
||||||
{
|
{
|
||||||
to_handler_future(state, |state| Handler::delete_all(state)).boxed()
|
to_handler_future(state, |state| Handler::remove_all(state)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_handler<Handler : ResourceDelete>(state : State) -> Pin<Box<HandlerFuture>>
|
fn remove_handler<Handler : ResourceRemove>(state : State) -> Pin<Box<HandlerFuture>>
|
||||||
{
|
{
|
||||||
let id = {
|
let id = {
|
||||||
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
||||||
path.id.clone()
|
path.id.clone()
|
||||||
};
|
};
|
||||||
to_handler_future(state, |state| Handler::delete(state, id)).boxed()
|
to_handler_future(state, |state| Handler::remove(state, id)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -386,7 +386,7 @@ macro_rules! implDrawResourceRoutes {
|
||||||
.to(|state| create_handler::<Handler>(state));
|
.to(|state| create_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_all<Handler : ResourceUpdateAll>(&mut self)
|
fn change_all<Handler : ResourceChangeAll>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : Send + 'static,
|
Handler::Res : Send + 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body : 'static
|
||||||
|
@ -396,10 +396,10 @@ macro_rules! implDrawResourceRoutes {
|
||||||
self.0.put(&self.1)
|
self.0.put(&self.1)
|
||||||
.extend_route_matcher(accept_matcher)
|
.extend_route_matcher(accept_matcher)
|
||||||
.extend_route_matcher(content_matcher)
|
.extend_route_matcher(content_matcher)
|
||||||
.to(|state| update_all_handler::<Handler>(state));
|
.to(|state| change_all_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update<Handler : ResourceUpdate>(&mut self)
|
fn change<Handler : ResourceChange>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : Send + 'static,
|
Handler::Res : Send + 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body : 'static
|
||||||
|
@ -410,24 +410,24 @@ macro_rules! implDrawResourceRoutes {
|
||||||
.extend_route_matcher(accept_matcher)
|
.extend_route_matcher(accept_matcher)
|
||||||
.extend_route_matcher(content_matcher)
|
.extend_route_matcher(content_matcher)
|
||||||
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
||||||
.to(|state| update_handler::<Handler>(state));
|
.to(|state| change_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_all<Handler : ResourceDeleteAll>(&mut self)
|
fn remove_all<Handler : ResourceRemoveAll>(&mut self)
|
||||||
{
|
{
|
||||||
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
self.0.delete(&self.1)
|
self.0.delete(&self.1)
|
||||||
.extend_route_matcher(matcher)
|
.extend_route_matcher(matcher)
|
||||||
.to(|state| delete_all_handler::<Handler>(state));
|
.to(|state| remove_all_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete<Handler : ResourceDelete>(&mut self)
|
fn remove<Handler : ResourceRemove>(&mut self)
|
||||||
{
|
{
|
||||||
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
self.0.delete(&format!("{}/:id", self.1))
|
self.0.delete(&format!("{}/:id", self.1))
|
||||||
.extend_route_matcher(matcher)
|
.extend_route_matcher(matcher)
|
||||||
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
||||||
.to(|state| delete_handler::<Handler>(state));
|
.to(|state| remove_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ pub fn derive_request_body(input : TokenStream) -> TokenStream
|
||||||
expand_derive(input, expand_request_body)
|
expand_derive(input, expand_request_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(Resource, attributes(rest_resource))]
|
#[proc_macro_derive(Resource, attributes(resource))]
|
||||||
pub fn derive_resource(input : TokenStream) -> TokenStream
|
pub fn derive_resource(input : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_derive(input, expand_resource)
|
expand_derive(input, expand_resource)
|
||||||
|
@ -86,49 +86,49 @@ pub fn derive_resource_error(input : TokenStream) -> TokenStream
|
||||||
|
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_read_all(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn read_all(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::ReadAll, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::ReadAll, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_read(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn read(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::Read, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Read, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_search(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn search(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::Search, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Search, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_create(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn create(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::Create, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Create, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_update_all(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn change_all(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::UpdateAll, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::ChangeAll, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_update(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn change(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::Update, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Change, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_delete_all(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn delete_all(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::DeleteAll, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::RemoveAll, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn rest_delete(attr : TokenStream, item : TokenStream) -> TokenStream
|
pub fn delete(attr : TokenStream, item : TokenStream) -> TokenStream
|
||||||
{
|
{
|
||||||
expand_macro(attr, item, |attr, item| expand_method(Method::Delete, attr, item))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Remove, attr, item))
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,10 @@ pub enum Method
|
||||||
Read,
|
Read,
|
||||||
Search,
|
Search,
|
||||||
Create,
|
Create,
|
||||||
UpdateAll,
|
ChangeAll,
|
||||||
Update,
|
Change,
|
||||||
DeleteAll,
|
RemoveAll,
|
||||||
Delete
|
Remove
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Method
|
impl FromStr for Method
|
||||||
|
@ -43,10 +43,10 @@ impl FromStr for Method
|
||||||
"Read" | "read" => Ok(Self::Read),
|
"Read" | "read" => Ok(Self::Read),
|
||||||
"Search" | "search" => Ok(Self::Search),
|
"Search" | "search" => Ok(Self::Search),
|
||||||
"Create" | "create" => Ok(Self::Create),
|
"Create" | "create" => Ok(Self::Create),
|
||||||
"UpdateAll" | "update_all" => Ok(Self::UpdateAll),
|
"ChangeAll" | "change_all" => Ok(Self::ChangeAll),
|
||||||
"Update" | "update" => Ok(Self::Update),
|
"Change" | "change" => Ok(Self::Change),
|
||||||
"DeleteAll" | "delete_all" => Ok(Self::DeleteAll),
|
"RemoveAll" | "remove_all" => Ok(Self::RemoveAll),
|
||||||
"Delete" | "delete" => Ok(Self::Delete),
|
"Remove" | "remove" => Ok(Self::Remove),
|
||||||
_ => Err(Error::new(Span::call_site(), format!("Unknown method: `{}'", str)))
|
_ => Err(Error::new(Span::call_site(), format!("Unknown method: `{}'", str)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,14 +59,11 @@ impl Method
|
||||||
use Method::*;
|
use Method::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
ReadAll => vec![],
|
ReadAll | RemoveAll => vec![],
|
||||||
Read => vec!["ID"],
|
Read | Remove => vec!["ID"],
|
||||||
Search => vec!["Query"],
|
Search => vec!["Query"],
|
||||||
Create => vec!["Body"],
|
Create | ChangeAll => vec!["Body"],
|
||||||
UpdateAll => vec!["Body"],
|
Change => vec!["ID", "Body"]
|
||||||
Update => vec!["ID", "Body"],
|
|
||||||
DeleteAll => vec![],
|
|
||||||
Delete => vec!["ID"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,10 +76,10 @@ impl Method
|
||||||
Read => "Read",
|
Read => "Read",
|
||||||
Search => "Search",
|
Search => "Search",
|
||||||
Create => "Create",
|
Create => "Create",
|
||||||
UpdateAll => "UpdateAll",
|
ChangeAll => "ChangeAll",
|
||||||
Update => "Update",
|
Change => "Change",
|
||||||
DeleteAll => "DeleteAll",
|
RemoveAll => "RemoveAll",
|
||||||
Delete => "Delete"
|
Remove => "Remove"
|
||||||
};
|
};
|
||||||
format_ident!("Resource{}", name)
|
format_ident!("Resource{}", name)
|
||||||
}
|
}
|
||||||
|
@ -96,10 +93,10 @@ impl Method
|
||||||
Read => "read",
|
Read => "read",
|
||||||
Search => "search",
|
Search => "search",
|
||||||
Create => "create",
|
Create => "create",
|
||||||
UpdateAll => "update_all",
|
ChangeAll => "change_all",
|
||||||
Update => "update",
|
Change => "change",
|
||||||
DeleteAll => "delete_all",
|
RemoveAll => "remove_all",
|
||||||
Delete => "delete"
|
Remove => "remove"
|
||||||
};
|
};
|
||||||
format_ident!("{}", name)
|
format_ident!("{}", name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub fn expand_resource(input : DeriveInput) -> Result<TokenStream>
|
||||||
let name = ident.to_string();
|
let name = ident.to_string();
|
||||||
|
|
||||||
let methods = input.attrs.into_iter().filter(|attr|
|
let methods = input.attrs.into_iter().filter(|attr|
|
||||||
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("rest_resource".to_string()) // TODO wtf
|
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string()) // TODO wtf
|
||||||
).map(|attr| {
|
).map(|attr| {
|
||||||
syn::parse2(attr.tokens).map(|m : MethodList| m.0.into_iter())
|
syn::parse2(attr.tokens).map(|m : MethodList| m.0.into_iter())
|
||||||
}).flat_map(|list| match list {
|
}).flat_map(|list| match list {
|
||||||
|
|
Loading…
Add table
Reference in a new issue