1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-04-20 06:54:46 +00:00

update to gotham 0.5 and start using rustfmt

This commit is contained in:
Dominic 2020-09-15 15:10:41 +02:00
parent 5317e50961
commit d55b0897e9
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
39 changed files with 1798 additions and 2095 deletions

View file

@ -1,29 +1,24 @@
use crate::{AuthError, Forbidden, HeaderName};
use cookie::CookieJar;
use futures_util::{future, future::{FutureExt, TryFutureExt}};
use futures_util::{
future,
future::{FutureExt, TryFutureExt}
};
use gotham::{
handler::HandlerFuture,
hyper::header::{AUTHORIZATION, HeaderMap},
hyper::header::{HeaderMap, AUTHORIZATION},
middleware::{Middleware, NewMiddleware},
state::{FromState, State}
};
use jsonwebtoken::{
errors::ErrorKind,
DecodingKey
};
use jsonwebtoken::{errors::ErrorKind, DecodingKey};
use serde::de::DeserializeOwned;
use std::{
marker::PhantomData,
panic::RefUnwindSafe,
pin::Pin
};
use std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin};
pub use jsonwebtoken::Validation as AuthValidation;
/// The authentication status returned by the auth middleware for each request.
#[derive(Debug, StateData)]
pub enum AuthStatus<T : Send + 'static>
{
pub enum AuthStatus<T: Send + 'static> {
/// The auth status is unknown.
Unknown,
/// The request has been performed without any kind of authentication.
@ -38,10 +33,9 @@ pub enum AuthStatus<T : Send + 'static>
impl<T> Clone for AuthStatus<T>
where
T : Clone + Send + 'static
T: Clone + Send + 'static
{
fn clone(&self) -> Self
{
fn clone(&self) -> Self {
match self {
Self::Unknown => Self::Unknown,
Self::Unauthenticated => Self::Unauthenticated,
@ -52,16 +46,10 @@ where
}
}
impl<T> Copy for AuthStatus<T>
where
T : Copy + Send + 'static
{
}
impl<T> Copy for AuthStatus<T> where T: Copy + Send + 'static {}
impl<T : Send + 'static> AuthStatus<T>
{
pub fn ok(self) -> Result<T, AuthError>
{
impl<T: Send + 'static> AuthStatus<T> {
pub fn ok(self) -> Result<T, AuthError> {
match self {
Self::Authenticated(data) => Ok(data),
_ => Err(Forbidden)
@ -71,8 +59,7 @@ impl<T : Send + 'static> AuthStatus<T>
/// The source of the authentication token in the request.
#[derive(Clone, Debug, StateData)]
pub enum AuthSource
{
pub enum AuthSource {
/// Take the token from a cookie with the given name.
Cookie(String),
/// Take the token from a header with the given name.
@ -100,36 +87,29 @@ impl<T> AuthHandler<T> for CustomAuthHandler {
}
```
*/
pub trait AuthHandler<Data>
{
pub trait AuthHandler<Data> {
/// Return the SHA256-HMAC secret used to verify the JWT token.
fn jwt_secret<F : FnOnce() -> Option<Data>>(&self, state : &mut State, decode_data : F) -> Option<Vec<u8>>;
fn jwt_secret<F: FnOnce() -> Option<Data>>(&self, state: &mut State, decode_data: F) -> Option<Vec<u8>>;
}
/// An `AuthHandler` returning always the same secret. See `AuthMiddleware` for a usage example.
#[derive(Clone, Debug)]
pub struct StaticAuthHandler
{
secret : Vec<u8>
pub struct StaticAuthHandler {
secret: Vec<u8>
}
impl StaticAuthHandler
{
pub fn from_vec(secret : Vec<u8>) -> Self
{
impl StaticAuthHandler {
pub fn from_vec(secret: Vec<u8>) -> Self {
Self { secret }
}
pub fn from_array(secret : &[u8]) -> Self
{
pub fn from_array(secret: &[u8]) -> Self {
Self::from_vec(secret.to_vec())
}
}
impl<T> AuthHandler<T> for StaticAuthHandler
{
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, _decode_data : F) -> Option<Vec<u8>>
{
impl<T> AuthHandler<T> for StaticAuthHandler {
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, _decode_data: F) -> Option<Vec<u8>> {
Some(self.secret.clone())
}
}
@ -173,19 +153,18 @@ fn main() {
```
*/
#[derive(Debug)]
pub struct AuthMiddleware<Data, Handler>
{
source : AuthSource,
validation : AuthValidation,
handler : Handler,
_data : PhantomData<Data>
pub struct AuthMiddleware<Data, Handler> {
source: AuthSource,
validation: AuthValidation,
handler: Handler,
_data: PhantomData<Data>
}
impl<Data, Handler> Clone for AuthMiddleware<Data, Handler>
where Handler : Clone
where
Handler: Clone
{
fn clone(&self) -> Self
{
fn clone(&self) -> Self {
Self {
source: self.source.clone(),
validation: self.validation.clone(),
@ -197,11 +176,10 @@ where Handler : Clone
impl<Data, Handler> AuthMiddleware<Data, Handler>
where
Data : DeserializeOwned + Send,
Handler : AuthHandler<Data> + Default
Data: DeserializeOwned + Send,
Handler: AuthHandler<Data> + Default
{
pub fn from_source(source : AuthSource) -> Self
{
pub fn from_source(source: AuthSource) -> Self {
Self {
source,
validation: Default::default(),
@ -213,11 +191,10 @@ where
impl<Data, Handler> AuthMiddleware<Data, Handler>
where
Data : DeserializeOwned + Send,
Handler : AuthHandler<Data>
Data: DeserializeOwned + Send,
Handler: AuthHandler<Data>
{
pub fn new(source : AuthSource, validation : AuthValidation, handler : Handler) -> Self
{
pub fn new(source: AuthSource, validation: AuthValidation, handler: Handler) -> Self {
Self {
source,
validation,
@ -225,59 +202,52 @@ where
_data: Default::default()
}
}
fn auth_status(&self, state : &mut State) -> AuthStatus<Data>
{
fn auth_status(&self, state: &mut State) -> AuthStatus<Data> {
// extract the provided token, if any
let token = match &self.source {
AuthSource::Cookie(name) => {
CookieJar::try_borrow_from(&state)
.and_then(|jar| jar.get(&name))
.map(|cookie| cookie.value().to_owned())
},
AuthSource::Header(name) => {
HeaderMap::try_borrow_from(&state)
.and_then(|map| map.get(name))
.and_then(|header| header.to_str().ok())
.map(|value| value.to_owned())
},
AuthSource::AuthorizationHeader => {
HeaderMap::try_borrow_from(&state)
.and_then(|map| map.get(AUTHORIZATION))
.and_then(|header| header.to_str().ok())
.and_then(|value| value.split_whitespace().nth(1))
.map(|value| value.to_owned())
}
AuthSource::Cookie(name) => CookieJar::try_borrow_from(&state)
.and_then(|jar| jar.get(&name))
.map(|cookie| cookie.value().to_owned()),
AuthSource::Header(name) => HeaderMap::try_borrow_from(&state)
.and_then(|map| map.get(name))
.and_then(|header| header.to_str().ok())
.map(|value| value.to_owned()),
AuthSource::AuthorizationHeader => HeaderMap::try_borrow_from(&state)
.and_then(|map| map.get(AUTHORIZATION))
.and_then(|header| header.to_str().ok())
.and_then(|value| value.split_whitespace().nth(1))
.map(|value| value.to_owned())
};
// unauthed if no token
let token = match token {
Some(token) => token,
None => return AuthStatus::Unauthenticated
};
// get the secret from the handler, possibly decoding claims ourselves
let secret = self.handler.jwt_secret(state, || {
let b64 = token.split('.').nth(1)?;
let raw = base64::decode_config(b64, base64::URL_SAFE_NO_PAD).ok()?;
serde_json::from_slice(&raw).ok()?
});
// unknown if no secret
let secret = match secret {
Some(secret) => secret,
None => return AuthStatus::Unknown
};
// validate the token
let data : Data = match jsonwebtoken::decode(&token, &DecodingKey::from_secret(&secret), &self.validation) {
let data: Data = match jsonwebtoken::decode(&token, &DecodingKey::from_secret(&secret), &self.validation) {
Ok(data) => data.claims,
Err(e) => match dbg!(e.into_kind()) {
ErrorKind::ExpiredSignature => return AuthStatus::Expired,
_ => return AuthStatus::Invalid
}
};
// we found a valid token
AuthStatus::Authenticated(data)
}
@ -285,20 +255,20 @@ where
impl<Data, Handler> Middleware for AuthMiddleware<Data, Handler>
where
Data : DeserializeOwned + Send + 'static,
Handler : AuthHandler<Data>
Data: DeserializeOwned + Send + 'static,
Handler: AuthHandler<Data>
{
fn call<Chain>(self, mut state : State, chain : Chain) -> Pin<Box<HandlerFuture>>
fn call<Chain>(self, mut state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
where
Chain : FnOnce(State) -> Pin<Box<HandlerFuture>>
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>
{
// put the source in our state, required for e.g. openapi
state.put(self.source.clone());
// put the status in our state
let status = self.auth_status(&mut state);
state.put(status);
// call the rest of the chain
chain(state).and_then(|(state, res)| future::ok((state, res))).boxed()
}
@ -306,45 +276,40 @@ where
impl<Data, Handler> NewMiddleware for AuthMiddleware<Data, Handler>
where
Self : Clone + Middleware + Sync + RefUnwindSafe
Self: Clone + Middleware + Sync + RefUnwindSafe
{
type Instance = Self;
fn new_middleware(&self) -> Result<Self::Instance, std::io::Error>
{
let c : Self = self.clone();
fn new_middleware(&self) -> Result<Self::Instance, std::io::Error> {
let c: Self = self.clone();
Ok(c)
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
use cookie::Cookie;
use std::fmt::Debug;
// 256-bit random string
const JWT_SECRET : &'static [u8; 32] = b"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW";
const JWT_SECRET: &'static [u8; 32] = b"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW";
// some known tokens
const VALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk";
const EXPIRED_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjE1Nzc4MzcxMDB9.eV1snaGLYrJ7qUoMk74OvBY3WUU9M0Je5HTU2xtX1v0";
const INVALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9";
#[derive(Debug, Deserialize, PartialEq)]
struct TestData
{
iss : String,
sub : String,
iat : u64,
exp : u64
struct TestData {
iss: String,
sub: String,
iat: u64,
exp: u64
}
impl Default for TestData
{
fn default() -> Self
{
impl Default for TestData {
fn default() -> Self {
Self {
iss: "msrd0".to_owned(),
sub: "gotham-restful".to_owned(),
@ -353,20 +318,17 @@ mod test
}
}
}
#[derive(Default)]
struct NoneAuthHandler;
impl<T> AuthHandler<T> for NoneAuthHandler
{
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, _decode_data : F) -> Option<Vec<u8>>
{
impl<T> AuthHandler<T> for NoneAuthHandler {
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, _decode_data: F) -> Option<Vec<u8>> {
None
}
}
#[test]
fn test_auth_middleware_none_secret()
{
fn test_auth_middleware_none_secret() {
let middleware = <AuthMiddleware<TestData, NoneAuthHandler>>::from_source(AuthSource::AuthorizationHeader);
State::with_new(|mut state| {
let mut headers = HeaderMap::new();
@ -375,22 +337,21 @@ mod test
middleware.auth_status(&mut state);
});
}
#[derive(Default)]
struct TestAssertingHandler;
impl<T> AuthHandler<T> for TestAssertingHandler
where T : Debug + Default + PartialEq
where
T: Debug + Default + PartialEq
{
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, decode_data : F) -> Option<Vec<u8>>
{
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, decode_data: F) -> Option<Vec<u8>> {
assert_eq!(decode_data(), Some(T::default()));
Some(JWT_SECRET.to_vec())
}
}
#[test]
fn test_auth_middleware_decode_data()
{
fn test_auth_middleware_decode_data() {
let middleware = <AuthMiddleware<TestData, TestAssertingHandler>>::from_source(AuthSource::AuthorizationHeader);
State::with_new(|mut state| {
let mut headers = HeaderMap::new();
@ -399,16 +360,16 @@ mod test
middleware.auth_status(&mut state);
});
}
fn new_middleware<T>(source : AuthSource) -> AuthMiddleware<T, StaticAuthHandler>
where T : DeserializeOwned + Send
fn new_middleware<T>(source: AuthSource) -> AuthMiddleware<T, StaticAuthHandler>
where
T: DeserializeOwned + Send
{
AuthMiddleware::new(source, Default::default(), StaticAuthHandler::from_array(JWT_SECRET))
}
#[test]
fn test_auth_middleware_no_token()
{
fn test_auth_middleware_no_token() {
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
State::with_new(|mut state| {
let status = middleware.auth_status(&mut state);
@ -418,10 +379,9 @@ mod test
};
});
}
#[test]
fn test_auth_middleware_expired_token()
{
fn test_auth_middleware_expired_token() {
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
State::with_new(|mut state| {
let mut headers = HeaderMap::new();
@ -434,10 +394,9 @@ mod test
};
});
}
#[test]
fn test_auth_middleware_invalid_token()
{
fn test_auth_middleware_invalid_token() {
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
State::with_new(|mut state| {
let mut headers = HeaderMap::new();
@ -450,10 +409,9 @@ mod test
};
});
}
#[test]
fn test_auth_middleware_auth_header_token()
{
fn test_auth_middleware_auth_header_token() {
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
State::with_new(|mut state| {
let mut headers = HeaderMap::new();
@ -466,10 +424,9 @@ mod test
};
})
}
#[test]
fn test_auth_middleware_header_token()
{
fn test_auth_middleware_header_token() {
let header_name = "x-znoiprwmvfexju";
let middleware = new_middleware::<TestData>(AuthSource::Header(HeaderName::from_static(header_name)));
State::with_new(|mut state| {
@ -483,10 +440,9 @@ mod test
};
})
}
#[test]
fn test_auth_middleware_cookie_token()
{
fn test_auth_middleware_cookie_token() {
let cookie_name = "znoiprwmvfexju";
let middleware = new_middleware::<TestData>(AuthSource::Cookie(cookie_name.to_owned()));
State::with_new(|mut state| {

View file

@ -4,22 +4,19 @@ use gotham::{
helpers::http::response::create_empty_response,
hyper::{
header::{
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,
ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY,
HeaderMap, HeaderName, HeaderValue
HeaderMap, HeaderName, HeaderValue, ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE,
ACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY
},
Body, Method, Response, StatusCode
},
middleware::Middleware,
pipeline::chain::PipelineHandleChain,
router::builder::*,
state::{FromState, State},
state::{FromState, State}
};
use itertools::Itertools;
use std::{
panic::RefUnwindSafe,
pin::Pin
};
use std::{panic::RefUnwindSafe, pin::Pin};
/**
Specify the allowed origins of the request. It is up to the browser to check the validity of the
@ -27,8 +24,7 @@ origin. This, when sent to the browser, will indicate whether or not the request
allowed to make the request.
*/
#[derive(Clone, Debug)]
pub enum Origin
{
pub enum Origin {
/// Do not send any `Access-Control-Allow-Origin` headers.
None,
/// Send `Access-Control-Allow-Origin: *`. Note that browser will not send credentials.
@ -39,19 +35,15 @@ pub enum Origin
Copy
}
impl Default for Origin
{
fn default() -> Self
{
impl Default for Origin {
fn default() -> Self {
Self::None
}
}
impl Origin
{
impl Origin {
/// Get the header value for the `Access-Control-Allow-Origin` header.
fn header_value(&self, state : &State) -> Option<HeaderValue>
{
fn header_value(&self, state: &State) -> Option<HeaderValue> {
match self {
Self::None => None,
Self::Star => Some("*".parse().unwrap()),
@ -126,23 +118,21 @@ gotham::start("127.0.0.1:8080", build_router((), pipeline_set, |route| {
[`State`]: ../gotham/state/struct.State.html
*/
#[derive(Clone, Debug, Default, NewMiddleware, StateData)]
pub struct CorsConfig
{
pub struct CorsConfig {
/// The allowed origins.
pub origin : Origin,
pub origin: Origin,
/// The allowed headers.
pub headers : Vec<HeaderName>,
pub headers: Vec<HeaderName>,
/// The amount of seconds that the preflight request can be cached.
pub max_age : u64,
pub max_age: u64,
/// Whether or not the request may be made with supplying credentials.
pub credentials : bool
pub credentials: bool
}
impl Middleware for CorsConfig
{
fn call<Chain>(self, mut state : State, chain : Chain) -> Pin<Box<HandlerFuture>>
impl Middleware for CorsConfig {
fn call<Chain>(self, mut state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
where
Chain : FnOnce(State) -> Pin<Box<HandlerFuture>>
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>
{
state.put(self);
chain(state)
@ -161,35 +151,31 @@ For further information on CORS, read https://developer.mozilla.org/en-US/docs/W
[`CorsConfig`]: ./struct.CorsConfig.html
*/
pub fn handle_cors(state : &State, res : &mut Response<Body>)
{
pub fn handle_cors(state: &State, res: &mut Response<Body>) {
let config = CorsConfig::try_borrow_from(state);
let headers = res.headers_mut();
// non-preflight requests require the Access-Control-Allow-Origin header
if let Some(header) = config.and_then(|cfg| cfg.origin.header_value(state))
{
if let Some(header) = config.and_then(|cfg| cfg.origin.header_value(state)) {
headers.insert(ACCESS_CONTROL_ALLOW_ORIGIN, header);
}
// if the origin is copied over, we should tell the browser by specifying the Vary header
if matches!(config.map(|cfg| &cfg.origin), Some(Origin::Copy))
{
if matches!(config.map(|cfg| &cfg.origin), Some(Origin::Copy)) {
let vary = headers.get(VARY).map(|vary| format!("{},Origin", vary.to_str().unwrap()));
headers.insert(VARY, vary.as_deref().unwrap_or("Origin").parse().unwrap());
}
// if we allow credentials, tell the browser
if config.map(|cfg| cfg.credentials).unwrap_or(false)
{
if config.map(|cfg| cfg.credentials).unwrap_or(false) {
headers.insert(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true".parse().unwrap());
}
}
/// Add CORS routing for your path. This is required for handling preflight requests.
///
///
/// Example:
///
///
/// ```rust,no_run
/// # use gotham::{hyper::{Body, Method, Response}, router::builder::*};
/// # use gotham_restful::*;
@ -206,16 +192,15 @@ pub fn handle_cors(state : &State, res : &mut Response<Body>)
/// ```
pub trait CorsRoute<C, P>
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
/// Handle a preflight request on `path` for `method`. To configure the behaviour, use
/// [`CorsConfig`](struct.CorsConfig.html).
fn cors(&mut self, path : &str, method : Method);
fn cors(&mut self, path: &str, method: Method);
}
fn cors_preflight_handler(state : State) -> (State, Response<Body>)
{
fn cors_preflight_handler(state: State) -> (State, Response<Body>) {
let config = CorsConfig::try_borrow_from(&state);
// prepare the response
@ -223,43 +208,40 @@ fn cors_preflight_handler(state : State) -> (State, Response<Body>)
let headers = res.headers_mut();
// copy the request method over to the response
let method = HeaderMap::borrow_from(&state).get(ACCESS_CONTROL_REQUEST_METHOD).unwrap().clone();
let method = HeaderMap::borrow_from(&state)
.get(ACCESS_CONTROL_REQUEST_METHOD)
.unwrap()
.clone();
headers.insert(ACCESS_CONTROL_ALLOW_METHODS, method);
// if we allow any headers, put them in
if let Some(hdrs) = config.map(|cfg| &cfg.headers)
{
if hdrs.len() > 0
{
if let Some(hdrs) = config.map(|cfg| &cfg.headers) {
if hdrs.len() > 0 {
// TODO do we want to return all headers or just those asked by the browser?
headers.insert(ACCESS_CONTROL_ALLOW_HEADERS, hdrs.iter().join(",").parse().unwrap());
}
}
// set the max age for the preflight cache
if let Some(age) = config.map(|cfg| cfg.max_age)
{
if let Some(age) = config.map(|cfg| cfg.max_age) {
headers.insert(ACCESS_CONTROL_MAX_AGE, age.into());
}
// make sure the browser knows that this request was based on the method
headers.insert(VARY, "Access-Control-Request-Method".parse().unwrap());
handle_cors(&state, &mut res);
(state, res)
}
impl<D, C, P> CorsRoute<C, P> for D
where
D : DrawRoutes<C, P>,
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
D: DrawRoutes<C, P>,
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn cors(&mut self, path : &str, method : Method)
{
fn cors(&mut self, path: &str, method: Method) {
let matcher = AccessControlRequestMethodMatcher::new(method);
self.options(path)
.extend_route_matcher(matcher)
.to(cors_preflight_handler);
self.options(path).extend_route_matcher(matcher).to(cors_preflight_handler);
}
}

View file

@ -263,7 +263,7 @@ 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");
@ -347,7 +347,7 @@ struct Foo;
# Examples
There is a lack of good examples, but there is currently a collection of code in the [example]
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
@ -370,9 +370,12 @@ Licensed under your option of:
// weird proc macro issue
extern crate self as gotham_restful;
#[macro_use] extern crate gotham_derive;
#[macro_use] extern crate log;
#[macro_use] extern crate serde;
#[macro_use]
extern crate gotham_derive;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde;
#[doc(no_inline)]
pub use gotham;
@ -388,15 +391,14 @@ pub use gotham_restful_derive::*;
/// Not public API
#[doc(hidden)]
pub mod export
{
pub mod export {
pub use futures_util::future::FutureExt;
pub use serde_json;
#[cfg(feature = "database")]
pub use gotham_middleware_diesel::Repo;
#[cfg(feature = "openapi")]
pub use indexmap::IndexMap;
#[cfg(feature = "openapi")]
@ -406,24 +408,12 @@ pub mod export
#[cfg(feature = "auth")]
mod auth;
#[cfg(feature = "auth")]
pub use auth::{
AuthHandler,
AuthMiddleware,
AuthSource,
AuthStatus,
AuthValidation,
StaticAuthHandler
};
pub use auth::{AuthHandler, AuthMiddleware, AuthSource, AuthStatus, AuthValidation, StaticAuthHandler};
#[cfg(feature = "cors")]
mod cors;
#[cfg(feature = "cors")]
pub use cors::{
handle_cors,
CorsConfig,
CorsRoute,
Origin
};
pub use cors::{handle_cors, CorsConfig, CorsRoute, Origin};
pub mod matcher;
@ -438,16 +428,8 @@ pub use openapi::{
mod resource;
pub use resource::{
Resource,
ResourceMethod,
ResourceReadAll,
ResourceRead,
ResourceSearch,
ResourceCreate,
ResourceChangeAll,
ResourceChange,
ResourceRemoveAll,
ResourceRemove
Resource, ResourceChange, ResourceChangeAll, ResourceCreate, ResourceMethod, ResourceRead, ResourceReadAll,
ResourceRemove, ResourceRemoveAll, ResourceSearch
};
mod response;
@ -455,22 +437,14 @@ pub use response::Response;
mod result;
pub use result::{
AuthError,
AuthError::Forbidden,
AuthErrorOrOther,
AuthResult,
AuthSuccess,
IntoResponseError,
NoContent,
Raw,
ResourceResult,
Success
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw,
ResourceResult, Success
};
mod routing;
pub use routing::{DrawResources, DrawResourceRoutes};
#[cfg(feature = "openapi")]
pub use routing::WithOpenapi;
pub use routing::{DrawResourceRoutes, DrawResources};
mod types;
pub use types::*;

View file

@ -1,13 +1,16 @@
use gotham::{
hyper::{header::{ACCESS_CONTROL_REQUEST_METHOD, HeaderMap}, Method, StatusCode},
hyper::{
header::{HeaderMap, ACCESS_CONTROL_REQUEST_METHOD},
Method, StatusCode
},
router::{non_match::RouteNonMatch, route::matcher::RouteMatcher},
state::{FromState, State}
};
/// A route matcher that checks whether the value of the `Access-Control-Request-Method` header matches the defined value.
///
///
/// Usage:
///
///
/// ```rust
/// # use gotham::{helpers::http::response::create_empty_response,
/// # hyper::{header::ACCESS_CONTROL_ALLOW_METHODS, Method, StatusCode},
@ -15,44 +18,38 @@ use gotham::{
/// # };
/// # use gotham_restful::matcher::AccessControlRequestMethodMatcher;
/// let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
///
///
/// # build_simple_router(|route| {
/// // use the matcher for your request
/// route.options("/foo")
/// .extend_route_matcher(matcher)
/// .to(|state| {
/// // we know that this is a CORS preflight for a PUT request
/// let mut res = create_empty_response(&state, StatusCode::NO_CONTENT);
/// res.headers_mut().insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap());
/// (state, res)
/// route.options("/foo").extend_route_matcher(matcher).to(|state| {
/// // we know that this is a CORS preflight for a PUT request
/// let mut res = create_empty_response(&state, StatusCode::NO_CONTENT);
/// res.headers_mut().insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap());
/// (state, res)
/// });
/// # });
/// ```
#[derive(Clone, Debug)]
pub struct AccessControlRequestMethodMatcher
{
method : Method
pub struct AccessControlRequestMethodMatcher {
method: Method
}
impl AccessControlRequestMethodMatcher
{
impl AccessControlRequestMethodMatcher {
/// Construct a new matcher that matches if the `Access-Control-Request-Method` header matches `method`.
/// Note that during matching the method is normalized according to the fetch specification, that is,
/// byte-uppercased. This means that when using a custom `method` instead of a predefined one, make sure
/// it is uppercased or this matcher will never succeed.
pub fn new(method : Method) -> Self
{
pub fn new(method: Method) -> Self {
Self { method }
}
}
impl RouteMatcher for AccessControlRequestMethodMatcher
{
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
{
impl RouteMatcher for AccessControlRequestMethodMatcher {
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
// according to the fetch specification, methods should be normalized by byte-uppercase
// https://fetch.spec.whatwg.org/#concept-method
match HeaderMap::borrow_from(state).get(ACCESS_CONTROL_REQUEST_METHOD)
match HeaderMap::borrow_from(state)
.get(ACCESS_CONTROL_REQUEST_METHOD)
.and_then(|value| value.to_str().ok())
.and_then(|str| str.to_ascii_uppercase().parse::<Method>().ok())
{
@ -62,19 +59,17 @@ impl RouteMatcher for AccessControlRequestMethodMatcher
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
fn with_state<F>(accept : Option<&str>, block : F)
where F : FnOnce(&mut State) -> ()
fn with_state<F>(accept: Option<&str>, block: F)
where
F: FnOnce(&mut State) -> ()
{
State::with_new(|state| {
let mut headers = HeaderMap::new();
if let Some(acc) = accept
{
if let Some(acc) = accept {
headers.insert(ACCESS_CONTROL_REQUEST_METHOD, acc.parse().unwrap());
}
state.put(headers);
@ -83,23 +78,20 @@ mod test
}
#[test]
fn no_acrm_header()
{
fn no_acrm_header() {
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
with_state(None, |state| assert!(matcher.is_match(&state).is_err()));
}
#[test]
fn correct_acrm_header()
{
fn correct_acrm_header() {
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
with_state(Some("PUT"), |state| assert!(matcher.is_match(&state).is_ok()));
with_state(Some("put"), |state| assert!(matcher.is_match(&state).is_ok()));
}
#[test]
fn incorrect_acrm_header()
{
fn incorrect_acrm_header() {
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
with_state(Some("DELETE"), |state| assert!(matcher.is_match(&state).is_err()));
}

View file

@ -2,4 +2,3 @@
mod access_control_request_method;
#[cfg(feature = "cors")]
pub use access_control_request_method::AccessControlRequestMethodMatcher;

View file

@ -1,29 +1,26 @@
use crate::{OpenapiType, OpenapiSchema};
use crate::{OpenapiSchema, OpenapiType};
use indexmap::IndexMap;
use openapiv3::{
Components, OpenAPI, PathItem, ReferenceOr, ReferenceOr::Item, ReferenceOr::Reference, Schema,
Server
Components, OpenAPI, PathItem, ReferenceOr,
ReferenceOr::{Item, Reference},
Schema, Server
};
use std::sync::{Arc, RwLock};
#[derive(Clone, Debug)]
pub struct OpenapiInfo
{
pub title : String,
pub version : String,
pub urls : Vec<String>
pub struct OpenapiInfo {
pub title: String,
pub version: String,
pub urls: Vec<String>
}
#[derive(Clone, Debug)]
pub struct OpenapiBuilder
{
pub openapi : Arc<RwLock<OpenAPI>>
pub struct OpenapiBuilder {
pub openapi: Arc<RwLock<OpenAPI>>
}
impl OpenapiBuilder
{
pub fn new(info : OpenapiInfo) -> Self
{
impl OpenapiBuilder {
pub fn new(info: OpenapiInfo) -> Self {
Self {
openapi: Arc::new(RwLock::new(OpenAPI {
openapi: "3.0.2".to_string(),
@ -32,18 +29,22 @@ impl OpenapiBuilder
version: info.version,
..Default::default()
},
servers: info.urls.into_iter()
.map(|url| Server { url, ..Default::default() })
servers: info
.urls
.into_iter()
.map(|url| Server {
url,
..Default::default()
})
.collect(),
..Default::default()
}))
}
}
/// Remove path from the OpenAPI spec, or return an empty one if not included. This is handy if you need to
/// modify the path and add it back after the modification
pub fn remove_path(&mut self, path : &str) -> PathItem
{
pub fn remove_path(&mut self, path: &str) -> PathItem {
let mut openapi = self.openapi.write().unwrap();
match openapi.paths.swap_remove(path) {
Some(Item(item)) => item,
@ -51,16 +52,14 @@ impl OpenapiBuilder
}
}
pub fn add_path<Path : ToString>(&mut self, path : Path, item : PathItem)
{
pub fn add_path<Path: ToString>(&mut self, path: Path, item: PathItem) {
let mut openapi = self.openapi.write().unwrap();
openapi.paths.insert(path.to_string(), Item(item));
}
fn add_schema_impl(&mut self, name : String, mut schema : OpenapiSchema)
{
fn add_schema_impl(&mut self, name: String, mut schema: OpenapiSchema) {
self.add_schema_dependencies(&mut schema.dependencies);
let mut openapi = self.openapi.write().unwrap();
match &mut openapi.components {
Some(comp) => {
@ -74,25 +73,23 @@ impl OpenapiBuilder
};
}
fn add_schema_dependencies(&mut self, dependencies : &mut IndexMap<String, OpenapiSchema>)
{
let keys : Vec<String> = dependencies.keys().map(|k| k.to_string()).collect();
for dep in keys
{
fn add_schema_dependencies(&mut self, dependencies: &mut IndexMap<String, OpenapiSchema>) {
let keys: Vec<String> = dependencies.keys().map(|k| k.to_string()).collect();
for dep in keys {
let dep_schema = dependencies.swap_remove(&dep);
if let Some(dep_schema) = dep_schema
{
if let Some(dep_schema) = dep_schema {
self.add_schema_impl(dep, dep_schema);
}
}
}
pub fn add_schema<T : OpenapiType>(&mut self) -> ReferenceOr<Schema>
{
pub fn add_schema<T: OpenapiType>(&mut self) -> ReferenceOr<Schema> {
let mut schema = T::schema();
match schema.name.clone() {
Some(name) => {
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
let reference = Reference {
reference: format!("#/components/schemas/{}", name)
};
self.add_schema_impl(name, schema);
reference
},
@ -104,59 +101,57 @@ impl OpenapiBuilder
}
}
#[cfg(test)]
#[allow(dead_code)]
mod test
{
mod test {
use super::*;
#[derive(OpenapiType)]
struct Message
{
msg : String
struct Message {
msg: String
}
#[derive(OpenapiType)]
struct Messages
{
msgs : Vec<Message>
struct Messages {
msgs: Vec<Message>
}
fn info() -> OpenapiInfo
{
fn info() -> OpenapiInfo {
OpenapiInfo {
title: "TEST CASE".to_owned(),
version: "1.2.3".to_owned(),
urls: vec!["http://localhost:1234".to_owned(), "https://example.org".to_owned()]
}
}
fn openapi(builder : OpenapiBuilder) -> OpenAPI
{
fn openapi(builder: OpenapiBuilder) -> OpenAPI {
Arc::try_unwrap(builder.openapi).unwrap().into_inner().unwrap()
}
#[test]
fn new_builder()
{
fn new_builder() {
let info = info();
let builder = OpenapiBuilder::new(info.clone());
let openapi = openapi(builder);
assert_eq!(info.title, openapi.info.title);
assert_eq!(info.version, openapi.info.version);
assert_eq!(info.urls.len(), openapi.servers.len());
}
#[test]
fn add_schema()
{
fn add_schema() {
let mut builder = OpenapiBuilder::new(info());
builder.add_schema::<Option<Messages>>();
let openapi = openapi(builder);
assert_eq!(openapi.components.clone().unwrap_or_default().schemas["Message"] , ReferenceOr::Item(Message ::schema().into_schema()));
assert_eq!(openapi.components.clone().unwrap_or_default().schemas["Messages"], ReferenceOr::Item(Messages::schema().into_schema()));
assert_eq!(
openapi.components.clone().unwrap_or_default().schemas["Message"],
ReferenceOr::Item(Message::schema().into_schema())
);
assert_eq!(
openapi.components.clone().unwrap_or_default().schemas["Messages"],
ReferenceOr::Item(Messages::schema().into_schema())
);
}
}

View file

@ -15,40 +15,34 @@ use std::{
};
#[derive(Clone)]
pub struct OpenapiHandler
{
openapi : Arc<RwLock<OpenAPI>>
pub struct OpenapiHandler {
openapi: Arc<RwLock<OpenAPI>>
}
impl OpenapiHandler
{
pub fn new(openapi : Arc<RwLock<OpenAPI>>) -> Self
{
impl OpenapiHandler {
pub fn new(openapi: Arc<RwLock<OpenAPI>>) -> Self {
Self { openapi }
}
}
impl NewHandler for OpenapiHandler
{
impl NewHandler for OpenapiHandler {
type Instance = Self;
fn new_handler(&self) -> Result<Self>
{
fn new_handler(&self) -> Result<Self> {
Ok(self.clone())
}
}
#[cfg(feature = "auth")]
fn get_security(state : &mut State) -> IndexMap<String, ReferenceOr<SecurityScheme>>
{
fn get_security(state: &mut State) -> IndexMap<String, ReferenceOr<SecurityScheme>> {
use crate::AuthSource;
use gotham::state::FromState;
let source = match AuthSource::try_borrow_from(state) {
Some(source) => source,
None => return Default::default()
};
let security_scheme = match source {
AuthSource::Cookie(name) => SecurityScheme::APIKey {
location: APIKeyLocation::Cookie,
@ -63,38 +57,35 @@ fn get_security(state : &mut State) -> IndexMap<String, ReferenceOr<SecuritySche
bearer_format: Some("JWT".to_owned())
}
};
let mut security_schemes : IndexMap<String, ReferenceOr<SecurityScheme>> = Default::default();
let mut security_schemes: IndexMap<String, ReferenceOr<SecurityScheme>> = Default::default();
security_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme));
security_schemes
}
#[cfg(not(feature = "auth"))]
fn get_security(state : &mut State) -> (Vec<SecurityRequirement>, IndexMap<String, ReferenceOr<SecurityScheme>>)
{
fn get_security(state: &mut State) -> (Vec<SecurityRequirement>, IndexMap<String, ReferenceOr<SecurityScheme>>) {
Default::default()
}
impl Handler for OpenapiHandler
{
fn handle(self, mut state : State) -> Pin<Box<HandlerFuture>>
{
impl Handler for OpenapiHandler {
fn handle(self, mut state: State) -> Pin<Box<HandlerFuture>> {
let openapi = match self.openapi.read() {
Ok(openapi) => openapi,
Err(e) => {
error!("Unable to acquire read lock for the OpenAPI specification: {}", e);
let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
return future::ok((state, res)).boxed()
return future::ok((state, res)).boxed();
}
};
let mut openapi = openapi.clone();
let security_schemes = get_security(&mut state);
let mut components = openapi.components.unwrap_or_default();
components.security_schemes = security_schemes;
openapi.components = Some(components);
match serde_json::to_string(&openapi) {
Ok(body) => {
let res = create_response(&state, crate::StatusCode::OK, APPLICATION_JSON, body);

View file

@ -1,5 +1,4 @@
const SECURITY_NAME : &str = "authToken";
const SECURITY_NAME: &str = "authToken";
pub mod builder;
pub mod handler;

View file

@ -1,32 +1,21 @@
use crate::{
resource::*,
result::*,
OpenapiSchema,
RequestBody
};
use super::SECURITY_NAME;
use crate::{resource::*, result::*, OpenapiSchema, RequestBody};
use indexmap::IndexMap;
use mime::Mime;
use openapiv3::{
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,
StatusCode, Type
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, ReferenceOr::Item,
RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, StatusCode, Type
};
#[derive(Default)]
struct OperationParams<'a>
{
path_params : Vec<(&'a str, ReferenceOr<Schema>)>,
query_params : Option<OpenapiSchema>
struct OperationParams<'a> {
path_params: Vec<(&'a str, ReferenceOr<Schema>)>,
query_params: Option<OpenapiSchema>
}
impl<'a> OperationParams<'a>
{
fn add_path_params(&self, params : &mut Vec<ReferenceOr<Parameter>>)
{
for param in &self.path_params
{
impl<'a> OperationParams<'a> {
fn add_path_params(&self, params: &mut Vec<ReferenceOr<Parameter>>) {
for param in &self.path_params {
params.push(Item(Parameter::Path {
parameter_data: ParameterData {
name: (*param).0.to_string(),
@ -37,13 +26,12 @@ impl<'a> OperationParams<'a>
example: None,
examples: IndexMap::new()
},
style: Default::default(),
style: Default::default()
}));
}
}
fn add_query_params(self, params : &mut Vec<ReferenceOr<Parameter>>)
{
fn add_query_params(self, params: &mut Vec<ReferenceOr<Parameter>>) {
let query_params = match self.query_params {
Some(qp) => qp.schema,
None => return
@ -52,8 +40,7 @@ impl<'a> OperationParams<'a>
SchemaKind::Type(Type::Object(ty)) => ty,
_ => panic!("Query Parameters needs to be a plain struct")
};
for (name, schema) in query_params.properties
{
for (name, schema) in query_params.properties {
let required = query_params.required.contains(&name);
params.push(Item(Parameter::Query {
parameter_data: ParameterData {
@ -71,32 +58,28 @@ impl<'a> OperationParams<'a>
}))
}
}
fn into_params(self) -> Vec<ReferenceOr<Parameter>>
{
let mut params : Vec<ReferenceOr<Parameter>> = Vec::new();
fn into_params(self) -> Vec<ReferenceOr<Parameter>> {
let mut params: Vec<ReferenceOr<Parameter>> = Vec::new();
self.add_path_params(&mut params);
self.add_query_params(&mut params);
params
}
}
pub struct OperationDescription<'a>
{
operation_id : Option<String>,
default_status : crate::StatusCode,
accepted_types : Option<Vec<Mime>>,
schema : ReferenceOr<Schema>,
params : OperationParams<'a>,
body_schema : Option<ReferenceOr<Schema>>,
supported_types : Option<Vec<Mime>>,
requires_auth : bool
pub struct OperationDescription<'a> {
operation_id: Option<String>,
default_status: crate::StatusCode,
accepted_types: Option<Vec<Mime>>,
schema: ReferenceOr<Schema>,
params: OperationParams<'a>,
body_schema: Option<ReferenceOr<Schema>>,
supported_types: Option<Vec<Mime>>,
requires_auth: bool
}
impl<'a> OperationDescription<'a>
{
pub fn new<Handler : ResourceMethod>(schema : ReferenceOr<Schema>) -> Self
{
impl<'a> OperationDescription<'a> {
pub fn new<Handler: ResourceMethod>(schema: ReferenceOr<Schema>) -> Self {
Self {
operation_id: Handler::operation_id(),
default_status: Handler::Res::default_status(),
@ -108,32 +91,26 @@ impl<'a> OperationDescription<'a>
requires_auth: Handler::wants_auth()
}
}
pub fn add_path_param(mut self, name : &'a str, schema : ReferenceOr<Schema>) -> Self
{
pub fn add_path_param(mut self, name: &'a str, schema: ReferenceOr<Schema>) -> Self {
self.params.path_params.push((name, schema));
self
}
pub fn with_query_params(mut self, params : OpenapiSchema) -> Self
{
pub fn with_query_params(mut self, params: OpenapiSchema) -> Self {
self.params.query_params = Some(params);
self
}
pub fn with_body<Body : RequestBody>(mut self, schema : ReferenceOr<Schema>) -> Self
{
pub fn with_body<Body: RequestBody>(mut self, schema: ReferenceOr<Schema>) -> Self {
self.body_schema = Some(schema);
self.supported_types = Body::supported_types();
self
}
fn schema_to_content(types : Vec<Mime>, schema : ReferenceOr<Schema>) -> IndexMap<String, MediaType>
{
let mut content : IndexMap<String, MediaType> = IndexMap::new();
for ty in types
{
fn schema_to_content(types: Vec<Mime>, schema: ReferenceOr<Schema>) -> IndexMap<String, MediaType> {
let mut content: IndexMap<String, MediaType> = IndexMap::new();
for ty in types {
content.insert(ty.to_string(), MediaType {
schema: Some(schema.clone()),
..Default::default()
@ -141,36 +118,47 @@ impl<'a> OperationDescription<'a>
}
content
}
pub fn into_operation(self) -> Operation
{
pub fn into_operation(self) -> Operation {
// this is unfortunately neccessary to prevent rust from complaining about partially moving self
let (operation_id, default_status, accepted_types, schema, params, body_schema, supported_types, requires_auth) = (
self.operation_id, self.default_status, self.accepted_types, self.schema, self.params, self.body_schema, self.supported_types, self.requires_auth);
self.operation_id,
self.default_status,
self.accepted_types,
self.schema,
self.params,
self.body_schema,
self.supported_types,
self.requires_auth
);
let content = Self::schema_to_content(accepted_types.or_all_types(), schema);
let mut responses : IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
responses.insert(StatusCode::Code(default_status.as_u16()), Item(Response {
description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(),
content,
..Default::default()
}));
let request_body = body_schema.map(|schema| Item(OARequestBody {
description: None,
content: Self::schema_to_content(supported_types.or_all_types(), schema),
required: true
}));
let mut responses: IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
responses.insert(
StatusCode::Code(default_status.as_u16()),
Item(Response {
description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(),
content,
..Default::default()
})
);
let request_body = body_schema.map(|schema| {
Item(OARequestBody {
description: None,
content: Self::schema_to_content(supported_types.or_all_types(), schema),
required: true
})
});
let mut security = Vec::new();
if requires_auth
{
if requires_auth {
let mut sec = IndexMap::new();
sec.insert(SECURITY_NAME.to_owned(), Vec::new());
security.push(sec);
}
Operation {
tags: Vec::new(),
operation_id,
@ -187,25 +175,21 @@ impl<'a> OperationDescription<'a>
}
}
#[cfg(test)]
mod test
{
use crate::{OpenapiType, ResourceResult};
mod test {
use super::*;
use crate::{OpenapiType, ResourceResult};
#[test]
fn no_content_schema_to_content()
{
fn no_content_schema_to_content() {
let types = NoContent::accepted_types();
let schema = <NoContent as OpenapiType>::schema();
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
assert!(content.is_empty());
}
#[test]
fn raw_schema_to_content()
{
fn raw_schema_to_content() {
let types = Raw::<&str>::accepted_types();
let schema = <Raw<&str> as OpenapiType>::schema();
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));

View file

@ -1,40 +1,30 @@
use crate::{
resource::*,
routing::*,
OpenapiType,
};
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription};
use gotham::{
pipeline::chain::PipelineHandleChain,
router::builder::*
};
use crate::{resource::*, routing::*, OpenapiType};
use gotham::{pipeline::chain::PipelineHandleChain, router::builder::*};
use std::panic::RefUnwindSafe;
/// This trait adds the `get_openapi` method to an OpenAPI-aware router.
pub trait GetOpenapi
{
fn get_openapi(&mut self, path : &str);
pub trait GetOpenapi {
fn get_openapi(&mut self, path: &str);
}
#[derive(Debug)]
pub struct OpenapiRouter<'a, D>
{
pub(crate) router : &'a mut D,
pub(crate) scope : Option<&'a str>,
pub(crate) openapi_builder : &'a mut OpenapiBuilder
pub struct OpenapiRouter<'a, D> {
pub(crate) router: &'a mut D,
pub(crate) scope: Option<&'a str>,
pub(crate) openapi_builder: &'a mut OpenapiBuilder
}
macro_rules! implOpenapiRouter {
($implType:ident) => {
impl<'a, 'b, C, P> OpenapiRouter<'a, $implType<'b, C, P>>
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
pub fn scope<F>(&mut self, path : &str, callback : F)
pub fn scope<F>(&mut self, path: &str, callback: F)
where
F : FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>)
F: FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>)
{
let mut openapi_builder = self.openapi_builder.clone();
let new_scope = self.scope.map(|scope| format!("{}/{}", scope, path).replace("//", "/"));
@ -48,107 +38,120 @@ macro_rules! implOpenapiRouter {
});
}
}
impl<'a, 'b, C, P> GetOpenapi for OpenapiRouter<'a, $implType<'b, C, P>>
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn get_openapi(&mut self, path : &str)
{
self.router.get(path).to_new_handler(OpenapiHandler::new(self.openapi_builder.openapi.clone()));
fn get_openapi(&mut self, path: &str) {
self.router
.get(path)
.to_new_handler(OpenapiHandler::new(self.openapi_builder.openapi.clone()));
}
}
impl<'a, 'b, C, P> DrawResources for OpenapiRouter<'a, $implType<'b, C, P>>
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn resource<R : Resource>(&mut self, path : &str)
{
fn resource<R: Resource>(&mut self, path: &str) {
R::setup((self, path));
}
}
impl<'a, 'b, C, P> DrawResourceRoutes for (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn read_all<Handler : ResourceReadAll>(&mut self)
{
fn read_all<Handler: ResourceReadAll>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.get = Some(OperationDescription::new::<Handler>(schema).into_operation());
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).read_all::<Handler>()
}
fn read<Handler : ResourceRead>(&mut self)
{
fn read<Handler: ResourceRead>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.get = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).into_operation());
item.get = Some(
OperationDescription::new::<Handler>(schema)
.add_path_param("id", id_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).read::<Handler>()
}
fn search<Handler : ResourceSearch>(&mut self)
{
fn search<Handler: ResourceSearch>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let path = format!("{}/{}/search", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.get = Some(OperationDescription::new::<Handler>(schema).with_query_params(Handler::Query::schema()).into_operation());
item.get = Some(
OperationDescription::new::<Handler>(schema)
.with_query_params(Handler::Query::schema())
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).search::<Handler>()
}
fn create<Handler : ResourceCreate>(&mut self)
fn create<Handler: ResourceCreate>(&mut self)
where
Handler::Res : 'static,
Handler::Body : 'static
Handler::Res: 'static,
Handler::Body: 'static
{
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.post = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
item.post = Some(
OperationDescription::new::<Handler>(schema)
.with_body::<Handler::Body>(body_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).create::<Handler>()
}
fn change_all<Handler : ResourceChangeAll>(&mut self)
fn change_all<Handler: ResourceChangeAll>(&mut self)
where
Handler::Res : 'static,
Handler::Body : 'static
Handler::Res: 'static,
Handler::Body: 'static
{
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.put = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
item.put = Some(
OperationDescription::new::<Handler>(schema)
.with_body::<Handler::Body>(body_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).change_all::<Handler>()
}
fn change<Handler : ResourceChange>(&mut self)
fn change<Handler: ResourceChange>(&mut self)
where
Handler::Res : 'static,
Handler::Body : 'static
Handler::Res: 'static,
Handler::Body: 'static
{
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
@ -156,39 +159,45 @@ macro_rules! implOpenapiRouter {
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.put = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).with_body::<Handler::Body>(body_schema).into_operation());
item.put = Some(
OperationDescription::new::<Handler>(schema)
.add_path_param("id", id_schema)
.with_body::<Handler::Body>(body_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).change::<Handler>()
}
fn remove_all<Handler : ResourceRemoveAll>(&mut self)
{
fn remove_all<Handler: ResourceRemoveAll>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.delete = Some(OperationDescription::new::<Handler>(schema).into_operation());
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).remove_all::<Handler>()
}
fn remove<Handler : ResourceRemove>(&mut self)
{
fn remove<Handler: ResourceRemove>(&mut self) {
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
let mut item = (self.0).openapi_builder.remove_path(&path);
item.delete = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).into_operation());
item.delete = Some(
OperationDescription::new::<Handler>(schema)
.add_path_param("id", id_schema)
.into_operation()
);
(self.0).openapi_builder.add_path(path, item);
(&mut *(self.0).router, self.1).remove::<Handler>()
}
}
}
};
}
implOpenapiRouter!(RouterBuilder);

View file

@ -1,18 +1,17 @@
#[cfg(feature = "chrono")]
use chrono::{
Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc
};
use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc};
use indexmap::IndexMap;
use openapiv3::{
AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, ReferenceOr::Item,
ReferenceOr::Reference, Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty
AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType,
ReferenceOr::{Item, Reference},
Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty
};
#[cfg(feature = "uuid")]
use uuid::Uuid;
use std::{
collections::{BTreeSet, HashMap, HashSet},
hash::BuildHasher
};
#[cfg(feature = "uuid")]
use uuid::Uuid;
/**
This struct needs to be available for every type that can be part of an OpenAPI Spec. It is
@ -22,26 +21,23 @@ for your type, simply derive from [`OpenapiType`].
[`OpenapiType`]: trait.OpenapiType.html
*/
#[derive(Debug, Clone, PartialEq)]
pub struct OpenapiSchema
{
pub struct OpenapiSchema {
/// The name of this schema. If it is None, the schema will be inlined.
pub name : Option<String>,
pub name: Option<String>,
/// 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,
pub nullable: bool,
/// The actual OpenAPI schema.
pub schema : SchemaKind,
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<String, OpenapiSchema>
pub dependencies: IndexMap<String, OpenapiSchema>
}
impl OpenapiSchema
{
impl OpenapiSchema {
/// Create a new schema that has no name.
pub fn new(schema : SchemaKind) -> Self
{
pub fn new(schema: SchemaKind) -> Self {
Self {
name: None,
nullable: false,
@ -49,10 +45,9 @@ 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
{
pub fn into_schema(self) -> Schema {
Schema {
schema_data: SchemaData {
nullable: self.nullable,
@ -80,15 +75,12 @@ struct MyResponse {
[`OpenapiSchema`]: struct.OpenapiSchema.html
*/
pub trait OpenapiType
{
pub trait OpenapiType {
fn schema() -> OpenapiSchema;
}
impl OpenapiType for ()
{
fn schema() -> OpenapiSchema
{
impl OpenapiType for () {
fn schema() -> OpenapiSchema {
OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType {
additional_properties: Some(AdditionalProperties::Any(false)),
..Default::default()
@ -96,11 +88,9 @@ impl OpenapiType for ()
}
}
impl OpenapiType for bool
{
fn schema() -> OpenapiSchema
{
OpenapiSchema::new(SchemaKind::Type(Type::Boolean{}))
impl OpenapiType for bool {
fn schema() -> OpenapiSchema {
OpenapiSchema::new(SchemaKind::Type(Type::Boolean {}))
}
}
@ -114,7 +104,7 @@ macro_rules! int_types {
}
}
)*};
(unsigned $($int_ty:ty),*) => {$(
impl OpenapiType for $int_ty
{
@ -127,7 +117,7 @@ macro_rules! int_types {
}
}
)*};
(bits = $bits:expr, $($int_ty:ty),*) => {$(
impl OpenapiType for $int_ty
{
@ -140,7 +130,7 @@ macro_rules! int_types {
}
}
)*};
(unsigned bits = $bits:expr, $($int_ty:ty),*) => {$(
impl OpenapiType for $int_ty
{
@ -203,7 +193,7 @@ macro_rules! str_types {
fn schema() -> OpenapiSchema
{
use openapiv3::StringFormat;
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
format: VariantOrUnknownOrEmpty::Item(StringFormat::$format),
..Default::default()
@ -211,7 +201,7 @@ macro_rules! str_types {
}
}
)*};
(format_str = $format:expr, $($str_ty:ty),*) => {$(
impl OpenapiType for $str_ty
{
@ -231,26 +221,32 @@ str_types!(String, &str);
#[cfg(feature = "chrono")]
str_types!(format = Date, Date<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate);
#[cfg(feature = "chrono")]
str_types!(format = DateTime, DateTime<FixedOffset>, DateTime<Local>, DateTime<Utc>, NaiveDateTime);
str_types!(
format = DateTime,
DateTime<FixedOffset>,
DateTime<Local>,
DateTime<Utc>,
NaiveDateTime
);
#[cfg(feature = "uuid")]
str_types!(format_str = "uuid", Uuid);
impl<T : OpenapiType> OpenapiType for Option<T>
{
fn schema() -> OpenapiSchema
{
impl<T: OpenapiType> OpenapiType for Option<T> {
fn schema() -> OpenapiSchema {
let schema = T::schema();
let mut dependencies = schema.dependencies.clone();
let schema = match schema.name.clone() {
Some(name) => {
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
let reference = Reference {
reference: format!("#/components/schemas/{}", name)
};
dependencies.insert(name, schema);
SchemaKind::AllOf { all_of: vec![reference] }
},
None => schema.schema
};
OpenapiSchema {
nullable: true,
name: None,
@ -260,22 +256,22 @@ impl<T : OpenapiType> OpenapiType for Option<T>
}
}
impl<T : OpenapiType> OpenapiType for Vec<T>
{
fn schema() -> OpenapiSchema
{
impl<T: OpenapiType> OpenapiType for Vec<T> {
fn schema() -> OpenapiSchema {
let schema = T::schema();
let mut dependencies = schema.dependencies.clone();
let items = match schema.name.clone() {
Some(name) => {
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
let reference = Reference {
reference: format!("#/components/schemas/{}", name)
};
dependencies.insert(name, schema);
reference
},
None => Item(Box::new(schema.into_schema()))
};
OpenapiSchema {
nullable: false,
name: None,
@ -290,38 +286,34 @@ impl<T : OpenapiType> OpenapiType for Vec<T>
}
}
impl<T : OpenapiType> OpenapiType for BTreeSet<T>
{
fn schema() -> OpenapiSchema
{
impl<T: OpenapiType> OpenapiType for BTreeSet<T> {
fn schema() -> OpenapiSchema {
<Vec<T> as OpenapiType>::schema()
}
}
impl<T : OpenapiType, S : BuildHasher> OpenapiType for HashSet<T, S>
{
fn schema() -> OpenapiSchema
{
impl<T: OpenapiType, S: BuildHasher> OpenapiType for HashSet<T, S> {
fn schema() -> OpenapiSchema {
<Vec<T> as OpenapiType>::schema()
}
}
impl<K, T : OpenapiType, S : BuildHasher> OpenapiType for HashMap<K, T, S>
{
fn schema() -> OpenapiSchema
{
impl<K, T: OpenapiType, S: BuildHasher> OpenapiType for HashMap<K, T, S> {
fn schema() -> OpenapiSchema {
let schema = T::schema();
let mut dependencies = schema.dependencies.clone();
let items = Box::new(match schema.name.clone() {
Some(name) => {
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
let reference = Reference {
reference: format!("#/components/schemas/{}", name)
};
dependencies.insert(name, schema);
reference
},
None => Item(schema.into_schema())
});
OpenapiSchema {
nullable: false,
name: None,
@ -334,10 +326,8 @@ impl<K, T : OpenapiType, S : BuildHasher> OpenapiType for HashMap<K, T, S>
}
}
impl OpenapiType for serde_json::Value
{
fn schema() -> OpenapiSchema
{
impl OpenapiType for serde_json::Value {
fn schema() -> OpenapiSchema {
OpenapiSchema {
nullable: true,
name: None,
@ -347,15 +337,13 @@ impl OpenapiType for serde_json::Value
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
use serde_json::Value;
type Unit = ();
macro_rules! assert_schema {
($ty:ident $(<$($generic:ident),+>)* => $json:expr) => {
paste::item! {
@ -369,7 +357,7 @@ mod test
}
};
}
assert_schema!(Unit => r#"{"type":"object","additionalProperties":false}"#);
assert_schema!(bool => r#"{"type":"boolean"}"#);
assert_schema!(isize => r#"{"type":"integer"}"#);
@ -386,7 +374,7 @@ mod test
assert_schema!(u128 => r#"{"type":"integer","format":"int128","minimum":0}"#);
assert_schema!(f32 => r#"{"type":"number","format":"float"}"#);
assert_schema!(f64 => r#"{"type":"number","format":"double"}"#);
assert_schema!(String => r#"{"type":"string"}"#);
#[cfg(feature = "chrono")]
assert_schema!(Date<FixedOffset> => r#"{"type":"string","format":"date"}"#);
@ -406,7 +394,7 @@ mod test
assert_schema!(NaiveDateTime => r#"{"type":"string","format":"date-time"}"#);
#[cfg(feature = "uuid")]
assert_schema!(Uuid => r#"{"type":"string","format":"uuid"}"#);
assert_schema!(Option<String> => r#"{"nullable":true,"type":"string"}"#);
assert_schema!(Vec<String> => r#"{"type":"array","items":{"type":"string"}}"#);
assert_schema!(BTreeSet<String> => r#"{"type":"array","items":{"type":"string"}}"#);

View file

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

View file

@ -3,62 +3,55 @@ use mime::{Mime, APPLICATION_JSON};
/// A response, used to create the final gotham response from.
#[derive(Debug)]
pub struct Response
{
pub status : StatusCode,
pub body : Body,
pub mime : Option<Mime>
pub struct Response {
pub status: StatusCode,
pub body: Body,
pub mime: Option<Mime>
}
impl Response
{
impl Response {
/// Create a new `Response` from raw data.
pub fn new<B : Into<Body>>(status : StatusCode, body : B, mime : Option<Mime>) -> Self
{
pub fn new<B: Into<Body>>(status: StatusCode, body: B, mime: Option<Mime>) -> Self {
Self {
status,
body: body.into(),
mime
}
}
/// Create a `Response` with mime type json from already serialized data.
pub fn json<B : Into<Body>>(status : StatusCode, body : B) -> Self
{
pub fn json<B: Into<Body>>(status: StatusCode, body: B) -> Self {
Self {
status,
body: body.into(),
mime: Some(APPLICATION_JSON)
}
}
/// Create a _204 No Content_ `Response`.
pub fn no_content() -> Self
{
pub fn no_content() -> Self {
Self {
status: StatusCode::NO_CONTENT,
body: Body::empty(),
mime: None
}
}
/// Create an empty _403 Forbidden_ `Response`.
pub fn forbidden() -> Self
{
pub fn forbidden() -> Self {
Self {
status: StatusCode::FORBIDDEN,
body: Body::empty(),
mime: None
}
}
#[cfg(test)]
pub(crate) fn full_body(mut self) -> Result<Vec<u8>, <Body as gotham::hyper::body::HttpBody>::Error>
{
pub(crate) fn full_body(mut self) -> Result<Vec<u8>, <Body as gotham::hyper::body::HttpBody>::Error> {
use futures_executor::block_on;
use gotham::hyper::body::to_bytes;
let bytes : &[u8] = &block_on(to_bytes(&mut self.body))?;
let bytes: &[u8] = &block_on(to_bytes(&mut self.body))?;
Ok(bytes.to_vec())
}
}
}

View file

@ -1,6 +1,5 @@
use gotham_restful_derive::ResourceError;
/**
This is an error type that always yields a _403 Forbidden_ response. This type is best used in
combination with [`AuthSuccess`] or [`AuthResult`].
@ -9,8 +8,7 @@ combination with [`AuthSuccess`] or [`AuthResult`].
[`AuthResult`]: type.AuthResult.html
*/
#[derive(Debug, Clone, Copy, ResourceError)]
pub enum AuthError
{
pub enum AuthError {
#[status(FORBIDDEN)]
#[display("Forbidden")]
Forbidden
@ -57,8 +55,7 @@ error, or delegates to another error type. This type is best used with [`AuthRes
[`AuthResult`]: type.AuthResult.html
*/
#[derive(Debug, ResourceError)]
pub enum AuthErrorOrOther<E>
{
pub enum AuthErrorOrOther<E> {
#[status(FORBIDDEN)]
#[display("Forbidden")]
Forbidden,
@ -67,10 +64,8 @@ pub enum AuthErrorOrOther<E>
Other(E)
}
impl<E> From<AuthError> for AuthErrorOrOther<E>
{
fn from(err : AuthError) -> Self
{
impl<E> From<AuthError> for AuthErrorOrOther<E> {
fn from(err: AuthError) -> Self {
match err {
AuthError::Forbidden => Self::Forbidden
}
@ -80,10 +75,9 @@ impl<E> From<AuthError> for AuthErrorOrOther<E>
impl<E, F> From<F> for AuthErrorOrOther<E>
where
// TODO https://gitlab.com/msrd0/gotham-restful/-/issues/20
F : std::error::Error + Into<E>
F: std::error::Error + Into<E>
{
fn from(err : F) -> Self
{
fn from(err: F) -> Self {
Self::Other(err.into())
}
}

View file

@ -1,13 +1,13 @@
use crate::Response;
#[cfg(feature = "openapi")]
use crate::OpenapiSchema;
use crate::Response;
use futures_util::future::FutureExt;
use mime::{Mime, STAR_STAR};
use serde::Serialize;
use std::{
error::Error,
future::Future,
fmt::{Debug, Display},
future::Future,
pin::Pin
};
@ -27,67 +27,54 @@ pub use result::IntoResponseError;
mod success;
pub use success::Success;
pub(crate) trait OrAllTypes
{
pub(crate) trait OrAllTypes {
fn or_all_types(self) -> Vec<Mime>;
}
impl OrAllTypes for Option<Vec<Mime>>
{
fn or_all_types(self) -> Vec<Mime>
{
impl OrAllTypes for Option<Vec<Mime>> {
fn or_all_types(self) -> Vec<Mime> {
self.unwrap_or_else(|| vec![STAR_STAR])
}
}
/// A trait provided to convert a resource's result to json.
pub trait ResourceResult
{
type Err : Error + Send + 'static;
pub trait ResourceResult {
type Err: Error + Send + Sync + 'static;
/// Turn this into a response that can be returned to the browser. This api will likely
/// change in the future.
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>;
/// Return a list of supported mime types.
fn accepted_types() -> Option<Vec<Mime>>
{
fn accepted_types() -> Option<Vec<Mime>> {
None
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema;
#[cfg(feature = "openapi")]
fn default_status() -> crate::StatusCode
{
fn default_status() -> crate::StatusCode {
crate::StatusCode::OK
}
}
#[cfg(feature = "openapi")]
impl<Res : ResourceResult> crate::OpenapiType for Res
{
fn schema() -> OpenapiSchema
{
impl<Res: ResourceResult> crate::OpenapiType for Res {
fn schema() -> OpenapiSchema {
Self::schema()
}
}
/// The default json returned on an 500 Internal Server Error.
#[derive(Debug, Serialize)]
pub(crate) struct ResourceError
{
error : bool,
message : String
pub(crate) struct ResourceError {
error: bool,
message: String
}
impl<T : ToString> From<T> for ResourceError
{
fn from(message : T) -> Self
{
impl<T: ToString> From<T> for ResourceError {
fn from(message: T) -> Self {
Self {
error: true,
message: message.to_string()
@ -95,27 +82,26 @@ impl<T : ToString> From<T> for ResourceError
}
}
fn into_response_helper<Err, F>(create_response : F) -> Pin<Box<dyn Future<Output = Result<Response, Err>> + Send>>
fn into_response_helper<Err, F>(create_response: F) -> Pin<Box<dyn Future<Output = Result<Response, Err>> + Send>>
where
Err : Send + 'static,
F : FnOnce() -> Result<Response, Err>
Err: Send + 'static,
F: FnOnce() -> Result<Response, Err>
{
let res = create_response();
async move { res }.boxed()
}
#[cfg(feature = "errorlog")]
fn errorlog<E : Display>(e : E)
{
fn errorlog<E: Display>(e: E) {
error!("The handler encountered an error: {}", e);
}
#[cfg(not(feature = "errorlog"))]
fn errorlog<E>(_e : E) {}
fn errorlog<E>(_e: E) {}
fn handle_error<E>(e : E) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
fn handle_error<E>(e: E) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
where
E : Display + IntoResponseError
E: Display + IntoResponseError
{
into_response_helper(|| {
errorlog(&e);
@ -123,67 +109,55 @@ where
})
}
impl<Res> ResourceResult for Pin<Box<dyn Future<Output = Res> + Send>>
where
Res : ResourceResult + 'static
Res: ResourceResult + 'static
{
type Err = Res::Err;
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>
{
self.then(|result| {
result.into_response()
}).boxed()
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
self.then(|result| result.into_response()).boxed()
}
fn accepted_types() -> Option<Vec<Mime>>
{
fn accepted_types() -> Option<Vec<Mime>> {
Res::accepted_types()
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema
{
fn schema() -> OpenapiSchema {
Res::schema()
}
#[cfg(feature = "openapi")]
fn default_status() -> crate::StatusCode
{
fn default_status() -> crate::StatusCode {
Res::default_status()
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
use futures_executor::block_on;
use thiserror::Error;
#[derive(Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
struct Msg
{
msg : String
struct Msg {
msg: String
}
#[derive(Debug, Default, Error)]
#[error("An Error")]
struct MsgError;
#[test]
fn result_from_future()
{
fn result_from_future() {
let nc = NoContent::default();
let res = block_on(nc.into_response()).unwrap();
let fut_nc = async move { NoContent::default() }.boxed();
let fut_res = block_on(fut_nc.into_response()).unwrap();
assert_eq!(res.status, fut_res.status);
assert_eq!(res.mime, fut_res.mime);
assert_eq!(res.full_body().unwrap(), fut_res.full_body().unwrap());

View file

@ -1,14 +1,10 @@
use super::{ResourceResult, handle_error};
use super::{handle_error, ResourceResult};
use crate::{IntoResponseError, Response};
#[cfg(feature = "openapi")]
use crate::{OpenapiSchema, OpenapiType};
use futures_util::{future, future::FutureExt};
use mime::Mime;
use std::{
fmt::Display,
future::Future,
pin::Pin
};
use std::{fmt::Display, future::Future, pin::Pin};
/**
This is the return type of a resource that doesn't actually return something. It will result
@ -35,104 +31,89 @@ fn read_all(_state: &mut State) {
#[derive(Clone, Copy, Debug, Default)]
pub struct NoContent;
impl From<()> for NoContent
{
fn from(_ : ()) -> Self
{
impl From<()> for NoContent {
fn from(_: ()) -> Self {
Self {}
}
}
impl ResourceResult for NoContent
{
impl ResourceResult for NoContent {
// TODO this shouldn't be a serde_json::Error
type Err = serde_json::Error; // just for easier handling of `Result<NoContent, E>`
/// This will always be a _204 No Content_ together with an empty string.
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>
{
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
future::ok(Response::no_content()).boxed()
}
fn accepted_types() -> Option<Vec<Mime>>
{
fn accepted_types() -> Option<Vec<Mime>> {
Some(Vec::new())
}
/// Returns the schema of the `()` type.
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema
{
fn schema() -> OpenapiSchema {
<()>::schema()
}
/// This will always be a _204 No Content_
#[cfg(feature = "openapi")]
fn default_status() -> crate::StatusCode
{
fn default_status() -> crate::StatusCode {
crate::StatusCode::NO_CONTENT
}
}
impl<E> ResourceResult for Result<NoContent, E>
where
E : Display + IntoResponseError<Err = serde_json::Error>
E: Display + IntoResponseError<Err = serde_json::Error>
{
type Err = serde_json::Error;
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, serde_json::Error>> + Send>>
{
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, serde_json::Error>> + Send>> {
match self {
Ok(nc) => nc.into_response(),
Err(e) => handle_error(e)
}
}
fn accepted_types() -> Option<Vec<Mime>>
{
fn accepted_types() -> Option<Vec<Mime>> {
NoContent::accepted_types()
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema
{
fn schema() -> OpenapiSchema {
<NoContent as ResourceResult>::schema()
}
#[cfg(feature = "openapi")]
fn default_status() -> crate::StatusCode
{
fn default_status() -> crate::StatusCode {
NoContent::default_status()
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
use futures_executor::block_on;
use gotham::hyper::StatusCode;
use thiserror::Error;
#[derive(Debug, Default, Error)]
#[error("An Error")]
struct MsgError;
#[test]
fn no_content_has_empty_response()
{
fn no_content_has_empty_response() {
let no_content = NoContent::default();
let res = block_on(no_content.into_response()).expect("didn't expect error response");
assert_eq!(res.status, StatusCode::NO_CONTENT);
assert_eq!(res.mime, None);
assert_eq!(res.full_body().unwrap(), &[] as &[u8]);
}
#[test]
fn no_content_result()
{
let no_content : Result<NoContent, MsgError> = Ok(NoContent::default());
fn no_content_result() {
let no_content: Result<NoContent, MsgError> = Ok(NoContent::default());
let res = block_on(no_content.into_response()).expect("didn't expect error response");
assert_eq!(res.status, StatusCode::NO_CONTENT);
assert_eq!(res.mime, None);

View file

@ -1,7 +1,7 @@
use super::{IntoResponseError, ResourceResult, handle_error};
use crate::{FromBody, RequestBody, ResourceType, Response, StatusCode};
use super::{handle_error, IntoResponseError, ResourceResult};
#[cfg(feature = "openapi")]
use crate::OpenapiSchema;
use crate::{FromBody, RequestBody, ResourceType, Response, StatusCode};
use futures_core::future::Future;
use futures_util::{future, future::FutureExt};
use gotham::hyper::body::{Body, Bytes};
@ -9,11 +9,7 @@ use mime::Mime;
#[cfg(feature = "openapi")]
use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
use serde_json::error::Error as SerdeJsonError;
use std::{
convert::Infallible,
fmt::Display,
pin::Pin
};
use std::{convert::Infallible, fmt::Display, pin::Pin};
/**
This type can be used both as a raw request body, as well as as a raw response. However, all types
@ -43,44 +39,37 @@ fn create(body : Raw<Vec<u8>>) -> Raw<Vec<u8>> {
[`OpenapiType`]: trait.OpenapiType.html
*/
#[derive(Debug)]
pub struct Raw<T>
{
pub raw : T,
pub mime : Mime
pub struct Raw<T> {
pub raw: T,
pub mime: Mime
}
impl<T> Raw<T>
{
pub fn new(raw : T, mime : Mime) -> Self
{
impl<T> Raw<T> {
pub fn new(raw: T, mime: Mime) -> Self {
Self { raw, mime }
}
}
impl<T, U> AsMut<U> for Raw<T>
where
T : AsMut<U>
T: AsMut<U>
{
fn as_mut(&mut self) -> &mut U
{
fn as_mut(&mut self) -> &mut U {
self.raw.as_mut()
}
}
impl<T, U> AsRef<U> for Raw<T>
where
T : AsRef<U>
T: AsRef<U>
{
fn as_ref(&self) -> &U
{
fn as_ref(&self) -> &U {
self.raw.as_ref()
}
}
impl<T : Clone> Clone for Raw<T>
{
fn clone(&self) -> Self
{
impl<T: Clone> Clone for Raw<T> {
fn clone(&self) -> Self {
Self {
raw: self.raw.clone(),
mime: self.mime.clone()
@ -88,36 +77,28 @@ impl<T : Clone> Clone for Raw<T>
}
}
impl<T : for<'a> From<&'a [u8]>> FromBody for Raw<T>
{
impl<T: for<'a> From<&'a [u8]>> FromBody for Raw<T> {
type Err = Infallible;
fn from_body(body : Bytes, mime : Mime) -> Result<Self, Self::Err>
{
fn from_body(body: Bytes, mime: Mime) -> Result<Self, Self::Err> {
Ok(Self::new(body.as_ref().into(), mime))
}
}
impl<T> RequestBody for Raw<T>
where
Raw<T> : FromBody + ResourceType
{
}
impl<T> RequestBody for Raw<T> where Raw<T>: FromBody + ResourceType {}
impl<T : Into<Body>> ResourceResult for Raw<T>
impl<T: Into<Body>> ResourceResult for Raw<T>
where
Self : Send
Self: Send
{
type Err = SerdeJsonError; // just for easier handling of `Result<Raw<T>, E>`
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>>
{
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>> {
future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed()
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema
{
fn schema() -> OpenapiSchema {
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
..Default::default()
@ -127,37 +108,32 @@ where
impl<T, E> ResourceResult for Result<Raw<T>, E>
where
Raw<T> : ResourceResult,
E : Display + IntoResponseError<Err = <Raw<T> as ResourceResult>::Err>
Raw<T>: ResourceResult,
E: Display + IntoResponseError<Err = <Raw<T> as ResourceResult>::Err>
{
type Err = E::Err;
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
{
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>> {
match self {
Ok(raw) => raw.into_response(),
Err(e) => handle_error(e)
}
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema
{
fn schema() -> OpenapiSchema {
<Raw<T> as ResourceResult>::schema()
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
use futures_executor::block_on;
use mime::TEXT_PLAIN;
#[test]
fn raw_response()
{
fn raw_response() {
let msg = "Test";
let raw = Raw::new(msg, TEXT_PLAIN);
let res = block_on(raw.into_response()).expect("didn't expect error response");

View file

@ -1,106 +1,95 @@
use super::{ResourceResult, handle_error, into_response_helper};
use crate::{
result::ResourceError,
Response, ResponseBody, StatusCode
};
use super::{handle_error, into_response_helper, ResourceResult};
#[cfg(feature = "openapi")]
use crate::OpenapiSchema;
use crate::{result::ResourceError, Response, ResponseBody, StatusCode};
use futures_core::future::Future;
use mime::{Mime, APPLICATION_JSON};
use std::{
error::Error,
fmt::Display,
pin::Pin
};
use std::{error::Error, fmt::Display, pin::Pin};
pub trait IntoResponseError {
type Err: Error + Send + 'static;
pub trait IntoResponseError
{
type Err : Error + Send + 'static;
fn into_response_error(self) -> Result<Response, Self::Err>;
}
impl<E : Error> IntoResponseError for E
{
impl<E: Error> IntoResponseError for E {
type Err = serde_json::Error;
fn into_response_error(self) -> Result<Response, Self::Err>
{
let err : ResourceError = self.into();
Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?))
fn into_response_error(self) -> Result<Response, Self::Err> {
let err: ResourceError = self.into();
Ok(Response::json(
StatusCode::INTERNAL_SERVER_ERROR,
serde_json::to_string(&err)?
))
}
}
impl<R, E> ResourceResult for Result<R, E>
where
R : ResponseBody,
E : Display + IntoResponseError<Err = serde_json::Error>
R: ResponseBody,
E: Display + IntoResponseError<Err = serde_json::Error>
{
type Err = E::Err;
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
{
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>> {
match self {
Ok(r) => into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(&r)?))),
Err(e) => handle_error(e)
}
}
fn accepted_types() -> Option<Vec<Mime>>
{
fn accepted_types() -> Option<Vec<Mime>> {
Some(vec![APPLICATION_JSON])
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema
{
fn schema() -> OpenapiSchema {
R::schema()
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
use crate::result::OrAllTypes;
use futures_executor::block_on;
use thiserror::Error;
#[derive(Debug, Default, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
struct Msg
{
msg : String
struct Msg {
msg: String
}
#[derive(Debug, Default, Error)]
#[error("An Error")]
struct MsgError;
#[test]
fn result_ok()
{
let ok : Result<Msg, MsgError> = Ok(Msg::default());
fn result_ok() {
let ok: Result<Msg, MsgError> = Ok(Msg::default());
let res = block_on(ok.into_response()).expect("didn't expect error response");
assert_eq!(res.status, StatusCode::OK);
assert_eq!(res.mime, Some(APPLICATION_JSON));
assert_eq!(res.full_body().unwrap(), r#"{"msg":""}"#.as_bytes());
}
#[test]
fn result_err()
{
let err : Result<Msg, MsgError> = Err(MsgError::default());
fn result_err() {
let err: Result<Msg, MsgError> = Err(MsgError::default());
let res = block_on(err.into_response()).expect("didn't expect error response");
assert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(res.mime, Some(APPLICATION_JSON));
assert_eq!(res.full_body().unwrap(), format!(r#"{{"error":true,"message":"{}"}}"#, MsgError::default()).as_bytes());
assert_eq!(
res.full_body().unwrap(),
format!(r#"{{"error":true,"message":"{}"}}"#, MsgError::default()).as_bytes()
);
}
#[test]
fn success_accepts_json()
{
assert!(<Result<Msg, MsgError>>::accepted_types().or_all_types().contains(&APPLICATION_JSON))
fn success_accepts_json() {
assert!(<Result<Msg, MsgError>>::accepted_types()
.or_all_types()
.contains(&APPLICATION_JSON))
}
}

View file

@ -1,14 +1,14 @@
use super::{ResourceResult, into_response_helper};
use crate::{Response, ResponseBody};
use super::{into_response_helper, ResourceResult};
#[cfg(feature = "openapi")]
use crate::OpenapiSchema;
use crate::{Response, ResponseBody};
use gotham::hyper::StatusCode;
use mime::{Mime, APPLICATION_JSON};
use std::{
fmt::Debug,
future::Future,
pin::Pin,
ops::{Deref, DerefMut}
ops::{Deref, DerefMut},
pin::Pin
};
/**
@ -45,119 +45,95 @@ fn read_all(_state: &mut State) -> Success<MyResponse> {
#[derive(Debug)]
pub struct Success<T>(T);
impl<T> AsMut<T> for Success<T>
{
fn as_mut(&mut self) -> &mut T
{
impl<T> AsMut<T> for Success<T> {
fn as_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> AsRef<T> for Success<T>
{
fn as_ref(&self) -> &T
{
impl<T> AsRef<T> for Success<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
impl<T> Deref for Success<T>
{
impl<T> Deref for Success<T> {
type Target = T;
fn deref(&self) -> &T
{
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for Success<T>
{
fn deref_mut(&mut self) -> &mut T
{
impl<T> DerefMut for Success<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> From<T> for Success<T>
{
fn from(t : T) -> Self
{
impl<T> From<T> for Success<T> {
fn from(t: T) -> Self {
Self(t)
}
}
impl<T : Clone> Clone for Success<T>
{
fn clone(&self) -> Self
{
impl<T: Clone> Clone for Success<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T : Copy> Copy for Success<T>
{
}
impl<T: Copy> Copy for Success<T> {}
impl<T : Default> Default for Success<T>
{
fn default() -> Self
{
impl<T: Default> Default for Success<T> {
fn default() -> Self {
Self(T::default())
}
}
impl<T : ResponseBody> ResourceResult for Success<T>
impl<T: ResponseBody> ResourceResult for Success<T>
where
Self : Send
Self: Send
{
type Err = serde_json::Error;
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>
{
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(self.as_ref())?)))
}
fn accepted_types() -> Option<Vec<Mime>>
{
fn accepted_types() -> Option<Vec<Mime>> {
Some(vec![APPLICATION_JSON])
}
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema
{
fn schema() -> OpenapiSchema {
T::schema()
}
}
#[cfg(test)]
mod test
{
mod test {
use super::*;
use crate::result::OrAllTypes;
use futures_executor::block_on;
#[derive(Debug, Default, Serialize)]
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
struct Msg
{
msg : String
struct Msg {
msg: String
}
#[test]
fn success_always_successfull()
{
let success : Success<Msg> = Msg::default().into();
fn success_always_successfull() {
let success: Success<Msg> = Msg::default().into();
let res = block_on(success.into_response()).expect("didn't expect error response");
assert_eq!(res.status, StatusCode::OK);
assert_eq!(res.mime, Some(APPLICATION_JSON));
assert_eq!(res.full_body().unwrap(), r#"{"msg":""}"#.as_bytes());
}
#[test]
fn success_accepts_json()
{
fn success_accepts_json() {
assert!(<Success<Msg>>::accepted_types().or_all_types().contains(&APPLICATION_JSON))
}
}

View file

@ -1,22 +1,21 @@
use crate::{
resource::*,
result::{ResourceError, ResourceResult},
RequestBody,
Response,
StatusCode
};
#[cfg(feature = "cors")]
use crate::CorsRoute;
#[cfg(feature = "openapi")]
use crate::openapi::{
builder::{OpenapiBuilder, OpenapiInfo},
router::OpenapiRouter
};
#[cfg(feature = "cors")]
use crate::CorsRoute;
use crate::{
resource::*,
result::{ResourceError, ResourceResult},
RequestBody, Response, StatusCode
};
use futures_util::{future, future::FutureExt};
use gotham::{
handler::{HandlerError, HandlerFuture, IntoHandlerError},
handler::{HandlerError, HandlerFuture},
helpers::http::response::{create_empty_response, create_response},
hyper::{body::to_bytes, header::CONTENT_TYPE, Body, HeaderMap, Method},
pipeline::chain::PipelineHandleChain,
router::{
builder::*,
@ -25,99 +24,84 @@ use gotham::{
},
state::{FromState, State}
};
use gotham::hyper::{
body::to_bytes,
header::CONTENT_TYPE,
Body,
HeaderMap,
Method
};
use mime::{Mime, APPLICATION_JSON};
use std::{
future::Future,
panic::RefUnwindSafe,
pin::Pin
};
use std::{future::Future, panic::RefUnwindSafe, pin::Pin};
/// Allow us to extract an id from a path.
#[derive(Deserialize, StateData, StaticResponseExtender)]
struct PathExtractor<ID : RefUnwindSafe + Send + 'static>
{
id : ID
struct PathExtractor<ID: RefUnwindSafe + Send + 'static> {
id: ID
}
/// 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.
#[cfg(feature = "openapi")]
pub trait WithOpenapi<D>
{
fn with_openapi<F>(&mut self, info : OpenapiInfo, block : F)
pub trait WithOpenapi<D> {
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
where
F : FnOnce(OpenapiRouter<'_, D>);
F: FnOnce(OpenapiRouter<'_, D>);
}
/// This trait adds the `resource` method to gotham's routing. It allows you to register
/// any RESTful `Resource` with a path.
pub trait DrawResources
{
fn resource<R : Resource>(&mut self, path : &str);
pub trait DrawResources {
fn resource<R: Resource>(&mut self, path: &str);
}
/// This trait allows to draw routes within an resource. Use this only inside the
/// `Resource::setup` method.
pub trait DrawResourceRoutes
{
fn read_all<Handler : ResourceReadAll>(&mut self);
fn read<Handler : ResourceRead>(&mut self);
fn search<Handler : ResourceSearch>(&mut self);
fn create<Handler : ResourceCreate>(&mut self)
pub trait DrawResourceRoutes {
fn read_all<Handler: ResourceReadAll>(&mut self);
fn read<Handler: ResourceRead>(&mut self);
fn search<Handler: ResourceSearch>(&mut self);
fn create<Handler: ResourceCreate>(&mut self)
where
Handler::Res : 'static,
Handler::Body : 'static;
fn change_all<Handler : ResourceChangeAll>(&mut self)
Handler::Res: 'static,
Handler::Body: 'static;
fn change_all<Handler: ResourceChangeAll>(&mut self)
where
Handler::Res : 'static,
Handler::Body : 'static;
fn change<Handler : ResourceChange>(&mut self)
Handler::Res: 'static,
Handler::Body: 'static;
fn change<Handler: ResourceChange>(&mut self)
where
Handler::Res : 'static,
Handler::Body : 'static;
fn remove_all<Handler : ResourceRemoveAll>(&mut self);
fn remove<Handler : ResourceRemove>(&mut self);
Handler::Res: 'static,
Handler::Body: 'static;
fn remove_all<Handler: ResourceRemoveAll>(&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> {
let mut r = create_empty_response(state, res.status);
if let Some(mime) = res.mime
{
if let Some(mime) = res.mime {
r.headers_mut().insert(CONTENT_TYPE, mime.as_ref().parse().unwrap());
}
let method = Method::borrow_from(state);
if method != Method::HEAD
{
if method != Method::HEAD {
*r.body_mut() = res.body;
}
#[cfg(feature = "cors")]
crate::cors::handle_cors(state, &mut r);
r
}
async fn to_handler_future<F, R>(state : State, get_result : F) -> Result<(State, gotham::hyper::Response<Body>), (State, HandlerError)>
async fn to_handler_future<F, R>(
state: State,
get_result: F
) -> Result<(State, gotham::hyper::Response<Body>), (State, HandlerError)>
where
F : FnOnce(State) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
R : ResourceResult
F: FnOnce(State) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
R: ResourceResult
{
let (state, res) = get_result(state).await;
let res = res.into_response().await;
@ -126,67 +110,70 @@ where
let r = response_from(res, &state);
Ok((state, r))
},
Err(e) => Err((state, e.into_handler_error()))
Err(e) => Err((state, e.into()))
}
}
async fn body_to_res<B, F, R>(mut state : State, get_result : F) -> (State, Result<gotham::hyper::Response<Body>, HandlerError>)
async fn body_to_res<B, F, R>(
mut state: State,
get_result: F
) -> (State, Result<gotham::hyper::Response<Body>, HandlerError>)
where
B : RequestBody,
F : FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
R : ResourceResult
B: RequestBody,
F: FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
R: ResourceResult
{
let body = to_bytes(Body::take_from(&mut state)).await;
let body = match body {
Ok(body) => body,
Err(e) => return (state, Err(e.into_handler_error()))
Err(e) => return (state, Err(e.into()))
};
let content_type : Mime = match HeaderMap::borrow_from(&state).get(CONTENT_TYPE) {
let content_type: Mime = match HeaderMap::borrow_from(&state).get(CONTENT_TYPE) {
Some(content_type) => content_type.to_str().unwrap().parse().unwrap(),
None => {
let res = create_empty_response(&state, StatusCode::UNSUPPORTED_MEDIA_TYPE);
return (state, Ok(res))
return (state, Ok(res));
}
};
let res = {
let body = match B::from_body(body, content_type) {
Ok(body) => body,
Err(e) => {
let error : ResourceError = e.into();
let error: ResourceError = e.into();
let res = match serde_json::to_string(&error) {
Ok(json) => {
let res = create_response(&state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json);
Ok(res)
},
Err(e) => Err(e.into_handler_error())
Err(e) => Err(e.into())
};
return (state, res)
return (state, res);
}
};
get_result(state, body)
};
let (state, res) = res.await;
let res = res.into_response().await;
let res = match res {
Ok(res) => {
let r = response_from(res, &state);
Ok(r)
},
Err(e) => Err(e.into_handler_error())
Err(e) => Err(e.into())
};
(state, res)
}
fn handle_with_body<B, F, R>(state : State, get_result : F) -> Pin<Box<HandlerFuture>>
fn handle_with_body<B, F, R>(state: State, get_result: F) -> Pin<Box<HandlerFuture>>
where
B : RequestBody + 'static,
F : FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>> + Send + 'static,
R : ResourceResult + Send + 'static
B: RequestBody + 'static,
F: FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>> + Send + 'static,
R: ResourceResult + Send + 'static
{
body_to_res(state, get_result)
.then(|(state, res)| match res {
@ -196,78 +183,70 @@ where
.boxed()
}
fn read_all_handler<Handler : ResourceReadAll>(state : State) -> Pin<Box<HandlerFuture>>
{
fn read_all_handler<Handler: ResourceReadAll>(state: State) -> Pin<Box<HandlerFuture>> {
to_handler_future(state, |state| Handler::read_all(state)).boxed()
}
fn read_handler<Handler : ResourceRead>(state : State) -> Pin<Box<HandlerFuture>>
{
fn read_handler<Handler: ResourceRead>(state: State) -> Pin<Box<HandlerFuture>> {
let id = {
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
path.id.clone()
};
to_handler_future(state, |state| Handler::read(state, id)).boxed()
}
fn search_handler<Handler : ResourceSearch>(mut state : State) -> Pin<Box<HandlerFuture>>
{
fn search_handler<Handler: ResourceSearch>(mut state: State) -> Pin<Box<HandlerFuture>> {
let query = Handler::Query::take_from(&mut state);
to_handler_future(state, |state| Handler::search(state, query)).boxed()
}
fn create_handler<Handler : ResourceCreate>(state : State) -> Pin<Box<HandlerFuture>>
fn create_handler<Handler: ResourceCreate>(state: State) -> Pin<Box<HandlerFuture>>
where
Handler::Res : 'static,
Handler::Body : 'static
Handler::Res: 'static,
Handler::Body: 'static
{
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::create(state, body))
}
fn change_all_handler<Handler : ResourceChangeAll>(state : State) -> Pin<Box<HandlerFuture>>
fn change_all_handler<Handler: ResourceChangeAll>(state: State) -> Pin<Box<HandlerFuture>>
where
Handler::Res : 'static,
Handler::Body : 'static
Handler::Res: 'static,
Handler::Body: 'static
{
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change_all(state, body))
}
fn change_handler<Handler : ResourceChange>(state : State) -> Pin<Box<HandlerFuture>>
fn change_handler<Handler: ResourceChange>(state: State) -> Pin<Box<HandlerFuture>>
where
Handler::Res : 'static,
Handler::Body : 'static
Handler::Res: 'static,
Handler::Body: 'static
{
let id = {
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
path.id.clone()
};
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change(state, id, body))
}
fn remove_all_handler<Handler : ResourceRemoveAll>(state : State) -> Pin<Box<HandlerFuture>>
{
fn remove_all_handler<Handler: ResourceRemoveAll>(state: State) -> Pin<Box<HandlerFuture>> {
to_handler_future(state, |state| Handler::remove_all(state)).boxed()
}
fn remove_handler<Handler : ResourceRemove>(state : State) -> Pin<Box<HandlerFuture>>
{
fn remove_handler<Handler: ResourceRemove>(state: State) -> Pin<Box<HandlerFuture>> {
let id = {
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
path.id.clone()
};
to_handler_future(state, |state| Handler::remove(state, id)).boxed()
}
#[derive(Clone)]
struct MaybeMatchAcceptHeader
{
matcher : Option<AcceptHeaderRouteMatcher>
struct MaybeMatchAcceptHeader {
matcher: Option<AcceptHeaderRouteMatcher>
}
impl RouteMatcher for MaybeMatchAcceptHeader
{
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
{
impl RouteMatcher for MaybeMatchAcceptHeader {
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
match &self.matcher {
Some(matcher) => matcher.is_match(state),
None => Ok(())
@ -275,10 +254,8 @@ impl RouteMatcher for MaybeMatchAcceptHeader
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader
{
fn from(types : Option<Vec<Mime>>) -> Self
{
impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
let types = match types {
Some(types) if types.is_empty() => None,
types => types
@ -290,15 +267,12 @@ impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader
}
#[derive(Clone)]
struct MaybeMatchContentTypeHeader
{
matcher : Option<ContentTypeHeaderRouteMatcher>
struct MaybeMatchContentTypeHeader {
matcher: Option<ContentTypeHeaderRouteMatcher>
}
impl RouteMatcher for MaybeMatchContentTypeHeader
{
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
{
impl RouteMatcher for MaybeMatchContentTypeHeader {
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
match &self.matcher {
Some(matcher) => matcher.is_match(state),
None => Ok(())
@ -306,10 +280,8 @@ impl RouteMatcher for MaybeMatchContentTypeHeader
}
}
impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader
{
fn from(types : Option<Vec<Mime>>) -> Self
{
impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader {
fn from(types: Option<Vec<Mime>>) -> Self {
Self {
matcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type())
}
@ -318,16 +290,15 @@ impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader
macro_rules! implDrawResourceRoutes {
($implType:ident) => {
#[cfg(feature = "openapi")]
impl<'a, C, P> WithOpenapi<Self> for $implType<'a, C, P>
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn with_openapi<F>(&mut self, info : OpenapiInfo, block : F)
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
where
F : FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)
F: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)
{
let router = OpenapiRouter {
router: self,
@ -337,58 +308,58 @@ macro_rules! implDrawResourceRoutes {
block(router);
}
}
impl<'a, C, P> DrawResources for $implType<'a, C, P>
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn resource<R : Resource>(&mut self, path : &str)
{
fn resource<R: Resource>(&mut self, path: &str) {
R::setup((self, path));
}
}
#[allow(clippy::redundant_closure)] // doesn't work because of type parameters
impl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str)
where
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P : RefUnwindSafe + Send + Sync + 'static
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static
{
fn read_all<Handler : ResourceReadAll>(&mut self)
{
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0.get(&self.1)
fn read_all<Handler: ResourceReadAll>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0
.get(&self.1)
.extend_route_matcher(matcher)
.to(|state| read_all_handler::<Handler>(state));
}
fn read<Handler : ResourceRead>(&mut self)
{
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0.get(&format!("{}/:id", self.1))
fn read<Handler: ResourceRead>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0
.get(&format!("{}/:id", self.1))
.extend_route_matcher(matcher)
.with_path_extractor::<PathExtractor<Handler::ID>>()
.to(|state| read_handler::<Handler>(state));
}
fn search<Handler : ResourceSearch>(&mut self)
{
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0.get(&format!("{}/search", self.1))
fn search<Handler: ResourceSearch>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0
.get(&format!("{}/search", self.1))
.extend_route_matcher(matcher)
.with_query_string_extractor::<Handler::Query>()
.to(|state| search_handler::<Handler>(state));
}
fn create<Handler : ResourceCreate>(&mut self)
fn create<Handler: ResourceCreate>(&mut self)
where
Handler::Res : Send + 'static,
Handler::Body : 'static
Handler::Res: Send + 'static,
Handler::Body: 'static
{
let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
self.0.post(&self.1)
let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
self.0
.post(&self.1)
.extend_route_matcher(accept_matcher)
.extend_route_matcher(content_matcher)
.to(|state| create_handler::<Handler>(state));
@ -396,14 +367,15 @@ macro_rules! implDrawResourceRoutes {
self.0.cors(&self.1, Method::POST);
}
fn change_all<Handler : ResourceChangeAll>(&mut self)
fn change_all<Handler: ResourceChangeAll>(&mut self)
where
Handler::Res : Send + 'static,
Handler::Body : 'static
Handler::Res: Send + 'static,
Handler::Body: 'static
{
let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
self.0.put(&self.1)
let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
self.0
.put(&self.1)
.extend_route_matcher(accept_matcher)
.extend_route_matcher(content_matcher)
.to(|state| change_all_handler::<Handler>(state));
@ -411,15 +383,16 @@ macro_rules! implDrawResourceRoutes {
self.0.cors(&self.1, Method::PUT);
}
fn change<Handler : ResourceChange>(&mut self)
fn change<Handler: ResourceChange>(&mut self)
where
Handler::Res : Send + 'static,
Handler::Body : 'static
Handler::Res: Send + 'static,
Handler::Body: 'static
{
let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
let path = format!("{}/:id", self.1);
self.0.put(&path)
self.0
.put(&path)
.extend_route_matcher(accept_matcher)
.extend_route_matcher(content_matcher)
.with_path_extractor::<PathExtractor<Handler::ID>>()
@ -428,21 +401,21 @@ macro_rules! implDrawResourceRoutes {
self.0.cors(&path, Method::PUT);
}
fn remove_all<Handler : ResourceRemoveAll>(&mut self)
{
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0.delete(&self.1)
fn remove_all<Handler: ResourceRemoveAll>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
self.0
.delete(&self.1)
.extend_route_matcher(matcher)
.to(|state| remove_all_handler::<Handler>(state));
#[cfg(feature = "cors")]
self.0.cors(&self.1, Method::DELETE);
}
fn remove<Handler : ResourceRemove>(&mut self)
{
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
fn remove<Handler: ResourceRemove>(&mut self) {
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
let path = format!("{}/:id", self.1);
self.0.delete(&path)
self.0
.delete(&path)
.extend_route_matcher(matcher)
.with_path_extractor::<PathExtractor<Handler::ID>>()
.to(|state| remove_handler::<Handler>(state));
@ -450,7 +423,7 @@ macro_rules! implDrawResourceRoutes {
self.0.cors(&path, Method::POST);
}
}
}
};
}
implDrawResourceRoutes!(RouterBuilder);

View file

@ -4,43 +4,26 @@ use crate::OpenapiType;
use gotham::hyper::body::Bytes;
use mime::{Mime, APPLICATION_JSON};
use serde::{de::DeserializeOwned, Serialize};
use std::{
error::Error,
panic::RefUnwindSafe
};
use std::{error::Error, panic::RefUnwindSafe};
#[cfg(not(feature = "openapi"))]
pub trait ResourceType
{
}
pub trait ResourceType {}
#[cfg(not(feature = "openapi"))]
impl<T> ResourceType for T
{
}
impl<T> ResourceType for T {}
#[cfg(feature = "openapi")]
pub trait ResourceType : OpenapiType
{
}
pub trait ResourceType: OpenapiType {}
#[cfg(feature = "openapi")]
impl<T : OpenapiType> ResourceType for T
{
}
impl<T: OpenapiType> ResourceType for T {}
/// 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`.
pub trait ResponseBody : ResourceType + Serialize
{
}
impl<T : ResourceType + Serialize> ResponseBody for T
{
}
pub trait ResponseBody: ResourceType + Serialize {}
impl<T: ResourceType + Serialize> ResponseBody for T {}
/**
This trait should be implemented for every type that can be built from an HTTP request body
@ -64,28 +47,24 @@ struct RawImage {
[`Bytes`]: ../bytes/struct.Bytes.html
[`Mime`]: ../mime/struct.Mime.html
*/
pub trait FromBody : Sized
{
pub trait FromBody: Sized {
/// The error type returned by the conversion if it was unsuccessfull. When using the derive
/// macro, there is no way to trigger an error, so `Infallible` is used here. However, this
/// might change in the future.
type Err : Error;
type Err: Error;
/// Perform the conversion.
fn from_body(body : Bytes, content_type : Mime) -> Result<Self, Self::Err>;
fn from_body(body: Bytes, content_type: Mime) -> Result<Self, Self::Err>;
}
impl<T : DeserializeOwned> FromBody for T
{
impl<T: DeserializeOwned> FromBody for T {
type Err = serde_json::Error;
fn from_body(body : Bytes, _content_type : Mime) -> Result<Self, Self::Err>
{
fn from_body(body: Bytes, _content_type: Mime) -> Result<Self, Self::Err> {
serde_json::from_slice(&body)
}
}
/**
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`].
@ -108,19 +87,15 @@ struct RawImage {
[`FromBody`]: trait.FromBody.html
[`OpenapiType`]: trait.OpenapiType.html
*/
pub trait RequestBody : ResourceType + FromBody
{
pub trait RequestBody: ResourceType + FromBody {
/// Return all types that are supported as content types. Use `None` if all types are supported.
fn supported_types() -> Option<Vec<Mime>>
{
fn supported_types() -> Option<Vec<Mime>> {
None
}
}
impl<T : ResourceType + DeserializeOwned> RequestBody for T
{
fn supported_types() -> Option<Vec<Mime>>
{
impl<T: ResourceType + DeserializeOwned> RequestBody for T {
fn supported_types() -> Option<Vec<Mime>> {
Some(vec![APPLICATION_JSON])
}
}
@ -128,10 +103,6 @@ impl<T : ResourceType + DeserializeOwned> RequestBody for T
/// 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`.
pub trait ResourceID : ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync
{
}
pub trait ResourceID: ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync {}
impl<T : ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync> ResourceID for T
{
}
impl<T: ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync> ResourceID for T {}