1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-02-22 20:52:27 +00:00

update doc

This commit is contained in:
Dominic 2020-05-04 21:34:20 +02:00
parent cc86d3396c
commit a1acc06f6d
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
8 changed files with 348 additions and 111 deletions

View file

@ -13,7 +13,6 @@ test-default:
- cargo -V
script:
- cargo test --workspace --lib
- cargo test --workspace --doc
cache:
paths:
- cargo/
@ -23,6 +22,7 @@ test-all:
stage: test
image: msrd0/rust:alpine-tarpaulin
before_script:
- apk add --no-cache postgresql-dev
- cargo -V
script:
- cargo test --workspace --all-features --doc

196
README.md
View file

@ -23,58 +23,71 @@
</div>
<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`
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.
## 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
/// Our RESTful Resource.
/// Our RESTful resource.
#[derive(Resource)]
#[rest_resource(read_all)]
struct UsersResource;
#[resource(read)]
struct FooResource;
/// Our return type.
#[derive(Deserialize, Serialize)]
struct User {
id: i64,
username: String,
email: String
/// The return type of the foo read method.
#[derive(Serialize)]
struct Foo {
id: u64
}
/// Our handler method.
#[rest_read_all(UsersResource)]
fn read_all(_state: &mut State) -> Success<Vec<User>> {
vec![User {
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");
}));
/// The foo read method handler.
#[read(FooResource)]
fn read(id: u64) -> Success<Foo> {
Foo { id }.into()
}
```
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
#[derive(Resource)]
#[rest_resource(create)]
#[resource(create)]
struct ImageResource;
#[derive(FromBody, RequestBody)]
@ -84,23 +97,105 @@ struct RawImage {
content_type: Mime
}
#[rest_create(ImageResource)]
fn create(_state : &mut State, body : RawImage) -> Raw<Vec<u8>> {
#[create(ImageResource)]
fn create(body : RawImage) -> Raw<Vec<u8>> {
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
[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).
### Authentication Feature
- Enabling the `openapi` feature might break code ([#4](https://gitlab.com/msrd0/gotham-restful/issues/4))
- For `chrono`'s `DateTime` types, the format is `date-time` instead of `datetime` ([openapiv3#14](https://github.com/glademiller/openapiv3/pull/14))
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
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
@ -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)
[example]: https://gitlab.com/msrd0/gotham-restful/tree/master/example
[gotham]: https://gotham.rs/
[serde]: https://github.com/serde-rs/serde#serde-----
[serde_json]: https://github.com/serde-rs/json#serde-json----
[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----
[`QueryStringExtractor`]: ../gotham/extractor/trait.QueryStringExtractor.html
[`RequestBody`]: trait.RequestBody.html
[`State`]: ../gotham/state/struct.State.html

View file

@ -36,11 +36,12 @@ thiserror = "1.0.15"
uuid = { version = ">= 0.1, < 0.9", optional = true }
[dev-dependencies]
diesel = { version = "1.4.4", features = ["postgres"] }
futures-executor = "0.3.4"
paste = "0.1.10"
[features]
default = []
default = ["errorlog"]
auth = ["gotham_restful_derive/auth", "base64", "cookie", "jsonwebtoken"]
errorlog = []
database = ["gotham_restful_derive/database", "gotham_middleware_diesel"]

View file

@ -1,4 +1,4 @@
use crate::HeaderName;
use crate::{AuthError, Forbidden, HeaderName};
use cookie::CookieJar;
use futures_util::{future, future::{FutureExt, TryFutureExt}};
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.
#[derive(Clone, Debug, StateData)]
pub enum AuthSource
@ -134,7 +145,7 @@ simply add it to your pipeline and request it inside your handler:
# use serde::{Deserialize, Serialize};
#
#[derive(Resource)]
#[rest_resource(read_all)]
#[resource(read_all)]
struct AuthResource;
#[derive(Debug, Deserialize, Clone)]
@ -143,7 +154,7 @@ struct AuthData {
exp: u64
}
#[rest_read_all(AuthResource)]
#[read_all(AuthResource)]
fn read_all(auth : &AuthStatus<AuthData>) -> Success<String> {
format!("{:?}", auth).into()
}

View file

@ -2,67 +2,85 @@
#![warn(missing_debug_implementations, rust_2018_idioms)]
#![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`
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.
# 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
# #[macro_use] extern crate gotham_restful_derive;
# use gotham::{router::builder::*, state::State};
# use gotham_restful::{DrawResources, Resource, Success};
# use gotham::router::builder::*;
# use gotham_restful::*;
# use serde::{Deserialize, Serialize};
/// Our RESTful Resource.
/// Our RESTful resource.
#[derive(Resource)]
#[rest_resource(read_all)]
struct UsersResource;
#[resource(read)]
struct FooResource;
/// Our return type.
#[derive(Deserialize, Serialize)]
/// The return type of the foo read method.
#[derive(Serialize)]
# #[derive(OpenapiType)]
struct User {
id: i64,
username: String,
email: String
struct Foo {
id: u64
}
/// Our handler method.
#[rest_read_all(UsersResource)]
fn read_all(_state: &mut State) -> Success<Vec<User>> {
vec![User {
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");
}));
/// The foo read method handler.
#[read(FooResource)]
fn read(id: u64) -> Success<Foo> {
Foo { id }.into()
}
# 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
# #[macro_use] extern crate gotham_restful_derive;
# use gotham::{router::builder::*, state::State};
# use gotham_restful::{DrawResources, Mime, Raw, Resource, Success};
# use gotham::router::builder::*;
# use gotham_restful::*;
# use serde::{Deserialize, Serialize};
#[derive(Resource)]
#[rest_resource(create)]
#[resource(create)]
struct ImageResource;
#[derive(FromBody, RequestBody)]
@ -72,8 +90,8 @@ struct RawImage {
content_type: Mime
}
#[rest_create(ImageResource)]
fn create(_state : &mut State, body : RawImage) -> Raw<Vec<u8>> {
#[create(ImageResource)]
fn create(body : RawImage) -> Raw<Vec<u8>> {
Raw::new(body.content, body.content_type)
}
# 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
[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).
## Authentication Feature
- Enabling the `openapi` feature might break code ([#4](https://gitlab.com/msrd0/gotham-restful/issues/4))
- For `chrono`'s `DateTime` types, the format is `date-time` instead of `datetime` ([openapiv3#14](https://github.com/glademiller/openapiv3/pull/14))
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
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
@ -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)
[example]: https://gitlab.com/msrd0/gotham-restful/tree/master/example
[gotham]: https://gotham.rs/
[serde]: https://github.com/serde-rs/serde#serde-----
[serde_json]: https://github.com/serde-rs/json#serde-json----
[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----
[`QueryStringExtractor`]: ../gotham/extractor/trait.QueryStringExtractor.html
[`RequestBody`]: trait.RequestBody.html
[`State`]: ../gotham/state/struct.State.html
*/
// weird proc macro issue

View file

@ -29,12 +29,13 @@ look something like this (assuming the `auth` feature is enabled):
# use serde::Deserialize;
#
# #[derive(Resource)]
# #[resource(read_all)]
# struct MyResource;
#
# #[derive(Clone, Deserialize)]
# struct MyAuthData { exp : u64 }
#
#[rest_read_all(MyResource)]
#[read_all(MyResource)]
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthSuccess<NoContent> {
let auth_data = match auth {
AuthStatus::Authenticated(data) => data,
@ -88,12 +89,13 @@ look something like this (assuming the `auth` feature is enabled):
# use std::io;
#
# #[derive(Resource)]
# #[resource(read_all)]
# struct MyResource;
#
# #[derive(Clone, Deserialize)]
# struct MyAuthData { exp : u64 }
#
#[rest_read_all(MyResource)]
#[read_all(MyResource)]
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthResult<NoContent, io::Error> {
let auth_data = match auth {
AuthStatus::Authenticated(data) => data,

View file

@ -22,9 +22,10 @@ the function attributes:
# use gotham_restful::*;
#
# #[derive(Resource)]
# #[resource(read_all)]
# struct MyResource;
#
#[rest_read_all(MyResource)]
#[read_all(MyResource)]
fn read_all(_state: &mut State) {
// do something
}

View file

@ -25,6 +25,7 @@ Usage example:
# use serde::{Deserialize, Serialize};
#
# #[derive(Resource)]
# #[resource(read_all)]
# struct MyResource;
#
#[derive(Deserialize, Serialize)]
@ -33,7 +34,7 @@ struct MyResponse {
message: &'static str
}
#[rest_read_all(MyResource)]
#[read_all(MyResource)]
fn read_all(_state: &mut State) -> Success<MyResponse> {
let res = MyResponse { message: "I'm always happy" };
res.into()