From 7de11cdae196214e907707a1ee5237362f0d0674 Mon Sep 17 00:00:00 2001 From: msrd0 <1182023-msrd0@users.noreply.gitlab.com> Date: Sun, 21 Feb 2021 18:21:09 +0000 Subject: [PATCH] split the ResourceResult trait --- CHANGELOG.md | 1 + src/endpoint.rs | 1 + src/lib.rs | 2 ++ src/openapi/operation.rs | 4 +-- src/openapi/router.rs | 2 +- src/result/mod.rs | 34 +++++++++++++++++--- src/result/no_content.rs | 16 ++++++--- src/result/raw.rs | 19 ++++++++--- src/result/redirect.rs | 20 ++++++++---- src/result/result.rs | 12 +++++-- src/result/success.rs | 9 ++++-- tests/ui/endpoint/invalid_return_type.stderr | 11 +++++++ 12 files changed, 102 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 369a09b..372120c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 - 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` ### Removed - All pre-defined methods (`read`, `create`, ...) from our router extensions ([!18]) diff --git a/src/endpoint.rs b/src/endpoint.rs index 32dd015..f85fdb2 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -16,6 +16,7 @@ pub trait Endpoint { fn uri() -> Cow<'static, str>; /// The output type that provides the response. + #[openapi_bound("Output: crate::ResourceResultSchema")] type Output: ResourceResult + Send; /// Returns `true` _iff_ the URI contains placeholders. `false` by default. diff --git a/src/lib.rs b/src/lib.rs index ac58c2e..1f50701 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -482,6 +482,8 @@ pub use result::{ AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw, Redirect, ResourceResult, Success }; +#[cfg(feature = "openapi")] +pub use result::{ResourceResultSchema, ResourceResultWithSchema}; mod routing; pub use routing::{DrawResourceRoutes, DrawResources}; diff --git a/src/openapi/operation.rs b/src/openapi/operation.rs index 103c46c..85f5e6c 100644 --- a/src/openapi/operation.rs +++ b/src/openapi/operation.rs @@ -188,7 +188,7 @@ mod test { #[test] fn no_content_schema_to_content() { let types = NoContent::accepted_types(); - let schema = ::schema(); + let schema = ::schema(); let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema())); assert!(content.is_empty()); } @@ -196,7 +196,7 @@ mod test { #[test] fn raw_schema_to_content() { let types = Raw::<&str>::accepted_types(); - let schema = as ResourceResult>::schema(); + let schema = as ResourceResultSchema>::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(); diff --git a/src/openapi/router.rs b/src/openapi/router.rs index 5a13302..2fd8caa 100644 --- a/src/openapi/router.rs +++ b/src/openapi/router.rs @@ -1,5 +1,5 @@ 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 once_cell::sync::Lazy; use regex::{Captures, Regex}; diff --git a/src/result/mod.rs b/src/result/mod.rs index 87967aa..a119a82 100644 --- a/src/result/mod.rs +++ b/src/result/mod.rs @@ -2,7 +2,7 @@ use crate::OpenapiSchema; use crate::Response; -use futures_util::future::FutureExt; +use futures_util::future::{BoxFuture, FutureExt}; use gotham::handler::HandlerError; #[cfg(feature = "openapi")] 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 /// change in the future. - fn into_response(self) -> Pin> + Send>>; + fn into_response(self) -> BoxFuture<'static, Result>; /// Return a list of supported mime types. fn accepted_types() -> Option> { 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; - #[cfg(feature = "openapi")] 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 private::Sealed for R {} + +#[cfg(feature = "openapi")] +impl ResourceResultWithSchema for R {} + /// The default json returned on an 500 Internal Server Error. #[derive(Debug, Serialize)] pub(crate) struct ResourceError { @@ -130,8 +149,13 @@ where fn accepted_types() -> Option> { Res::accepted_types() } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl ResourceResultSchema for Pin + Send>> +where + Res: ResourceResultSchema +{ fn schema() -> OpenapiSchema { Res::schema() } diff --git a/src/result/no_content.rs b/src/result/no_content.rs index 30413fd..e6072ed 100644 --- a/src/result/no_content.rs +++ b/src/result/no_content.rs @@ -1,7 +1,7 @@ use super::{handle_error, ResourceResult}; use crate::{IntoResponseError, Response}; #[cfg(feature = "openapi")] -use crate::{OpenapiSchema, OpenapiType}; +use crate::{OpenapiSchema, OpenapiType, ResourceResultSchema}; use futures_util::{future, future::FutureExt}; #[cfg(feature = "openapi")] @@ -52,15 +52,16 @@ impl ResourceResult for NoContent { fn accepted_types() -> Option> { Some(Vec::new()) } +} +#[cfg(feature = "openapi")] +impl ResourceResultSchema for NoContent { /// 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 } @@ -82,10 +83,15 @@ where fn accepted_types() -> Option> { NoContent::accepted_types() } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl ResourceResultSchema for Result +where + E: Display + IntoResponseError +{ fn schema() -> OpenapiSchema { - ::schema() + ::schema() } #[cfg(feature = "openapi")] diff --git a/src/result/raw.rs b/src/result/raw.rs index cc30a55..759adbb 100644 --- a/src/result/raw.rs +++ b/src/result/raw.rs @@ -1,7 +1,7 @@ use super::{handle_error, IntoResponseError, ResourceResult}; use crate::{FromBody, RequestBody, ResourceType, Response}; #[cfg(feature = "openapi")] -use crate::{OpenapiSchema, OpenapiType}; +use crate::{OpenapiSchema, OpenapiType, ResourceResultSchema}; use futures_core::future::Future; use futures_util::{future, future::FutureExt}; @@ -108,8 +108,13 @@ where fn into_response(self) -> Pin> + Send>> { future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime))).boxed() } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl> ResourceResultSchema for Raw +where + Self: Send +{ fn schema() -> OpenapiSchema { ::schema() } @@ -128,10 +133,16 @@ where Err(e) => handle_error(e) } } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl ResourceResultSchema for Result, E> +where + Raw: ResourceResult + ResourceResultSchema, + E: Display + IntoResponseError as ResourceResult>::Err> +{ fn schema() -> OpenapiSchema { - as ResourceResult>::schema() + as ResourceResultSchema>::schema() } } diff --git a/src/result/redirect.rs b/src/result/redirect.rs index 432203d..0a1933c 100644 --- a/src/result/redirect.rs +++ b/src/result/redirect.rs @@ -1,7 +1,7 @@ use super::{handle_error, ResourceResult}; use crate::{IntoResponseError, Response}; #[cfg(feature = "openapi")] -use crate::{NoContent, OpenapiSchema}; +use crate::{NoContent, OpenapiSchema, ResourceResultSchema}; use futures_util::future::{BoxFuture, FutureExt, TryFutureExt}; use gotham::hyper::{ header::{InvalidHeaderValue, LOCATION}, @@ -53,15 +53,16 @@ impl ResourceResult for Redirect { } .boxed() } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl ResourceResultSchema for Redirect { fn default_status() -> StatusCode { StatusCode::SEE_OTHER } - #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { - ::schema() + ::schema() } } @@ -88,15 +89,20 @@ where Err(e) => handle_error(e).map_err(|e| RedirectError::Other(e)).boxed() } } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl ResourceResultSchema for Result +where + E: Display + IntoResponseError, + ::Err: StdError + Sync +{ fn default_status() -> StatusCode { Redirect::default_status() } - #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema { - ::schema() + ::schema() } } diff --git a/src/result/result.rs b/src/result/result.rs index 71c44b7..deaa45b 100644 --- a/src/result/result.rs +++ b/src/result/result.rs @@ -1,7 +1,7 @@ use super::{handle_error, into_response_helper, ResourceResult}; -#[cfg(feature = "openapi")] -use crate::OpenapiSchema; use crate::{result::ResourceError, Response, ResponseBody}; +#[cfg(feature = "openapi")] +use crate::{OpenapiSchema, ResourceResultSchema}; use futures_core::future::Future; use gotham::hyper::StatusCode; @@ -43,8 +43,14 @@ where fn accepted_types() -> Option> { Some(vec![APPLICATION_JSON]) } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl ResourceResultSchema for Result +where + R: ResponseBody, + E: Display + IntoResponseError +{ fn schema() -> OpenapiSchema { R::schema() } diff --git a/src/result/success.rs b/src/result/success.rs index bd899bf..3c1bcaa 100644 --- a/src/result/success.rs +++ b/src/result/success.rs @@ -1,6 +1,6 @@ use super::{into_response_helper, ResourceResult}; #[cfg(feature = "openapi")] -use crate::OpenapiSchema; +use crate::{OpenapiSchema, ResourceResultSchema}; use crate::{Response, ResponseBody}; use gotham::hyper::StatusCode; use mime::{Mime, APPLICATION_JSON}; @@ -104,8 +104,13 @@ where fn accepted_types() -> Option> { Some(vec![APPLICATION_JSON]) } +} - #[cfg(feature = "openapi")] +#[cfg(feature = "openapi")] +impl ResourceResultSchema for Success +where + Self: Send +{ fn schema() -> OpenapiSchema { T::schema() } diff --git a/tests/ui/endpoint/invalid_return_type.stderr b/tests/ui/endpoint/invalid_return_type.stderr index bece7da..2879898 100644 --- a/tests/ui/endpoint/invalid_return_type.stderr +++ b/tests/ui/endpoint/invalid_return_type.stderr @@ -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 --> $DIR/invalid_return_type.rs:12:18 |