mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-04-20 14:57:01 +00:00
Allow custom error types through a macro and allow them to be used with Result
This commit is contained in:
parent
8593e133b7
commit
d754d6044d
19 changed files with 1165 additions and 751 deletions
|
@ -52,7 +52,7 @@ fn main() {
|
|||
}
|
||||
```
|
||||
|
||||
Uploads and Downloads can also be handled, but you need to specify the mime type manually:
|
||||
Uploads and Downloads can also be handled:
|
||||
|
||||
```rust,no_run
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
|
@ -131,6 +131,8 @@ pub mod export
|
|||
{
|
||||
pub use futures_util::future::FutureExt;
|
||||
|
||||
pub use serde_json;
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
pub use gotham_middleware_diesel::Repo;
|
||||
|
||||
|
@ -176,14 +178,20 @@ pub use resource::{
|
|||
ResourceDelete
|
||||
};
|
||||
|
||||
mod response;
|
||||
pub use response::Response;
|
||||
|
||||
mod result;
|
||||
pub use result::{
|
||||
AuthError,
|
||||
AuthError::Forbidden,
|
||||
AuthErrorOrOther,
|
||||
AuthResult,
|
||||
AuthResult::AuthErr,
|
||||
AuthSuccess,
|
||||
IntoResponseError,
|
||||
NoContent,
|
||||
Raw,
|
||||
ResourceResult,
|
||||
Response,
|
||||
Success
|
||||
};
|
||||
|
||||
|
|
63
gotham_restful/src/response.rs
Normal file
63
gotham_restful/src/response.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use gotham::hyper::{Body, StatusCode};
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
|
||||
/// A response, used to create the final gotham response from.
|
||||
pub struct Response
|
||||
{
|
||||
pub status : StatusCode,
|
||||
pub body : Body,
|
||||
pub mime : Option<Mime>
|
||||
}
|
||||
|
||||
impl Response
|
||||
{
|
||||
/// Create a new `Response` from raw data.
|
||||
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
|
||||
{
|
||||
Self {
|
||||
status,
|
||||
body: body.into(),
|
||||
mime: Some(APPLICATION_JSON)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a _204 No Content_ `Response`.
|
||||
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
|
||||
{
|
||||
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>
|
||||
{
|
||||
use futures_executor::block_on;
|
||||
use gotham::hyper::body::to_bytes;
|
||||
|
||||
let bytes : &[u8] = &block_on(to_bytes(&mut self.body))?;
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
}
|
|
@ -1,704 +0,0 @@
|
|||
use crate::{ResponseBody, StatusCode};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::{OpenapiSchema, OpenapiType};
|
||||
use futures_core::future::Future;
|
||||
use futures_util::{future, future::FutureExt};
|
||||
use gotham::hyper::Body;
|
||||
#[cfg(feature = "errorlog")]
|
||||
use log::error;
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
#[cfg(feature = "openapi")]
|
||||
use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
|
||||
use serde::Serialize;
|
||||
use serde_json::error::Error as SerdeJsonError;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::Debug,
|
||||
pin::Pin
|
||||
};
|
||||
|
||||
/// A response, used to create the final gotham response from.
|
||||
pub struct Response
|
||||
{
|
||||
pub status : StatusCode,
|
||||
pub body : Body,
|
||||
pub mime : Option<Mime>
|
||||
}
|
||||
|
||||
impl Response
|
||||
{
|
||||
/// Create a new `Response` from raw data.
|
||||
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
|
||||
{
|
||||
Self {
|
||||
status,
|
||||
body: body.into(),
|
||||
mime: Some(APPLICATION_JSON)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a _204 No Content_ `Response`.
|
||||
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
|
||||
{
|
||||
Self {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
body: Body::empty(),
|
||||
mime: None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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))?;
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A trait provided to convert a resource's result to json.
|
||||
pub trait ResourceResult
|
||||
{
|
||||
type Err : Error + Send + '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>>
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema;
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode
|
||||
{
|
||||
StatusCode::OK
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
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 struct ResourceError
|
||||
{
|
||||
error : bool,
|
||||
message : String
|
||||
}
|
||||
|
||||
impl<T : ToString> From<T> for ResourceError
|
||||
{
|
||||
fn from(message : T) -> Self
|
||||
{
|
||||
Self {
|
||||
error: true,
|
||||
message: message.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
{
|
||||
let res = create_response();
|
||||
async move { res }.boxed()
|
||||
}
|
||||
|
||||
#[cfg(feature = "errorlog")]
|
||||
fn errorlog<E : std::fmt::Display>(e : E)
|
||||
{
|
||||
error!("The handler encountered an error: {}", e);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "errorlog"))]
|
||||
fn errorlog<E>(_e : E) {}
|
||||
|
||||
impl<R : ResponseBody, E : Error> ResourceResult for Result<R, E>
|
||||
where
|
||||
Self : Send
|
||||
{
|
||||
type Err = SerdeJsonError;
|
||||
|
||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>>
|
||||
{
|
||||
into_response_helper(|| {
|
||||
Ok(match self {
|
||||
Ok(r) => Response::json(StatusCode::OK, serde_json::to_string(&r)?),
|
||||
Err(e) => {
|
||||
errorlog(&e);
|
||||
let err : ResourceError = e.into();
|
||||
Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
Some(vec![APPLICATION_JSON])
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
R::schema()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<Res> ResourceResult for Pin<Box<dyn Future<Output = Res> + Send>>
|
||||
where
|
||||
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 accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
Res::accepted_types()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
Res::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode
|
||||
{
|
||||
Res::default_status()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
This can be returned from a resource when there is no cause of an error. For example:
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# mod doc_tests_are_broken {
|
||||
# use gotham::state::State;
|
||||
# use gotham_restful::*;
|
||||
# use serde::{Deserialize, Serialize};
|
||||
#
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
#
|
||||
#[derive(Deserialize, Serialize)]
|
||||
# #[derive(OpenapiType)]
|
||||
struct MyResponse {
|
||||
message: String
|
||||
}
|
||||
|
||||
#[rest_read_all(MyResource)]
|
||||
fn read_all(_state: &mut State) -> Success<MyResponse> {
|
||||
let res = MyResponse { message: "I'm always happy".to_string() };
|
||||
res.into()
|
||||
}
|
||||
# }
|
||||
```
|
||||
*/
|
||||
pub struct Success<T>(T);
|
||||
|
||||
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
|
||||
{
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for Success<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Success({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : ResponseBody> ResourceResult for Success<T>
|
||||
where
|
||||
Self : Send
|
||||
{
|
||||
type Err = SerdeJsonError;
|
||||
|
||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>>
|
||||
{
|
||||
into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(&self.0)?)))
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
Some(vec![APPLICATION_JSON])
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
T::schema()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This return type can be used to map another `ResourceResult` that can only be returned if the
|
||||
client is authenticated. Otherwise, an empty _403 Forbidden_ response will be issued. Use can
|
||||
look something like this (assuming the `auth` feature is enabled):
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# mod doc_tests_are_broken {
|
||||
# use gotham::state::State;
|
||||
# use gotham_restful::*;
|
||||
# use serde::Deserialize;
|
||||
#
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
#
|
||||
# #[derive(Clone, Deserialize)]
|
||||
# struct MyAuthData { exp : u64 }
|
||||
#
|
||||
#[rest_read_all(MyResource)]
|
||||
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthResult<NoContent> {
|
||||
let auth_data = match auth {
|
||||
AuthStatus::Authenticated(data) => data,
|
||||
_ => return AuthErr
|
||||
};
|
||||
// do something
|
||||
NoContent::default().into()
|
||||
}
|
||||
# }
|
||||
```
|
||||
*/
|
||||
pub enum AuthResult<T>
|
||||
{
|
||||
Ok(T),
|
||||
AuthErr
|
||||
}
|
||||
|
||||
impl<T> AuthResult<T>
|
||||
{
|
||||
pub fn is_ok(&self) -> bool
|
||||
{
|
||||
match self {
|
||||
Self::Ok(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap(self) -> T
|
||||
{
|
||||
match self {
|
||||
Self::Ok(data) => data,
|
||||
_ => panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for AuthResult<T>
|
||||
{
|
||||
fn from(t : T) -> Self
|
||||
{
|
||||
Self::Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Clone> Clone for AuthResult<T>
|
||||
{
|
||||
fn clone(&self) -> Self
|
||||
{
|
||||
match self {
|
||||
Self::Ok(t) => Self::Ok(t.clone()),
|
||||
Self::AuthErr => Self::AuthErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for AuthResult<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Ok(t) => write!(f, "Ok({:?})", t),
|
||||
Self::AuthErr => write!(f, "AuthErr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : ResourceResult> ResourceResult for AuthResult<T>
|
||||
{
|
||||
type Err = T::Err;
|
||||
|
||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>
|
||||
{
|
||||
match self
|
||||
{
|
||||
Self::Ok(res) => res.into_response(),
|
||||
Self::AuthErr => future::ok(Response::forbidden()).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
T::accepted_types()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
T::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode
|
||||
{
|
||||
T::default_status()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : ResourceResult<Err = SerdeJsonError>, E : Error> ResourceResult for Result<AuthResult<T>, E>
|
||||
{
|
||||
type Err = T::Err;
|
||||
|
||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, T::Err>> + Send>>
|
||||
{
|
||||
match self {
|
||||
Ok(r) => r.into_response(),
|
||||
Err(e) => {
|
||||
into_response_helper(|| {
|
||||
errorlog(&e);
|
||||
let err : ResourceError = e.into();
|
||||
Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
T::accepted_types()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
T::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode
|
||||
{
|
||||
T::default_status()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This is the return type of a resource that doesn't actually return something. It will result
|
||||
in a _204 No Content_ answer by default. You don't need to use this type directly if using
|
||||
the function attributes:
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# mod doc_tests_are_broken {
|
||||
# use gotham::state::State;
|
||||
# use gotham_restful::*;
|
||||
#
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
#
|
||||
#[rest_read_all(MyResource)]
|
||||
fn read_all(_state: &mut State) {
|
||||
// do something
|
||||
}
|
||||
# }
|
||||
```
|
||||
*/
|
||||
#[derive(Default)]
|
||||
pub struct NoContent;
|
||||
|
||||
impl From<()> for NoContent
|
||||
{
|
||||
fn from(_ : ()) -> Self
|
||||
{
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceResult for NoContent
|
||||
{
|
||||
type Err = SerdeJsonError; // 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>>
|
||||
{
|
||||
future::ok(Response::no_content()).boxed()
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
Some(Vec::new())
|
||||
}
|
||||
|
||||
/// Returns the schema of the `()` type.
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
<()>::schema()
|
||||
}
|
||||
|
||||
/// This will always be a _204 No Content_
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode
|
||||
{
|
||||
StatusCode::NO_CONTENT
|
||||
}
|
||||
}
|
||||
|
||||
impl<E : Error> ResourceResult for Result<NoContent, E>
|
||||
where
|
||||
Self : Send
|
||||
{
|
||||
type Err = SerdeJsonError;
|
||||
|
||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>>
|
||||
{
|
||||
match self {
|
||||
Ok(nc) => nc.into_response(),
|
||||
Err(e) => into_response_helper(|| {
|
||||
let err : ResourceError = e.into();
|
||||
Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
NoContent::accepted_types()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
<NoContent as ResourceResult>::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode
|
||||
{
|
||||
NoContent::default_status()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Raw<T>
|
||||
{
|
||||
pub raw : T,
|
||||
pub mime : Mime
|
||||
}
|
||||
|
||||
impl<T> Raw<T>
|
||||
{
|
||||
pub fn new(raw : T, mime : Mime) -> Self
|
||||
{
|
||||
Self { raw, mime }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Clone> Clone for Raw<T>
|
||||
{
|
||||
fn clone(&self) -> Self
|
||||
{
|
||||
Self {
|
||||
raw: self.raw.clone(),
|
||||
mime: self.mime.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for Raw<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Raw({:?}, {:?})", self.raw, self.mime)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Into<Body>> ResourceResult for Raw<T>
|
||||
where
|
||||
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>>
|
||||
{
|
||||
future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
|
||||
format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E : Error> ResourceResult for Result<Raw<T>, E>
|
||||
where
|
||||
Self : Send,
|
||||
Raw<T> : ResourceResult<Err = SerdeJsonError>
|
||||
{
|
||||
type Err = SerdeJsonError;
|
||||
|
||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>>
|
||||
{
|
||||
match self {
|
||||
Ok(raw) => raw.into_response(),
|
||||
Err(e) => into_response_helper(|| {
|
||||
let err : ResourceError = e.into();
|
||||
Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
<Raw<T> as ResourceResult>::accepted_types()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
<Raw<T> as ResourceResult>::schema()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test
|
||||
{
|
||||
use super::*;
|
||||
use futures_executor::block_on;
|
||||
use mime::TEXT_PLAIN;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||
struct Msg
|
||||
{
|
||||
msg : String
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Error)]
|
||||
#[error("An Error")]
|
||||
struct MsgError;
|
||||
|
||||
#[test]
|
||||
fn resource_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 resource_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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 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());
|
||||
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 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");
|
||||
assert_eq!(res.status, StatusCode::OK);
|
||||
assert_eq!(res.mime, Some(TEXT_PLAIN));
|
||||
assert_eq!(res.full_body().unwrap(), msg.as_bytes());
|
||||
}
|
||||
}
|
107
gotham_restful/src/result/auth_result.rs
Normal file
107
gotham_restful/src/result/auth_result.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
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`].
|
||||
|
||||
[`AuthSuccess`]: type.AuthSuccess.html
|
||||
[`AuthResult`]: type.AuthResult.html
|
||||
*/
|
||||
#[derive(ResourceError)]
|
||||
pub enum AuthError
|
||||
{
|
||||
#[status(FORBIDDEN)]
|
||||
#[display("Forbidden")]
|
||||
Forbidden
|
||||
}
|
||||
|
||||
/**
|
||||
This return type can be used to map another `ResourceResult` that can only be returned if the
|
||||
client is authenticated. Otherwise, an empty _403 Forbidden_ response will be issued. Use can
|
||||
look something like this (assuming the `auth` feature is enabled):
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# mod doc_tests_are_broken {
|
||||
# use gotham::state::State;
|
||||
# use gotham_restful::*;
|
||||
# use serde::Deserialize;
|
||||
#
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
#
|
||||
# #[derive(Clone, Deserialize)]
|
||||
# struct MyAuthData { exp : u64 }
|
||||
#
|
||||
#[rest_read_all(MyResource)]
|
||||
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthSuccess<NoContent> {
|
||||
let auth_data = match auth {
|
||||
AuthStatus::Authenticated(data) => data,
|
||||
_ => return Err(Forbidden)
|
||||
};
|
||||
// do something
|
||||
Ok(NoContent::default())
|
||||
}
|
||||
# }
|
||||
```
|
||||
*/
|
||||
pub type AuthSuccess<T> = Result<T, AuthError>;
|
||||
|
||||
/**
|
||||
This is an error type that either yields a _403 Forbidden_ respone if produced from an authentication
|
||||
error, or delegates to another error type. This type is best used with [`AuthResult`].
|
||||
|
||||
[`AuthResult`]: type.AuthResult.html
|
||||
*/
|
||||
#[derive(ResourceError)]
|
||||
pub enum AuthErrorOrOther<E>
|
||||
{
|
||||
#[status(UNAUTHORIZED)]
|
||||
#[display("Forbidden")]
|
||||
Forbidden,
|
||||
#[display("{0}")]
|
||||
Other(#[from] E)
|
||||
}
|
||||
|
||||
impl<E> From<AuthError> for AuthErrorOrOther<E>
|
||||
{
|
||||
fn from(err : AuthError) -> Self
|
||||
{
|
||||
match err {
|
||||
AuthError::Forbidden => Self::Forbidden
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This return type can be used to map another `ResourceResult` that can only be returned if the
|
||||
client is authenticated. Otherwise, an empty _403 Forbidden_ response will be issued. Use can
|
||||
look something like this (assuming the `auth` feature is enabled):
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# mod doc_tests_are_broken {
|
||||
# use gotham::state::State;
|
||||
# use gotham_restful::*;
|
||||
# use serde::Deserialize;
|
||||
# use std::io;
|
||||
#
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
#
|
||||
# #[derive(Clone, Deserialize)]
|
||||
# struct MyAuthData { exp : u64 }
|
||||
#
|
||||
#[rest_read_all(MyResource)]
|
||||
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthResult<NoContent, io::Error> {
|
||||
let auth_data = match auth {
|
||||
AuthStatus::Authenticated(data) => data,
|
||||
_ => Err(Forbidden)?
|
||||
};
|
||||
// do something
|
||||
Ok(NoContent::default().into())
|
||||
}
|
||||
# }
|
||||
*/
|
||||
pub type AuthResult<T, E> = Result<T, AuthErrorOrOther<E>>;
|
224
gotham_restful/src/result/mod.rs
Normal file
224
gotham_restful/src/result/mod.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
use crate::Response;
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::OpenapiSchema;
|
||||
use futures_util::future::FutureExt;
|
||||
use mime::Mime;
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
error::Error,
|
||||
future::Future,
|
||||
fmt::{Debug, Display},
|
||||
pin::Pin
|
||||
};
|
||||
|
||||
mod auth_result;
|
||||
pub use auth_result::{AuthError, AuthErrorOrOther, AuthResult, AuthSuccess};
|
||||
|
||||
mod no_content;
|
||||
pub use no_content::NoContent;
|
||||
|
||||
mod raw;
|
||||
pub use raw::Raw;
|
||||
|
||||
mod result;
|
||||
pub use result::IntoResponseError;
|
||||
|
||||
mod success;
|
||||
pub use success::Success;
|
||||
|
||||
/// A trait provided to convert a resource's result to json.
|
||||
pub trait ResourceResult
|
||||
{
|
||||
type Err : Error + Send + '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>>
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema;
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> crate::StatusCode
|
||||
{
|
||||
crate::StatusCode::OK
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
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
|
||||
}
|
||||
|
||||
impl<T : ToString> From<T> for ResourceError
|
||||
{
|
||||
fn from(message : T) -> Self
|
||||
{
|
||||
Self {
|
||||
error: true,
|
||||
message: message.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
{
|
||||
let res = create_response();
|
||||
async move { res }.boxed()
|
||||
}
|
||||
|
||||
#[cfg(feature = "errorlog")]
|
||||
fn errorlog<E : Display>(e : E)
|
||||
{
|
||||
error!("The handler encountered an error: {}", e);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "errorlog"))]
|
||||
fn errorlog<E>(_e : E) {}
|
||||
|
||||
fn handle_error<E>(e : E) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
|
||||
where
|
||||
E : Display + IntoResponseError
|
||||
{
|
||||
into_response_helper(|| {
|
||||
errorlog(&e);
|
||||
e.into_response_error()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
impl<Res> ResourceResult for Pin<Box<dyn Future<Output = Res> + Send>>
|
||||
where
|
||||
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 accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
Res::accepted_types()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
Res::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> crate::StatusCode
|
||||
{
|
||||
Res::default_status()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test
|
||||
{
|
||||
use super::*;
|
||||
use crate::{OpenapiType, StatusCode};
|
||||
use futures_executor::block_on;
|
||||
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||
struct Msg
|
||||
{
|
||||
msg : String
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Error)]
|
||||
#[error("An Error")]
|
||||
struct MsgError;
|
||||
|
||||
#[test]
|
||||
fn resource_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 resource_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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 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());
|
||||
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 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");
|
||||
assert_eq!(res.status, StatusCode::OK);
|
||||
assert_eq!(res.mime, Some(TEXT_PLAIN));
|
||||
assert_eq!(res.full_body().unwrap(), msg.as_bytes());
|
||||
}
|
||||
}
|
106
gotham_restful/src/result/no_content.rs
Normal file
106
gotham_restful/src/result/no_content.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use super::{ResourceResult, handle_error};
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
This is the return type of a resource that doesn't actually return something. It will result
|
||||
in a _204 No Content_ answer by default. You don't need to use this type directly if using
|
||||
the function attributes:
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# mod doc_tests_are_broken {
|
||||
# use gotham::state::State;
|
||||
# use gotham_restful::*;
|
||||
#
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
#
|
||||
#[rest_read_all(MyResource)]
|
||||
fn read_all(_state: &mut State) {
|
||||
// do something
|
||||
}
|
||||
# }
|
||||
```
|
||||
*/
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct NoContent;
|
||||
|
||||
impl From<()> for NoContent
|
||||
{
|
||||
fn from(_ : ()) -> Self
|
||||
{
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
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>>
|
||||
{
|
||||
future::ok(Response::no_content()).boxed()
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
Some(Vec::new())
|
||||
}
|
||||
|
||||
/// Returns the schema of the `()` type.
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
<()>::schema()
|
||||
}
|
||||
|
||||
/// This will always be a _204 No Content_
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> crate::StatusCode
|
||||
{
|
||||
crate::StatusCode::NO_CONTENT
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> ResourceResult for Result<NoContent, E>
|
||||
where
|
||||
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>>
|
||||
{
|
||||
match self {
|
||||
Ok(nc) => nc.into_response(),
|
||||
Err(e) => handle_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
NoContent::accepted_types()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
<NoContent as ResourceResult>::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> crate::StatusCode
|
||||
{
|
||||
NoContent::default_status()
|
||||
}
|
||||
}
|
90
gotham_restful/src/result/raw.rs
Normal file
90
gotham_restful/src/result/raw.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use super::{IntoResponseError, ResourceResult, handle_error};
|
||||
use crate::{Response, StatusCode};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::OpenapiSchema;
|
||||
use futures_core::future::Future;
|
||||
use futures_util::{future, future::FutureExt};
|
||||
use gotham::hyper::Body;
|
||||
use mime::Mime;
|
||||
#[cfg(feature = "openapi")]
|
||||
use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
|
||||
use serde_json::error::Error as SerdeJsonError;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
pin::Pin
|
||||
};
|
||||
|
||||
pub struct Raw<T>
|
||||
{
|
||||
pub raw : T,
|
||||
pub mime : Mime
|
||||
}
|
||||
|
||||
impl<T> Raw<T>
|
||||
{
|
||||
pub fn new(raw : T, mime : Mime) -> Self
|
||||
{
|
||||
Self { raw, mime }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Clone> Clone for Raw<T>
|
||||
{
|
||||
fn clone(&self) -> Self
|
||||
{
|
||||
Self {
|
||||
raw: self.raw.clone(),
|
||||
mime: self.mime.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for Raw<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Raw({:?}, {:?})", self.raw, self.mime)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Into<Body>> ResourceResult for Raw<T>
|
||||
where
|
||||
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>>
|
||||
{
|
||||
future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
|
||||
format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> ResourceResult for Result<Raw<T>, E>
|
||||
where
|
||||
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>>
|
||||
{
|
||||
match self {
|
||||
Ok(raw) => raw.into_response(),
|
||||
Err(e) => handle_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
<Raw<T> as ResourceResult>::schema()
|
||||
}
|
||||
}
|
59
gotham_restful/src/result/result.rs
Normal file
59
gotham_restful/src/result/result.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use super::{ResourceResult, handle_error, into_response_helper};
|
||||
use crate::{
|
||||
result::ResourceError,
|
||||
Response, ResponseBody, StatusCode
|
||||
};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::OpenapiSchema;
|
||||
use futures_core::future::Future;
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
pin::Pin
|
||||
};
|
||||
|
||||
pub trait IntoResponseError
|
||||
{
|
||||
type Err : Error + Send + 'static;
|
||||
|
||||
fn into_response_error(self) -> Result<Response, Self::Err>;
|
||||
}
|
||||
|
||||
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)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, E> ResourceResult for Result<R, E>
|
||||
where
|
||||
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>>
|
||||
{
|
||||
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>>
|
||||
{
|
||||
Some(vec![APPLICATION_JSON])
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
R::schema()
|
||||
}
|
||||
}
|
136
gotham_restful/src/result/success.rs
Normal file
136
gotham_restful/src/result/success.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use super::{ResourceResult, into_response_helper};
|
||||
use crate::{Response, ResponseBody};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::OpenapiSchema;
|
||||
use gotham::hyper::StatusCode;
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
ops::{Deref, DerefMut}
|
||||
};
|
||||
|
||||
/**
|
||||
This can be returned from a resource when there is no cause of an error. It behaves similar to a
|
||||
smart pointer like box, it that it implements `AsRef`, `Deref` and the likes.
|
||||
|
||||
Usage example:
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# mod doc_tests_are_broken {
|
||||
# use gotham::state::State;
|
||||
# use gotham_restful::*;
|
||||
# use serde::{Deserialize, Serialize};
|
||||
#
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
#
|
||||
#[derive(Deserialize, Serialize)]
|
||||
# #[derive(OpenapiType)]
|
||||
struct MyResponse {
|
||||
message: &'static str
|
||||
}
|
||||
|
||||
#[rest_read_all(MyResource)]
|
||||
fn read_all(_state: &mut State) -> Success<MyResponse> {
|
||||
let res = MyResponse { message: "I'm always happy" };
|
||||
res.into()
|
||||
}
|
||||
# }
|
||||
```
|
||||
*/
|
||||
pub struct Success<T>(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
|
||||
{
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Success<T>
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T
|
||||
{
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
Self(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Clone> Clone for Success<T>
|
||||
{
|
||||
fn clone(&self) -> Self
|
||||
{
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Copy> Copy for Success<T>
|
||||
{
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for Success<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Success({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Default> Default for Success<T>
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : ResponseBody> ResourceResult for Success<T>
|
||||
where
|
||||
Self : Send
|
||||
{
|
||||
type Err = serde_json::Error;
|
||||
|
||||
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>>
|
||||
{
|
||||
Some(vec![APPLICATION_JSON])
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
T::schema()
|
||||
}
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
use crate::{
|
||||
matcher::{AcceptHeaderMatcher, ContentTypeMatcher},
|
||||
openapi::router::OpenapiRouter,
|
||||
resource::*,
|
||||
result::{ResourceError, ResourceResult, Response},
|
||||
result::{ResourceError, ResourceResult},
|
||||
RequestBody,
|
||||
Response,
|
||||
StatusCode
|
||||
};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::openapi::builder::OpenapiBuilder;
|
||||
use crate::openapi::{
|
||||
builder::OpenapiBuilder,
|
||||
router::OpenapiRouter
|
||||
};
|
||||
|
||||
use futures_util::{future, future::FutureExt};
|
||||
use gotham::{
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
#[cfg(feature = "openapi")]
|
||||
use crate::OpenapiType;
|
||||
use crate::result::ResourceError;
|
||||
|
||||
use gotham::hyper::body::Bytes;
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::{
|
||||
error::Error,
|
||||
panic::RefUnwindSafe
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(not(feature = "openapi"))]
|
||||
pub trait ResourceType
|
||||
|
@ -44,7 +47,7 @@ impl<T : ResourceType + Serialize> ResponseBody for T
|
|||
/// to create the type from a hyper body chunk and it's content type.
|
||||
pub trait FromBody : Sized
|
||||
{
|
||||
type Err : Into<ResourceError>;
|
||||
type Err : Error;
|
||||
|
||||
/// Create the request body from a raw body and the content type.
|
||||
fn from_body(body : Bytes, content_type : Mime) -> Result<Self, Self::Err>;
|
||||
|
@ -60,6 +63,14 @@ impl<T : DeserializeOwned> FromBody for T
|
|||
}
|
||||
}
|
||||
|
||||
/// This error type can be used by `FromBody` implementations when there is no need to return any
|
||||
/// errors.
|
||||
|
||||
#[derive(Clone, Copy, Debug, Error)]
|
||||
#[error("No Error")]
|
||||
pub struct FromBodyNoError;
|
||||
|
||||
|
||||
/// 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`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue