diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1f8e1dd..1e09c92 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,8 +2,21 @@ stages: - check -check: +check-none: stage: check image: msrd0/rust-pq script: - - cargo check + - cargo check --all --no-default-features + +check-all: + stage: check + image: msrd0/rust-pq + script: + - cargo check --all --all-features + +readme: + stage: check + image: msrd0/rust:alpine-readme + script: + - cargo readme -r gotham_restful -t ../README.tpl >README.md.new + - diff README.md README.md.new diff --git a/README.md b/README.md new file mode 100644 index 0000000..70082d3 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# gotham_restful [![Build Status](https://gitlab.com/msrd0/gotham-restful/badges/master/build.svg)](https://gitlab.com/msrd0/gotham-restful/commits/master) + +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. + +## Usage + +To use this crate, add the following to your `Cargo.toml`: + +```toml +[dependencies] +gotham_restful = "0.0.1" +``` + +A basic server with only one resource, handling a simple `GET` request, could look like this: + +```rust +# +/// Our RESTful Resource. +#[derive(Resource)] +#[rest_resource(read_all)] +struct UsersResource; + +/// Our return type. +#[derive(Deserialize, Serialize)] +struct User { + id: i64, + username: String, + email: String +} + +/// Our handler method. +#[rest_read_all(UsersResource)] +fn read_all(_state: &mut State) -> Success> { + 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::("users"); + })); +} +``` + +Look at the [example] for more methods and usage with the `openapi` feature. + +## License + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THE ECLIPSE
+PUBLIC LICENSE VERSION 2.0. ANY USE, REPRODUCTION OR DISTRIBUTION
+OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS LICENSE. + + +[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---- diff --git a/README.tpl b/README.tpl new file mode 100644 index 0000000..3f7c721 --- /dev/null +++ b/README.tpl @@ -0,0 +1,3 @@ +# {{crate}} {{badges}} + +{{readme}} diff --git a/gotham_restful/src/lib.rs b/gotham_restful/src/lib.rs index 47d0a50..594c938 100644 --- a/gotham_restful/src/lib.rs +++ b/gotham_restful/src/lib.rs @@ -1,8 +1,79 @@ +/*! +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. + +# Usage + +To use this crate, add the following to your `Cargo.toml`: + +```toml +[dependencies] +gotham_restful = "0.0.1" +``` + +A basic server with only one resource, handling a simple `GET` request, 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 serde::{Deserialize, Serialize}; +# +/// Our RESTful Resource. +#[derive(Resource)] +#[rest_resource(read_all)] +struct UsersResource; + +/// Our return type. +#[derive(Deserialize, Serialize)] +# #[derive(OpenapiType)] +struct User { + id: i64, + username: String, + email: String +} + +/// Our handler method. +#[rest_read_all(UsersResource)] +fn read_all(_state: &mut State) -> Success> { + 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::("users"); + })); +} +``` + +Look at the [example] for more methods and usage with the `openapi` feature. + +# License + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THE ECLIPSE
+PUBLIC LICENSE VERSION 2.0. ANY USE, REPRODUCTION OR DISTRIBUTION
+OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS LICENSE. + + +[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---- +*/ + #[macro_use] extern crate gotham_derive; #[macro_use] extern crate serde; pub use hyper::StatusCode; -use serde::{de::DeserializeOwned, Serialize}; pub use gotham_restful_derive::*; @@ -17,7 +88,7 @@ pub mod export } #[cfg(feature = "openapi")] -pub mod openapi; +mod openapi; #[cfg(feature = "openapi")] pub use openapi::{ router::{GetOpenapi, OpenapiRouter}, @@ -49,29 +120,5 @@ pub use routing::{DrawResources, DrawResourceRoutes}; #[cfg(feature = "openapi")] pub use routing::WithOpenapi; - -/// A type that can be used inside a request or response body. Implemented for every type -/// that is serializable with serde, however, it is recommended to use the rest_struct! -/// macro to create one. -#[cfg(not(feature = "openapi"))] -pub trait ResourceType : DeserializeOwned + Serialize -{ -} - -#[cfg(not(feature = "openapi"))] -impl ResourceType for T -{ -} - -/// A type that can be used inside a request or response body. Implemented for every type -/// that is serializable with serde, however, it is recommended to use the rest_struct! -/// macro to create one. -#[cfg(feature = "openapi")] -pub trait ResourceType : OpenapiType + DeserializeOwned + Serialize -{ -} - -#[cfg(feature = "openapi")] -impl ResourceType for T -{ -} +mod types; +pub use types::ResourceType; diff --git a/gotham_restful/src/openapi/router.rs b/gotham_restful/src/openapi/router.rs index 86d0cd5..cd40590 100644 --- a/gotham_restful/src/openapi/router.rs +++ b/gotham_restful/src/openapi/router.rs @@ -27,6 +27,12 @@ use openapiv3::{ use serde::de::DeserializeOwned; use std::panic::RefUnwindSafe; +/** +This type is required to build routes while adding them to the generated OpenAPI Spec at the +same time. There is no need to use this type directly. See [`WithOpenapi`] on how to do this. + +[`WithOpenapi`]: trait.WithOpenapi.html +*/ pub struct OpenapiRouter(OpenAPI); impl OpenapiRouter @@ -159,6 +165,7 @@ impl Handler for OpenapiHandler } } +/// This trait adds the `get_openapi` method to an OpenAPI-aware router. pub trait GetOpenapi { fn get_openapi(&mut self, path : &str); diff --git a/gotham_restful/src/openapi/types.rs b/gotham_restful/src/openapi/types.rs index fc321ea..d6f2300 100644 --- a/gotham_restful/src/openapi/types.rs +++ b/gotham_restful/src/openapi/types.rs @@ -10,18 +10,32 @@ use openapiv3::{ #[cfg(feature = "chrono")] use openapiv3::{StringFormat, VariantOrUnknownOrEmpty}; +/** +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`]. + +[`OpenapiType`]: trait.OpenapiType.html +*/ #[derive(Debug, Clone, PartialEq)] pub struct OpenapiSchema { /// The name of this schema. If it is None, the schema will be inlined. pub name : Option, + /// Whether this particular schema is nullable. Note that there is no guarantee that this will + /// make it into the final specification, it might just be interpreted as a hint to make it + /// an optional parameter. pub nullable : bool, + /// The actual OpenAPI schema. pub schema : SchemaKind, + /// Other schemas that this schema depends on. They will be included in the final OpenAPI Spec + /// along with this schema. pub dependencies : IndexMap } impl OpenapiSchema { + /// Create a new schema that has no name. pub fn new(schema : SchemaKind) -> Self { Self { @@ -31,7 +45,8 @@ impl OpenapiSchema dependencies: IndexMap::new() } } - + + /// Convert this schema to an `openapiv3::Schema` that can be serialized to the OpenAPI Spec. pub fn into_schema(self) -> Schema { Schema { @@ -52,6 +67,20 @@ impl OpenapiSchema } } +/** +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: + +``` +# #[macro_use] extern crate gotham_restful_derive; +# +#[derive(OpenapiType)] +struct MyResponse { + message: String +} +``` +*/ pub trait OpenapiType { fn schema() -> OpenapiSchema; @@ -115,7 +144,7 @@ macro_rules! str_types { (format = $format:ident, $($str_ty:ty),*) => {$( impl OpenapiType for $str_ty { - fn to_schema() -> OpenapiSchema + fn schema() -> OpenapiSchema { OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { format: VariantOrUnknownOrEmpty::Item(StringFormat::$format), diff --git a/gotham_restful/src/result.rs b/gotham_restful/src/result.rs index d0b6229..fdd601b 100644 --- a/gotham_restful/src/result.rs +++ b/gotham_restful/src/result.rs @@ -68,7 +68,31 @@ impl ResourceResult for Result } } -/// This can be returned from a resource when there is no cause of an error. +/** +This can be returned from a resource when there is no cause of an error. For example: + +``` +# #[macro_use] extern crate gotham_restful_derive; +# use gotham::state::State; +# use gotham_restful::Success; +# use serde::{Deserialize, Serialize}; +# +# #[derive(Resource)] +# struct MyResource; +# +#[derive(Deserialize, Serialize)] +# #[derive(OpenapiType)] +struct MyResponse { + message: String +} + +#[rest_read_all(MyResource)] +fn read_all(_state: &mut State) -> Success { + let res = MyResponse { message: "I'm always happy".to_string() }; + res.into() +} +``` +*/ pub struct Success(T); impl From for Success @@ -93,7 +117,24 @@ impl ResourceResult for Success } } -/// This can be returned from a resource when there is no content to send. +/** +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: + +``` +# #[macro_use] extern crate gotham_restful_derive; +# use gotham::state::State; +# +# #[derive(Resource)] +# struct MyResource; +# +#[rest_read_all(MyResource)] +fn read_all(_state: &mut State) { + // do something +} +``` +*/ #[derive(Default)] pub struct NoContent; @@ -107,17 +148,20 @@ impl From<()> for NoContent impl ResourceResult for NoContent { + /// This will always be a _204 No Content_ together with an empty string. fn to_json(&self) -> Result<(StatusCode, String), SerdeJsonError> { - Ok((StatusCode::NO_CONTENT, "".to_string())) + Ok((Self::default_status(), "".to_string())) } + /// Returns the schema of the `()` type. #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { <()>::schema() } + /// This will always be a _204 No Content_ #[cfg(feature = "openapi")] fn default_status() -> StatusCode { diff --git a/gotham_restful/src/types.rs b/gotham_restful/src/types.rs new file mode 100644 index 0000000..f354e92 --- /dev/null +++ b/gotham_restful/src/types.rs @@ -0,0 +1,30 @@ +#[cfg(feature = "openapi")] +use crate::OpenapiType; + +use serde::{de::DeserializeOwned, Serialize}; + +/// A type that can be used inside a request or response body. Implemented for every type +/// that is serializable with serde, however, it is recommended to use the rest_struct! +/// macro to create one. +#[cfg(not(feature = "openapi"))] +pub trait ResourceType : DeserializeOwned + Serialize +{ +} + +#[cfg(not(feature = "openapi"))] +impl ResourceType for T +{ +} + +/// A type that can be used inside a request or response body. Implemented for every type +/// that is serializable with serde, however, it is recommended to use the rest_struct! +/// macro to create one. +#[cfg(feature = "openapi")] +pub trait ResourceType : OpenapiType + DeserializeOwned + Serialize +{ +} + +#[cfg(feature = "openapi")] +impl ResourceType for T +{ +}