mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-22 12:42:28 +00:00
Custom HTTP Headers
This commit is contained in:
parent
28ae4dfdee
commit
31f92c07cd
18 changed files with 475 additions and 416 deletions
|
@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- All fields of `Response` are now private
|
||||
- If not enabling the `openapi` feature, `without-openapi` has to be enabled
|
||||
- The endpoint macro attributes (`read`, `create`, ...) no longer take the resource ident and reject all unknown attributes ([!18])
|
||||
- The `ResourceResult` trait has been split into `ResourceResult` and `ResourceResultSchema`
|
||||
- The `ResourceResult` trait has been split into `IntoResponse` and `ResponseSchema`
|
||||
- `HashMap`'s keys are included in the generated OpenAPI spec (they defaulted to `type: string` previously)
|
||||
|
||||
### Removed
|
||||
|
|
21
README.md
21
README.md
|
@ -148,6 +148,27 @@ fn create(body : RawImage) -> Raw<Vec<u8>> {
|
|||
}
|
||||
```
|
||||
|
||||
## Custom HTTP Headers
|
||||
|
||||
You can read request headers from the state as you would in any other gotham handler, and specify
|
||||
custom response headers using [Response::header].
|
||||
|
||||
```rust
|
||||
#[derive(Resource)]
|
||||
#[resource(read_all)]
|
||||
struct FooResource;
|
||||
|
||||
#[read_all]
|
||||
async fn read_all(state: &mut State) -> NoContent {
|
||||
let headers: &HeaderMap = state.borrow();
|
||||
let accept = &headers[ACCEPT];
|
||||
|
||||
let mut res = NoContent::default();
|
||||
res.header(VARY, "accept".parse().unwrap());
|
||||
res
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
To make life easier for common use-cases, this create offers a few features that might be helpful
|
||||
|
|
|
@ -487,7 +487,7 @@ fn expand_endpoint_type(mut ty: EndpointType, attrs: AttributeArgs, fun: &ItemFn
|
|||
handle_content = quote!(#handle_content.await);
|
||||
}
|
||||
if is_no_content {
|
||||
handle_content = quote!(#handle_content; ::gotham_restful::NoContent)
|
||||
handle_content = quote!(#handle_content; <::gotham_restful::NoContent as ::std::default::Default>::default())
|
||||
}
|
||||
|
||||
if let Some(arg) = args.iter().find(|arg| arg.ty.is_database_conn()) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{RequestBody, ResourceResult};
|
||||
use crate::{IntoResponse, RequestBody};
|
||||
use futures_util::future::BoxFuture;
|
||||
use gotham::{
|
||||
extractor::{PathExtractor, QueryStringExtractor},
|
||||
|
@ -16,8 +16,8 @@ pub trait Endpoint {
|
|||
fn uri() -> Cow<'static, str>;
|
||||
|
||||
/// The output type that provides the response.
|
||||
#[openapi_bound("Output: crate::ResourceResultSchema")]
|
||||
type Output: ResourceResult + Send;
|
||||
#[openapi_bound("Output: crate::ResponseSchema")]
|
||||
type Output: IntoResponse + Send;
|
||||
|
||||
/// Returns `true` _iff_ the URI contains placeholders. `false` by default.
|
||||
fn has_placeholders() -> bool {
|
||||
|
|
44
src/lib.rs
44
src/lib.rs
|
@ -158,6 +158,37 @@ fn create(body : RawImage) -> Raw<Vec<u8>> {
|
|||
# }
|
||||
```
|
||||
|
||||
# Custom HTTP Headers
|
||||
|
||||
You can read request headers from the state as you would in any other gotham handler, and specify
|
||||
custom response headers using [Response::header].
|
||||
|
||||
```rust,no_run
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
# use gotham::hyper::header::{ACCEPT, HeaderMap, VARY};
|
||||
# use gotham::{router::builder::*, state::State};
|
||||
# use gotham_restful::*;
|
||||
#[derive(Resource)]
|
||||
#[resource(read_all)]
|
||||
struct FooResource;
|
||||
|
||||
#[read_all]
|
||||
async fn read_all(state: &mut State) -> NoContent {
|
||||
let headers: &HeaderMap = state.borrow();
|
||||
let accept = &headers[ACCEPT];
|
||||
# drop(accept);
|
||||
|
||||
let mut res = NoContent::default();
|
||||
res.header(VARY, "accept".parse().unwrap());
|
||||
res
|
||||
}
|
||||
# fn main() {
|
||||
# gotham::start("127.0.0.1:8080", build_simple_router(|route| {
|
||||
# route.resource::<FooResource>("foo");
|
||||
# }));
|
||||
# }
|
||||
```
|
||||
|
||||
# Features
|
||||
|
||||
To make life easier for common use-cases, this create offers a few features that might be helpful
|
||||
|
@ -475,15 +506,12 @@ pub use endpoint::Endpoint;
|
|||
pub use endpoint::EndpointWithSchema;
|
||||
|
||||
mod response;
|
||||
pub use response::Response;
|
||||
|
||||
mod result;
|
||||
pub use result::{
|
||||
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw, Redirect,
|
||||
ResourceResult, Success
|
||||
pub use response::{
|
||||
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponse, IntoResponseError, NoContent,
|
||||
Raw, Redirect, Response, Success
|
||||
};
|
||||
#[cfg(feature = "openapi")]
|
||||
pub use result::{ResourceResultSchema, ResourceResultWithSchema};
|
||||
pub use response::{IntoResponseWithSchema, ResponseSchema};
|
||||
|
||||
mod routing;
|
||||
pub use routing::{DrawResourceRoutes, DrawResources};
|
||||
|
@ -496,7 +524,7 @@ pub use types::*;
|
|||
/// This trait must be implemented for every resource. It allows you to register the different
|
||||
/// endpoints 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)]`.
|
||||
/// It is not recommended to implement this yourself, just use `#[derive(Resource)]`.
|
||||
#[_private_openapi_trait(ResourceWithSchema)]
|
||||
pub trait Resource {
|
||||
/// Register all methods handled by this resource with the underlying router.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::SECURITY_NAME;
|
||||
use crate::{result::*, EndpointWithSchema, OpenapiSchema, RequestBody};
|
||||
use crate::{response::OrAllTypes, EndpointWithSchema, IntoResponse, OpenapiSchema, RequestBody, ResponseSchema};
|
||||
use indexmap::IndexMap;
|
||||
use mime::Mime;
|
||||
use openapiv3::{
|
||||
|
@ -184,11 +184,12 @@ impl OperationDescription {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{NoContent, Raw, ResponseSchema};
|
||||
|
||||
#[test]
|
||||
fn no_content_schema_to_content() {
|
||||
let types = NoContent::accepted_types();
|
||||
let schema = <NoContent as ResourceResultSchema>::schema();
|
||||
let schema = <NoContent as ResponseSchema>::schema();
|
||||
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
|
||||
assert!(content.is_empty());
|
||||
}
|
||||
|
@ -196,7 +197,7 @@ mod test {
|
|||
#[test]
|
||||
fn raw_schema_to_content() {
|
||||
let types = Raw::<&str>::accepted_types();
|
||||
let schema = <Raw<&str> as ResourceResultSchema>::schema();
|
||||
let schema = <Raw<&str> as ResponseSchema>::schema();
|
||||
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
|
||||
assert_eq!(content.len(), 1);
|
||||
let json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();
|
||||
|
|
|
@ -3,7 +3,7 @@ use super::{
|
|||
handler::{OpenapiHandler, SwaggerUiHandler},
|
||||
operation::OperationDescription
|
||||
};
|
||||
use crate::{routing::*, EndpointWithSchema, OpenapiType, ResourceResultSchema, ResourceWithSchema};
|
||||
use crate::{routing::*, EndpointWithSchema, OpenapiType, ResourceWithSchema, ResponseSchema};
|
||||
use gotham::{hyper::Method, pipeline::chain::PipelineHandleChain, router::builder::*};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::{Captures, Regex};
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
use gotham::hyper::{
|
||||
header::{HeaderMap, HeaderName, HeaderValue},
|
||||
Body, StatusCode
|
||||
};
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
|
||||
/// A response, used to create the final gotham response from.
|
||||
#[derive(Debug)]
|
||||
pub struct Response {
|
||||
pub(crate) status: StatusCode,
|
||||
pub(crate) body: Body,
|
||||
pub(crate) mime: Option<Mime>,
|
||||
pub(crate) headers: HeaderMap
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Create a new [Response] from raw data.
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn new<B: Into<Body>>(status: StatusCode, body: B, mime: Option<Mime>) -> Self {
|
||||
Self {
|
||||
status,
|
||||
body: body.into(),
|
||||
mime,
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [Response] with mime type json from already serialized data.
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn json<B: Into<Body>>(status: StatusCode, body: B) -> Self {
|
||||
Self {
|
||||
status,
|
||||
body: body.into(),
|
||||
mime: Some(APPLICATION_JSON),
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a _204 No Content_ [Response].
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn no_content() -> Self {
|
||||
Self {
|
||||
status: StatusCode::NO_CONTENT,
|
||||
body: Body::empty(),
|
||||
mime: None,
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an empty _403 Forbidden_ [Response].
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn forbidden() -> Self {
|
||||
Self {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
body: Body::empty(),
|
||||
mime: None,
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the status code of this [Response].
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.status
|
||||
}
|
||||
|
||||
/// Return the mime type of this [Response].
|
||||
pub fn mime(&self) -> Option<&Mime> {
|
||||
self.mime.as_ref()
|
||||
}
|
||||
|
||||
/// Add an HTTP header to the [Response].
|
||||
pub fn header(&mut self, name: HeaderName, value: HeaderValue) {
|
||||
self.headers.insert(name, value);
|
||||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
}
|
|
@ -12,9 +12,9 @@ pub enum AuthError {
|
|||
}
|
||||
|
||||
/**
|
||||
This return type can be used to map another [ResourceResult](crate::ResourceResult) that can
|
||||
only be returned if the client is authenticated. Otherwise, an empty _403 Forbidden_ response
|
||||
will be issued.
|
||||
This return type can be used to wrap any type implementing [IntoResponse](crate::IntoResponse)
|
||||
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):
|
||||
|
||||
|
@ -86,9 +86,9 @@ where
|
|||
}
|
||||
|
||||
/**
|
||||
This return type can be used to map another [ResourceResult](crate::ResourceResult) that can
|
||||
only be returned if the client is authenticated. Otherwise, an empty _403 Forbidden_ response
|
||||
will be issued.
|
||||
This return type can be used to wrap any type implementing [IntoResponse](crate::IntoResponse)
|
||||
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):
|
||||
|
283
src/response/mod.rs
Normal file
283
src/response/mod.rs
Normal file
|
@ -0,0 +1,283 @@
|
|||
#[cfg(feature = "openapi")]
|
||||
use crate::OpenapiSchema;
|
||||
|
||||
use futures_util::future::{self, BoxFuture, FutureExt};
|
||||
use gotham::{
|
||||
handler::HandlerError,
|
||||
hyper::{
|
||||
header::{HeaderMap, HeaderName, HeaderValue},
|
||||
Body, StatusCode
|
||||
}
|
||||
};
|
||||
use mime::{Mime, APPLICATION_JSON, STAR_STAR};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
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 redirect;
|
||||
pub use redirect::Redirect;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod result;
|
||||
pub use result::IntoResponseError;
|
||||
|
||||
mod success;
|
||||
pub use success::Success;
|
||||
|
||||
pub(crate) trait OrAllTypes {
|
||||
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 response, used to create the final gotham response from.
|
||||
#[derive(Debug)]
|
||||
pub struct Response {
|
||||
pub(crate) status: StatusCode,
|
||||
pub(crate) body: Body,
|
||||
pub(crate) mime: Option<Mime>,
|
||||
pub(crate) headers: HeaderMap
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Create a new [Response] from raw data.
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn new<B: Into<Body>>(status: StatusCode, body: B, mime: Option<Mime>) -> Self {
|
||||
Self {
|
||||
status,
|
||||
body: body.into(),
|
||||
mime,
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [Response] with mime type json from already serialized data.
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn json<B: Into<Body>>(status: StatusCode, body: B) -> Self {
|
||||
Self {
|
||||
status,
|
||||
body: body.into(),
|
||||
mime: Some(APPLICATION_JSON),
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a _204 No Content_ [Response].
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn no_content() -> Self {
|
||||
Self {
|
||||
status: StatusCode::NO_CONTENT,
|
||||
body: Body::empty(),
|
||||
mime: None,
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an empty _403 Forbidden_ [Response].
|
||||
#[must_use = "Creating a response is pointless if you don't use it"]
|
||||
pub fn forbidden() -> Self {
|
||||
Self {
|
||||
status: StatusCode::FORBIDDEN,
|
||||
body: Body::empty(),
|
||||
mime: None,
|
||||
headers: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the status code of this [Response].
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.status
|
||||
}
|
||||
|
||||
/// Return the mime type of this [Response].
|
||||
pub fn mime(&self) -> Option<&Mime> {
|
||||
self.mime.as_ref()
|
||||
}
|
||||
|
||||
/// Add an HTTP header to the [Response].
|
||||
pub fn header(&mut self, name: HeaderName, value: HeaderValue) {
|
||||
self.headers.insert(name, value);
|
||||
}
|
||||
|
||||
pub(crate) fn with_headers(mut self, headers: HeaderMap) -> Self {
|
||||
self.headers = headers;
|
||||
self
|
||||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Response {
|
||||
type Err = Infallible;
|
||||
|
||||
fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>> {
|
||||
future::ok(self).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait needs to be implemented by every type returned from an endpoint to
|
||||
/// to provide the response.
|
||||
pub trait IntoResponse {
|
||||
type Err: Into<HandlerError> + 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) -> BoxFuture<'static, Result<Response, Self::Err>>;
|
||||
|
||||
/// Return a list of supported mime types.
|
||||
fn accepted_types() -> Option<Vec<Mime>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional details for [IntoResponse] to be used with an OpenAPI-aware router.
|
||||
#[cfg(feature = "openapi")]
|
||||
pub trait ResponseSchema {
|
||||
fn schema() -> OpenapiSchema;
|
||||
|
||||
fn default_status() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
/// A trait provided to convert a resource's result to json, and provide an OpenAPI schema to the
|
||||
/// router. This trait is implemented for all types that implement [IntoResponse] and
|
||||
/// [ResponseSchema].
|
||||
#[cfg(feature = "openapi")]
|
||||
pub trait IntoResponseWithSchema: IntoResponse + ResponseSchema + private::Sealed {}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<R: IntoResponse + ResponseSchema> private::Sealed for R {}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<R: IntoResponse + ResponseSchema> IntoResponseWithSchema for R {}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
{
|
||||
let msg = e.to_string();
|
||||
let res = e.into_response_error();
|
||||
match &res {
|
||||
Ok(res) if res.status.is_server_error() => errorlog(msg),
|
||||
Err(err) => {
|
||||
errorlog(msg);
|
||||
errorlog(&err);
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
future::ready(res).boxed()
|
||||
}
|
||||
|
||||
impl<Res> IntoResponse for Pin<Box<dyn Future<Output = Res> + Send>>
|
||||
where
|
||||
Res: IntoResponse + 'static
|
||||
{
|
||||
type Err = Res::Err;
|
||||
|
||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
|
||||
self.then(IntoResponse::into_response).boxed()
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>> {
|
||||
Res::accepted_types()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<Res> ResponseSchema for Pin<Box<dyn Future<Output = Res> + Send>>
|
||||
where
|
||||
Res: ResponseSchema
|
||||
{
|
||||
fn schema() -> OpenapiSchema {
|
||||
Res::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode {
|
||||
Res::default_status()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(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
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Error)]
|
||||
#[error("An Error")]
|
||||
struct MsgError;
|
||||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
use super::{handle_error, ResourceResult};
|
||||
use super::{handle_error, IntoResponse};
|
||||
use crate::{IntoResponseError, Response};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::{OpenapiSchema, OpenapiType, ResourceResultSchema};
|
||||
|
||||
use crate::{OpenapiSchema, OpenapiType, ResponseSchema};
|
||||
use futures_util::{future, future::FutureExt};
|
||||
use gotham::hyper::header::{HeaderMap, HeaderValue, IntoHeaderName};
|
||||
#[cfg(feature = "openapi")]
|
||||
use gotham::hyper::StatusCode;
|
||||
use mime::Mime;
|
||||
|
@ -31,22 +31,36 @@ fn read_all() {
|
|||
# }
|
||||
```
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct NoContent;
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NoContent {
|
||||
headers: HeaderMap
|
||||
}
|
||||
|
||||
impl From<()> for NoContent {
|
||||
fn from(_: ()) -> Self {
|
||||
Self {}
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceResult for NoContent {
|
||||
impl NoContent {
|
||||
/// Set a custom HTTP header. If a header with this name was set before, its value is being updated.
|
||||
pub fn header<K: IntoHeaderName>(&mut self, name: K, value: HeaderValue) {
|
||||
self.headers.insert(name, value);
|
||||
}
|
||||
|
||||
/// Allow manipulating HTTP headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse 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()
|
||||
future::ok(Response::no_content().with_headers(self.headers)).boxed()
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>> {
|
||||
|
@ -55,7 +69,7 @@ impl ResourceResult for NoContent {
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl ResourceResultSchema for NoContent {
|
||||
impl ResponseSchema for NoContent {
|
||||
/// Returns the schema of the `()` type.
|
||||
fn schema() -> OpenapiSchema {
|
||||
<()>::schema()
|
||||
|
@ -67,7 +81,7 @@ impl ResourceResultSchema for NoContent {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E> ResourceResult for Result<NoContent, E>
|
||||
impl<E> IntoResponse for Result<NoContent, E>
|
||||
where
|
||||
E: Display + IntoResponseError<Err = serde_json::Error>
|
||||
{
|
||||
|
@ -86,12 +100,12 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<E> ResourceResultSchema for Result<NoContent, E>
|
||||
impl<E> ResponseSchema for Result<NoContent, E>
|
||||
where
|
||||
E: Display + IntoResponseError<Err = serde_json::Error>
|
||||
{
|
||||
fn schema() -> OpenapiSchema {
|
||||
<NoContent as ResourceResultSchema>::schema()
|
||||
<NoContent as ResponseSchema>::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
|
@ -104,7 +118,7 @@ where
|
|||
mod test {
|
||||
use super::*;
|
||||
use futures_executor::block_on;
|
||||
use gotham::hyper::StatusCode;
|
||||
use gotham::hyper::{header::ACCESS_CONTROL_ALLOW_ORIGIN, StatusCode};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Default, Error)]
|
||||
|
@ -118,6 +132,9 @@ mod test {
|
|||
assert_eq!(res.status, StatusCode::NO_CONTENT);
|
||||
assert_eq!(res.mime, None);
|
||||
assert_eq!(res.full_body().unwrap(), &[] as &[u8]);
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
assert_eq!(NoContent::default_status(), StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -128,4 +145,13 @@ mod test {
|
|||
assert_eq!(res.mime, None);
|
||||
assert_eq!(res.full_body().unwrap(), &[] as &[u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_content_custom_headers() {
|
||||
let mut no_content = NoContent::default();
|
||||
no_content.header(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
|
||||
let res = block_on(no_content.into_response()).expect("didn't expect error response");
|
||||
let cors = res.headers.get(ACCESS_CONTROL_ALLOW_ORIGIN);
|
||||
assert_eq!(cors.map(|value| value.to_str().unwrap()), Some("*"));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use super::{handle_error, IntoResponseError, ResourceResult};
|
||||
use super::{handle_error, IntoResponse, IntoResponseError};
|
||||
use crate::{FromBody, RequestBody, ResourceType, Response};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::{OpenapiSchema, OpenapiType, ResourceResultSchema};
|
||||
use crate::{IntoResponseWithSchema, OpenapiSchema, OpenapiType, ResponseSchema};
|
||||
|
||||
use futures_core::future::Future;
|
||||
use futures_util::{future, future::FutureExt};
|
||||
|
@ -99,7 +99,7 @@ impl<T> OpenapiType for Raw<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Body>> ResourceResult for Raw<T>
|
||||
impl<T: Into<Body>> IntoResponse for Raw<T>
|
||||
where
|
||||
Self: Send
|
||||
{
|
||||
|
@ -111,7 +111,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<T: Into<Body>> ResourceResultSchema for Raw<T>
|
||||
impl<T: Into<Body>> ResponseSchema for Raw<T>
|
||||
where
|
||||
Self: Send
|
||||
{
|
||||
|
@ -120,10 +120,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, E> ResourceResult for Result<Raw<T>, E>
|
||||
impl<T, E> IntoResponse for Result<Raw<T>, E>
|
||||
where
|
||||
Raw<T>: ResourceResult,
|
||||
E: Display + IntoResponseError<Err = <Raw<T> as ResourceResult>::Err>
|
||||
Raw<T>: IntoResponse,
|
||||
E: Display + IntoResponseError<Err = <Raw<T> as IntoResponse>::Err>
|
||||
{
|
||||
type Err = E::Err;
|
||||
|
||||
|
@ -136,13 +136,13 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<T, E> ResourceResultSchema for Result<Raw<T>, E>
|
||||
impl<T, E> ResponseSchema for Result<Raw<T>, E>
|
||||
where
|
||||
Raw<T>: ResourceResult + ResourceResultSchema,
|
||||
E: Display + IntoResponseError<Err = <Raw<T> as ResourceResult>::Err>
|
||||
Raw<T>: IntoResponseWithSchema,
|
||||
E: Display + IntoResponseError<Err = <Raw<T> as IntoResponse>::Err>
|
||||
{
|
||||
fn schema() -> OpenapiSchema {
|
||||
<Raw<T> as ResourceResultSchema>::schema()
|
||||
<Raw<T> as ResponseSchema>::schema()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use super::{handle_error, ResourceResult};
|
||||
use super::{handle_error, IntoResponse};
|
||||
use crate::{IntoResponseError, Response};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::{NoContent, OpenapiSchema, ResourceResultSchema};
|
||||
use crate::{NoContent, OpenapiSchema, ResponseSchema};
|
||||
use futures_util::future::{BoxFuture, FutureExt, TryFutureExt};
|
||||
use gotham::hyper::{
|
||||
header::{InvalidHeaderValue, LOCATION},
|
||||
|
@ -42,7 +42,7 @@ pub struct Redirect {
|
|||
pub to: String
|
||||
}
|
||||
|
||||
impl ResourceResult for Redirect {
|
||||
impl IntoResponse for Redirect {
|
||||
type Err = InvalidHeaderValue;
|
||||
|
||||
fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>> {
|
||||
|
@ -56,13 +56,13 @@ impl ResourceResult for Redirect {
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl ResourceResultSchema for Redirect {
|
||||
impl ResponseSchema for Redirect {
|
||||
fn default_status() -> StatusCode {
|
||||
StatusCode::SEE_OTHER
|
||||
}
|
||||
|
||||
fn schema() -> OpenapiSchema {
|
||||
<NoContent as ResourceResultSchema>::schema()
|
||||
<NoContent as ResponseSchema>::schema()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ pub enum RedirectError<E: StdError + 'static> {
|
|||
}
|
||||
|
||||
#[allow(ambiguous_associated_items)] // an enum variant is not a type. never.
|
||||
impl<E> ResourceResult for Result<Redirect, E>
|
||||
impl<E> IntoResponse for Result<Redirect, E>
|
||||
where
|
||||
E: Display + IntoResponseError,
|
||||
<E as IntoResponseError>::Err: StdError + Sync
|
||||
|
@ -92,7 +92,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<E> ResourceResultSchema for Result<Redirect, E>
|
||||
impl<E> ResponseSchema for Result<Redirect, E>
|
||||
where
|
||||
E: Display + IntoResponseError,
|
||||
<E as IntoResponseError>::Err: StdError + Sync
|
||||
|
@ -102,7 +102,7 @@ where
|
|||
}
|
||||
|
||||
fn schema() -> OpenapiSchema {
|
||||
<Redirect as ResourceResultSchema>::schema()
|
||||
<Redirect as ResponseSchema>::schema()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use super::{handle_error, into_response_helper, ResourceResult};
|
||||
use crate::{result::ResourceError, Response, ResponseBody};
|
||||
use super::{handle_error, IntoResponse, ResourceError};
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::{OpenapiSchema, ResourceResultSchema};
|
||||
use crate::{OpenapiSchema, ResponseSchema};
|
||||
use crate::{Response, ResponseBody, Success};
|
||||
|
||||
use futures_core::future::Future;
|
||||
use gotham::hyper::StatusCode;
|
||||
|
@ -26,7 +26,7 @@ impl<E: Error> IntoResponseError for E {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, E> ResourceResult for Result<R, E>
|
||||
impl<R, E> IntoResponse for Result<R, E>
|
||||
where
|
||||
R: ResponseBody,
|
||||
E: Display + IntoResponseError<Err = serde_json::Error>
|
||||
|
@ -35,7 +35,7 @@ where
|
|||
|
||||
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)?))),
|
||||
Ok(r) => Success::from(r).into_response(),
|
||||
Err(e) => handle_error(e)
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<R, E> ResourceResultSchema for Result<R, E>
|
||||
impl<R, E> ResponseSchema for Result<R, E>
|
||||
where
|
||||
R: ResponseBody,
|
||||
E: Display + IntoResponseError<Err = serde_json::Error>
|
||||
|
@ -59,7 +59,7 @@ where
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::result::OrAllTypes;
|
||||
use crate::response::OrAllTypes;
|
||||
use futures_executor::block_on;
|
||||
use thiserror::Error;
|
||||
|
|
@ -1,19 +1,17 @@
|
|||
use super::{into_response_helper, ResourceResult};
|
||||
use super::IntoResponse;
|
||||
#[cfg(feature = "openapi")]
|
||||
use crate::{OpenapiSchema, ResourceResultSchema};
|
||||
use crate::{OpenapiSchema, ResponseSchema};
|
||||
use crate::{Response, ResponseBody};
|
||||
use gotham::hyper::StatusCode;
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin
|
||||
use futures_util::future::{self, FutureExt};
|
||||
use gotham::hyper::{
|
||||
header::{HeaderMap, HeaderValue, IntoHeaderName},
|
||||
StatusCode
|
||||
};
|
||||
use mime::{Mime, APPLICATION_JSON};
|
||||
use std::{fmt::Debug, future::Future, pin::Pin};
|
||||
|
||||
/**
|
||||
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.
|
||||
This can be returned from a resource when there is no cause of an error.
|
||||
|
||||
Usage example:
|
||||
|
||||
|
@ -42,63 +40,40 @@ fn read_all() -> Success<MyResponse> {
|
|||
# }
|
||||
```
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
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
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Success<T> {
|
||||
value: T,
|
||||
headers: HeaderMap
|
||||
}
|
||||
|
||||
impl<T> From<T> for Success<T> {
|
||||
fn from(t: T) -> Self {
|
||||
Self(t)
|
||||
Self {
|
||||
value: t,
|
||||
headers: HeaderMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Success<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
impl<T> Success<T> {
|
||||
/// Set a custom HTTP header. If a header with this name was set before, its value is being updated.
|
||||
pub fn header<K: IntoHeaderName>(&mut self, name: K, value: HeaderValue) {
|
||||
self.headers.insert(name, value);
|
||||
}
|
||||
|
||||
/// Allow manipulating HTTP headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Copy for Success<T> {}
|
||||
|
||||
impl<T: Default> Default for Success<T> {
|
||||
fn default() -> Self {
|
||||
Self(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResponseBody> ResourceResult for Success<T>
|
||||
where
|
||||
Self: Send
|
||||
{
|
||||
impl<T: ResponseBody> IntoResponse for Success<T> {
|
||||
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())?)))
|
||||
let res =
|
||||
serde_json::to_string(&self.value).map(|body| Response::json(StatusCode::OK, body).with_headers(self.headers));
|
||||
future::ready(res).boxed()
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>> {
|
||||
|
@ -107,10 +82,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<T: ResponseBody> ResourceResultSchema for Success<T>
|
||||
where
|
||||
Self: Send
|
||||
{
|
||||
impl<T: ResponseBody> ResponseSchema for Success<T> {
|
||||
fn schema() -> OpenapiSchema {
|
||||
T::schema()
|
||||
}
|
||||
|
@ -119,8 +91,9 @@ where
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::result::OrAllTypes;
|
||||
use crate::response::OrAllTypes;
|
||||
use futures_executor::block_on;
|
||||
use gotham::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
||||
|
@ -135,6 +108,17 @@ mod test {
|
|||
assert_eq!(res.status, StatusCode::OK);
|
||||
assert_eq!(res.mime, Some(APPLICATION_JSON));
|
||||
assert_eq!(res.full_body().unwrap(), br#"{"msg":""}"#);
|
||||
#[cfg(feature = "openapi")]
|
||||
assert_eq!(<Success<Msg>>::default_status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn success_custom_headers() {
|
||||
let mut success: Success<Msg> = Msg::default().into();
|
||||
success.header(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
|
||||
let res = block_on(success.into_response()).expect("didn't expect error response");
|
||||
let cors = res.headers.get(ACCESS_CONTROL_ALLOW_ORIGIN);
|
||||
assert_eq!(cors.map(|value| value.to_str().unwrap()), Some("*"));
|
||||
}
|
||||
|
||||
#[test]
|
|
@ -1,197 +0,0 @@
|
|||
#[cfg(feature = "openapi")]
|
||||
use crate::OpenapiSchema;
|
||||
use crate::Response;
|
||||
|
||||
use futures_util::future::{BoxFuture, FutureExt};
|
||||
use gotham::handler::HandlerError;
|
||||
#[cfg(feature = "openapi")]
|
||||
use gotham::hyper::StatusCode;
|
||||
use mime::{Mime, STAR_STAR};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
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 redirect;
|
||||
pub use redirect::Redirect;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod result;
|
||||
pub use result::IntoResponseError;
|
||||
|
||||
mod success;
|
||||
pub use success::Success;
|
||||
|
||||
pub(crate) trait OrAllTypes {
|
||||
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: Into<HandlerError> + 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) -> BoxFuture<'static, Result<Response, Self::Err>>;
|
||||
|
||||
/// Return a list of supported mime types.
|
||||
fn accepted_types() -> Option<Vec<Mime>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional details for [ResourceResult] to be used with an OpenAPI-aware router.
|
||||
#[cfg(feature = "openapi")]
|
||||
pub trait ResourceResultSchema {
|
||||
fn schema() -> OpenapiSchema;
|
||||
|
||||
fn default_status() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
/// A trait provided to convert a resource's result to json, and provide an OpenAPI schema to the
|
||||
/// router. This trait is implemented for all types that implement [ResourceResult] and
|
||||
/// [ResourceResultSchema].
|
||||
#[cfg(feature = "openapi")]
|
||||
pub trait ResourceResultWithSchema: ResourceResult + ResourceResultSchema + private::Sealed {}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<R: ResourceResult + ResourceResultSchema> private::Sealed for R {}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<R: ResourceResult + ResourceResultSchema> ResourceResultWithSchema for R {}
|
||||
|
||||
/// 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(|| {
|
||||
let msg = e.to_string();
|
||||
let res = e.into_response_error();
|
||||
match &res {
|
||||
Ok(res) if res.status.is_server_error() => errorlog(msg),
|
||||
Err(err) => {
|
||||
errorlog(msg);
|
||||
errorlog(&err);
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
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(ResourceResult::into_response).boxed()
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>> {
|
||||
Res::accepted_types()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
impl<Res> ResourceResultSchema for Pin<Box<dyn Future<Output = Res> + Send>>
|
||||
where
|
||||
Res: ResourceResultSchema
|
||||
{
|
||||
fn schema() -> OpenapiSchema {
|
||||
Res::schema()
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
fn default_status() -> StatusCode {
|
||||
Res::default_status()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(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
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Error)]
|
||||
#[error("An Error")]
|
||||
struct MsgError;
|
||||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -3,10 +3,7 @@ use crate::openapi::{
|
|||
builder::{OpenapiBuilder, OpenapiInfo},
|
||||
router::OpenapiRouter
|
||||
};
|
||||
use crate::{
|
||||
result::{ResourceError, ResourceResult},
|
||||
Endpoint, FromBody, Resource, Response
|
||||
};
|
||||
use crate::{response::ResourceError, Endpoint, FromBody, IntoResponse, Resource, Response};
|
||||
|
||||
#[cfg(feature = "cors")]
|
||||
use gotham::router::route::matcher::AccessControlRequestMethodMatcher;
|
||||
|
@ -90,7 +87,7 @@ fn response_from(res: Response, state: &State) -> gotham::hyper::Response<Body>
|
|||
async fn endpoint_handler<E: Endpoint>(state: &mut State) -> Result<gotham::hyper::Response<Body>, HandlerError>
|
||||
where
|
||||
E: Endpoint,
|
||||
<E::Output as ResourceResult>::Err: Into<HandlerError>
|
||||
<E::Output as IntoResponse>::Err: Into<HandlerError>
|
||||
{
|
||||
trace!("entering endpoint_handler");
|
||||
let placeholders = E::Placeholders::take_from(state);
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
error[E0277]: the trait bound `FooResponse: ResourceResultSchema` is not satisfied
|
||||
error[E0277]: the trait bound `FooResponse: ResponseSchema` is not satisfied
|
||||
--> $DIR/invalid_return_type.rs:12:18
|
||||
|
|
||||
12 | fn endpoint() -> FooResponse {
|
||||
| ^^^^^^^^^^^ the trait `ResourceResultSchema` is not implemented for `FooResponse`
|
||||
| ^^^^^^^^^^^ the trait `ResponseSchema` is not implemented for `FooResponse`
|
||||
|
|
||||
::: $WORKSPACE/src/endpoint.rs
|
||||
|
|
||||
| #[openapi_bound("Output: crate::ResourceResultSchema")]
|
||||
| ------------------------------------- required by this bound in `gotham_restful::EndpointWithSchema::Output`
|
||||
| #[openapi_bound("Output: crate::ResponseSchema")]
|
||||
| ------------------------------- required by this bound in `gotham_restful::EndpointWithSchema::Output`
|
||||
|
||||
error[E0277]: the trait bound `FooResponse: ResourceResult` is not satisfied
|
||||
error[E0277]: the trait bound `FooResponse: gotham_restful::IntoResponse` is not satisfied
|
||||
--> $DIR/invalid_return_type.rs:12:18
|
||||
|
|
||||
12 | fn endpoint() -> FooResponse {
|
||||
| ^^^^^^^^^^^ the trait `ResourceResult` is not implemented for `FooResponse`
|
||||
| ^^^^^^^^^^^ the trait `gotham_restful::IntoResponse` is not implemented for `FooResponse`
|
||||
|
|
||||
::: $WORKSPACE/src/endpoint.rs
|
||||
|
|
||||
| type Output: ResourceResult + Send;
|
||||
| -------------- required by this bound in `gotham_restful::EndpointWithSchema::Output`
|
||||
| type Output: IntoResponse + Send;
|
||||
| ------------ required by this bound in `gotham_restful::EndpointWithSchema::Output`
|
||||
|
|
Loading…
Add table
Reference in a new issue