1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-02-22 20:52:27 +00:00

split the ResourceResult trait

This commit is contained in:
msrd0 2021-02-21 18:21:09 +00:00
parent c640efcb88
commit 7de11cdae1
12 changed files with 102 additions and 29 deletions

View file

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- All fields of `Response` are now private - All fields of `Response` are now private
- If not enabling the `openapi` feature, `without-openapi` has to be enabled - 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 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`
### Removed ### Removed
- All pre-defined methods (`read`, `create`, ...) from our router extensions ([!18]) - All pre-defined methods (`read`, `create`, ...) from our router extensions ([!18])

View file

@ -16,6 +16,7 @@ pub trait Endpoint {
fn uri() -> Cow<'static, str>; fn uri() -> Cow<'static, str>;
/// The output type that provides the response. /// The output type that provides the response.
#[openapi_bound("Output: crate::ResourceResultSchema")]
type Output: ResourceResult + Send; type Output: ResourceResult + Send;
/// Returns `true` _iff_ the URI contains placeholders. `false` by default. /// Returns `true` _iff_ the URI contains placeholders. `false` by default.

View file

@ -482,6 +482,8 @@ pub use result::{
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw, Redirect, AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw, Redirect,
ResourceResult, Success ResourceResult, Success
}; };
#[cfg(feature = "openapi")]
pub use result::{ResourceResultSchema, ResourceResultWithSchema};
mod routing; mod routing;
pub use routing::{DrawResourceRoutes, DrawResources}; pub use routing::{DrawResourceRoutes, DrawResources};

View file

@ -188,7 +188,7 @@ mod test {
#[test] #[test]
fn no_content_schema_to_content() { fn no_content_schema_to_content() {
let types = NoContent::accepted_types(); let types = NoContent::accepted_types();
let schema = <NoContent as ResourceResult>::schema(); let schema = <NoContent as ResourceResultSchema>::schema();
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema())); let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
assert!(content.is_empty()); assert!(content.is_empty());
} }
@ -196,7 +196,7 @@ mod test {
#[test] #[test]
fn raw_schema_to_content() { fn raw_schema_to_content() {
let types = Raw::<&str>::accepted_types(); let types = Raw::<&str>::accepted_types();
let schema = <Raw<&str> as ResourceResult>::schema(); let schema = <Raw<&str> as ResourceResultSchema>::schema();
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema())); let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
assert_eq!(content.len(), 1); assert_eq!(content.len(), 1);
let json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap(); let json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();

View file

@ -1,5 +1,5 @@
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription}; use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription};
use crate::{routing::*, EndpointWithSchema, OpenapiType, ResourceResult, ResourceWithSchema}; use crate::{routing::*, EndpointWithSchema, OpenapiType, ResourceResultSchema, ResourceWithSchema};
use gotham::{hyper::Method, pipeline::chain::PipelineHandleChain, router::builder::*}; use gotham::{hyper::Method, pipeline::chain::PipelineHandleChain, router::builder::*};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::{Captures, Regex}; use regex::{Captures, Regex};

View file

@ -2,7 +2,7 @@
use crate::OpenapiSchema; use crate::OpenapiSchema;
use crate::Response; use crate::Response;
use futures_util::future::FutureExt; use futures_util::future::{BoxFuture, FutureExt};
use gotham::handler::HandlerError; use gotham::handler::HandlerError;
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
use gotham::hyper::StatusCode; use gotham::hyper::StatusCode;
@ -49,22 +49,41 @@ pub trait ResourceResult {
/// Turn this into a response that can be returned to the browser. This api will likely /// Turn this into a response that can be returned to the browser. This api will likely
/// change in the future. /// change in the future.
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>; fn into_response(self) -> BoxFuture<'static, Result<Response, Self::Err>>;
/// Return a list of supported mime types. /// Return a list of supported mime types.
fn accepted_types() -> Option<Vec<Mime>> { fn accepted_types() -> Option<Vec<Mime>> {
None None
} }
}
#[cfg(feature = "openapi")] /// Additional details for [ResourceResult] to be used with an OpenAPI-aware router.
#[cfg(feature = "openapi")]
pub trait ResourceResultSchema {
fn schema() -> OpenapiSchema; fn schema() -> OpenapiSchema;
#[cfg(feature = "openapi")]
fn default_status() -> StatusCode { fn default_status() -> StatusCode {
StatusCode::OK 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. /// The default json returned on an 500 Internal Server Error.
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub(crate) struct ResourceError { pub(crate) struct ResourceError {
@ -130,8 +149,13 @@ where
fn accepted_types() -> Option<Vec<Mime>> { fn accepted_types() -> Option<Vec<Mime>> {
Res::accepted_types() Res::accepted_types()
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl<Res> ResourceResultSchema for Pin<Box<dyn Future<Output = Res> + Send>>
where
Res: ResourceResultSchema
{
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
Res::schema() Res::schema()
} }

View file

@ -1,7 +1,7 @@
use super::{handle_error, ResourceResult}; use super::{handle_error, ResourceResult};
use crate::{IntoResponseError, Response}; use crate::{IntoResponseError, Response};
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
use crate::{OpenapiSchema, OpenapiType}; use crate::{OpenapiSchema, OpenapiType, ResourceResultSchema};
use futures_util::{future, future::FutureExt}; use futures_util::{future, future::FutureExt};
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
@ -52,15 +52,16 @@ impl ResourceResult for NoContent {
fn accepted_types() -> Option<Vec<Mime>> { fn accepted_types() -> Option<Vec<Mime>> {
Some(Vec::new()) Some(Vec::new())
} }
}
#[cfg(feature = "openapi")]
impl ResourceResultSchema for NoContent {
/// Returns the schema of the `()` type. /// Returns the schema of the `()` type.
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
<()>::schema() <()>::schema()
} }
/// This will always be a _204 No Content_ /// This will always be a _204 No Content_
#[cfg(feature = "openapi")]
fn default_status() -> StatusCode { fn default_status() -> StatusCode {
StatusCode::NO_CONTENT StatusCode::NO_CONTENT
} }
@ -82,10 +83,15 @@ where
fn accepted_types() -> Option<Vec<Mime>> { fn accepted_types() -> Option<Vec<Mime>> {
NoContent::accepted_types() NoContent::accepted_types()
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl<E> ResourceResultSchema for Result<NoContent, E>
where
E: Display + IntoResponseError<Err = serde_json::Error>
{
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
<NoContent as ResourceResult>::schema() <NoContent as ResourceResultSchema>::schema()
} }
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]

View file

@ -1,7 +1,7 @@
use super::{handle_error, IntoResponseError, ResourceResult}; use super::{handle_error, IntoResponseError, ResourceResult};
use crate::{FromBody, RequestBody, ResourceType, Response}; use crate::{FromBody, RequestBody, ResourceType, Response};
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
use crate::{OpenapiSchema, OpenapiType}; use crate::{OpenapiSchema, OpenapiType, ResourceResultSchema};
use futures_core::future::Future; use futures_core::future::Future;
use futures_util::{future, future::FutureExt}; use futures_util::{future, future::FutureExt};
@ -108,8 +108,13 @@ where
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))).boxed() future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime))).boxed()
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl<T: Into<Body>> ResourceResultSchema for Raw<T>
where
Self: Send
{
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
<Self as OpenapiType>::schema() <Self as OpenapiType>::schema()
} }
@ -128,10 +133,16 @@ where
Err(e) => handle_error(e) Err(e) => handle_error(e)
} }
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl<T, E> ResourceResultSchema for Result<Raw<T>, E>
where
Raw<T>: ResourceResult + ResourceResultSchema,
E: Display + IntoResponseError<Err = <Raw<T> as ResourceResult>::Err>
{
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
<Raw<T> as ResourceResult>::schema() <Raw<T> as ResourceResultSchema>::schema()
} }
} }

View file

@ -1,7 +1,7 @@
use super::{handle_error, ResourceResult}; use super::{handle_error, ResourceResult};
use crate::{IntoResponseError, Response}; use crate::{IntoResponseError, Response};
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
use crate::{NoContent, OpenapiSchema}; use crate::{NoContent, OpenapiSchema, ResourceResultSchema};
use futures_util::future::{BoxFuture, FutureExt, TryFutureExt}; use futures_util::future::{BoxFuture, FutureExt, TryFutureExt};
use gotham::hyper::{ use gotham::hyper::{
header::{InvalidHeaderValue, LOCATION}, header::{InvalidHeaderValue, LOCATION},
@ -53,15 +53,16 @@ impl ResourceResult for Redirect {
} }
.boxed() .boxed()
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl ResourceResultSchema for Redirect {
fn default_status() -> StatusCode { fn default_status() -> StatusCode {
StatusCode::SEE_OTHER StatusCode::SEE_OTHER
} }
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
<NoContent as ResourceResult>::schema() <NoContent as ResourceResultSchema>::schema()
} }
} }
@ -88,15 +89,20 @@ where
Err(e) => handle_error(e).map_err(|e| RedirectError::Other(e)).boxed() Err(e) => handle_error(e).map_err(|e| RedirectError::Other(e)).boxed()
} }
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl<E> ResourceResultSchema for Result<Redirect, E>
where
E: Display + IntoResponseError,
<E as IntoResponseError>::Err: StdError + Sync
{
fn default_status() -> StatusCode { fn default_status() -> StatusCode {
Redirect::default_status() Redirect::default_status()
} }
#[cfg(feature = "openapi")]
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
<Redirect as ResourceResult>::schema() <Redirect as ResourceResultSchema>::schema()
} }
} }

View file

@ -1,7 +1,7 @@
use super::{handle_error, into_response_helper, ResourceResult}; use super::{handle_error, into_response_helper, ResourceResult};
#[cfg(feature = "openapi")]
use crate::OpenapiSchema;
use crate::{result::ResourceError, Response, ResponseBody}; use crate::{result::ResourceError, Response, ResponseBody};
#[cfg(feature = "openapi")]
use crate::{OpenapiSchema, ResourceResultSchema};
use futures_core::future::Future; use futures_core::future::Future;
use gotham::hyper::StatusCode; use gotham::hyper::StatusCode;
@ -43,8 +43,14 @@ where
fn accepted_types() -> Option<Vec<Mime>> { fn accepted_types() -> Option<Vec<Mime>> {
Some(vec![APPLICATION_JSON]) Some(vec![APPLICATION_JSON])
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl<R, E> ResourceResultSchema for Result<R, E>
where
R: ResponseBody,
E: Display + IntoResponseError<Err = serde_json::Error>
{
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
R::schema() R::schema()
} }

View file

@ -1,6 +1,6 @@
use super::{into_response_helper, ResourceResult}; use super::{into_response_helper, ResourceResult};
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
use crate::OpenapiSchema; use crate::{OpenapiSchema, ResourceResultSchema};
use crate::{Response, ResponseBody}; use crate::{Response, ResponseBody};
use gotham::hyper::StatusCode; use gotham::hyper::StatusCode;
use mime::{Mime, APPLICATION_JSON}; use mime::{Mime, APPLICATION_JSON};
@ -104,8 +104,13 @@ where
fn accepted_types() -> Option<Vec<Mime>> { fn accepted_types() -> Option<Vec<Mime>> {
Some(vec![APPLICATION_JSON]) Some(vec![APPLICATION_JSON])
} }
}
#[cfg(feature = "openapi")] #[cfg(feature = "openapi")]
impl<T: ResponseBody> ResourceResultSchema for Success<T>
where
Self: Send
{
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
T::schema() T::schema()
} }

View file

@ -1,3 +1,14 @@
error[E0277]: the trait bound `FooResponse: ResourceResultSchema` is not satisfied
--> $DIR/invalid_return_type.rs:12:18
|
12 | fn endpoint() -> FooResponse {
| ^^^^^^^^^^^ the trait `ResourceResultSchema` is not implemented for `FooResponse`
|
::: $WORKSPACE/src/endpoint.rs
|
| #[openapi_bound("Output: crate::ResourceResultSchema")]
| ------------------------------------- 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: ResourceResult` is not satisfied
--> $DIR/invalid_return_type.rs:12:18 --> $DIR/invalid_return_type.rs:12:18
| |