Crate gotham_restful
source · [−]Expand description
This crate is an extension to the popular gotham web framework for Rust. It allows you to create resources with assigned methods that aim to be a more convenient way of creating handlers for requests.
Features
- Automatically parse JSON request and produce response bodies
- Allow using raw request and response bodies
- Convenient macros to create responses that can be registered with gotham’s router
- Auto-Generate an OpenAPI specification for your API
- Manage CORS headers so you don’t have to
- Manage Authentication with JWT
- Integrate diesel connection pools for easy database integration
Safety
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
Assuming you assign /foobar
to your resource, you can implement the following methods:
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:
/// Our RESTful resource.
#[derive(Resource)]
#[resource(read)]
struct FooResource;
/// The return type of the foo read method.
#[derive(Serialize)]
struct Foo {
id: u64
}
/// The foo read method handler.
#[read(FooResource)]
fn read(id: u64) -> Success<Foo> {
Foo { id }.into()
}
Arguments
Some methods require arguments. Those should be
- id Should be a deserializable json-primitive like
i64
orString
. - 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 implementQueryStringExtractor
.
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:
#[derive(Resource)]
#[resource(create)]
struct ImageResource;
#[derive(FromBody, RequestBody)]
#[supported_types(mime::IMAGE_GIF, mime::IMAGE_JPEG, mime::IMAGE_PNG)]
struct RawImage {
content: Vec<u8>,
content_type: Mime
}
#[create(ImageResource)]
fn create(body : RawImage) -> Raw<Vec<u8>> {
Raw::new(body.content, body.content_type)
}
Features
To make life easier for common use-cases, this create offers a few features that might be helpful when you implement your web server. The complete feature list is
auth
Advanced JWT middlewarechrono
openapi support for chrono typescors
CORS handling for all method handlersdatabase
diesel middleware supporterrorlog
log errors returned from method handlersopenapi
router additions to generate an openapi specuuid
openapi support for uuid
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
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:
#[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");
}));
}
CORS Feature
The cors feature allows an easy usage of this web server from other origins. By default, only
the Access-Control-Allow-Methods
header is touched. To change the behaviour, add your desired
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:
#[derive(Resource)]
#[resource(read_all)]
struct FooResource;
#[read_all(FooResource)]
fn read_all() {
// your handler
}
fn main() {
let cors = CorsConfig {
origin: Origin::Copy,
headers: vec![CONTENT_TYPE],
max_age: 0,
credentials: true
};
let (chain, pipelines) = single_pipeline(new_pipeline().add(cors).build());
gotham::start("127.0.0.1:8080", build_router(chain, pipelines, |route| {
route.resource::<FooResource>("foo");
}));
}
The cors feature can also be used for non-resource handlers. Take a look at CorsRoute
for an example.
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:
#[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");
}));
}
OpenAPI Feature
The OpenAPI feature is probably the most powerful one of this crate. Definitely read this section carefully both as a binary as well as a library author to avoid unwanted suprises.
In order to automatically create an openapi specification, gotham-restful needs knowledge over
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:
#[derive(Resource)]
#[resource(read_all)]
struct FooResource;
#[derive(OpenapiType, Serialize)]
struct Foo {
bar: String
}
#[read_all(FooResource)]
fn read_all() -> Success<Foo> {
Foo { bar: "Hello World".to_owned() }.into()
}
fn main() {
gotham::start("127.0.0.1:8080", build_simple_router(|route| {
let info = OpenapiInfo {
title: "My Foo API".to_owned(),
version: "0.1.0".to_owned(),
urls: vec!["https://example.org/foo/api/v1".to_owned()]
};
route.with_openapi(info, |mut route| {
route.resource::<FooResource>("foo");
route.get_openapi("openapi");
});
}));
}
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.
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
[features]
openapi = ["gotham-restful/openapi"]
into your libraries Cargo.toml
and use the following for all types used with handlers:
#[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.
License
Licensed under your option of:
Re-exports
pub use gotham;
pub use gotham::hyper::header::HeaderName;
pub use gotham::hyper::StatusCode;
pub use gotham::state::FromState;
pub use gotham::state::State;
pub use mime::Mime;
pub use result::AuthError::Forbidden;
Structs
This is the auth middleware. To use it, first make sure you have the auth
feature enabled. Then
simply add it to your pipeline and request it inside your handler:
Contains the various validations that are applied after decoding a JWT.
This is the configuration that the CORS handler will follow. Its default configuration is basically not to touch any responses, resulting in the browser’s default behaviour.
This is the return type of a resource that doesn’t actually return something. It will result in a 204 No Content answer by default. You don’t need to use this type directly if using the function attributes:
This struct needs to be available for every type that can be part of an OpenAPI Spec. It is already implemented for primitive types, String, Vec, Option and the like. To have it available for your type, simply derive from OpenapiType.
This type can be used both as a raw request body, as well as as a raw response. However, all types of request bodies are accepted by this type. It is therefore recommended to derive your own type from RequestBody and only use this when you need to return a raw response. This is a usage example that simply returns its body:
A response, used to create the final gotham response from.
An AuthHandler returning always the same secret. See AuthMiddleware for a usage example.
Enums
This is an error type that always yields a 403 Forbidden response. This type is best used in combination with AuthSuccess or AuthResult.
This is an error type that either yields a 403 Forbidden respone if produced from an authentication error, or delegates to another error type. This type is best used with AuthResult.
The source of the authentication token in the request.
The authentication status returned by the auth middleware for each request.
Specify the allowed origins of the request. It is up to the browser to check the validity of the origin. This, when sent to the browser, will indicate whether or not the request’s origin was allowed to make the request.
Traits
This trait will help the auth middleware to determine the validity of an authentication token.
Add CORS routing for your path. This is required for handling preflight requests.
This trait allows to draw routes within an resource. Use this only inside the Resource::setup method.
This trait adds the resource
method to gotham’s routing. It allows you to register
any RESTful Resource with a path.
This trait should be implemented for every type that can be built from an HTTP request body plus its media type.
This trait adds the get_openapi
method to an OpenAPI-aware router.
This trait needs to be implemented by every type that is being used in the OpenAPI Spec. It gives access to the OpenapiSchema of this type. It is provided for primitive types, String and the like. For use on your own types, there is a derive macro:
A type that can be used inside a request body. Implemented for every type that is deserializable
with serde. If the openapi
feature is used, it must also be of type OpenapiType.
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.
The change ResourceMethod.
The change_all ResourceMethod.
The create ResourceMethod.
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.
A common trait for every resource method. It defines the return type as well as some general information about a resource method.
The read ResourceMethod.
The read_all ResourceMethod.
The remove ResourceMethod.
The remove_all ResourceMethod.
A trait provided to convert a resource’s result to json.
The search ResourceMethod.
A type that can be used inside a response body. Implemented for every type that is
serializable with serde. If the openapi
feature is used, it must also be of type
OpenapiType.
This trait adds the with_openapi
method to gotham’s routing. It turns the default
router into one that will only allow RESTful resources, but record them and generate
an OpenAPI specification on request.
Functions
Handle CORS for a non-preflight request. This means manipulating the res
HTTP headers so that
the response is aligned with the state
’s CorsConfig.
Type Definitions
This return type can be used to map another ResourceResult that can only be returned if the client is authenticated. Otherwise, an empty 403 Forbidden response will be issued.
This return type can be used to map another ResourceResult that can only be returned if the client is authenticated. Otherwise, an empty 403 Forbidden response will be issued.