From ebea39fe0d5f9ae446dfb42bbff70a38096376ab Mon Sep 17 00:00:00 2001 From: Dominic Date: Tue, 9 Mar 2021 19:46:11 +0100 Subject: [PATCH] use openapi_type::OpenapiType for gotham_restful --- Cargo.toml | 13 +- README.md | 20 +- derive/Cargo.toml | 2 +- derive/src/endpoint.rs | 10 +- derive/src/lib.rs | 11 - derive/src/openapi_type.rs | 289 -------------------- derive/src/request_body.rs | 23 +- openapi_type/src/impls.rs | 2 +- openapi_type/src/lib.rs | 6 + openapi_type_derive/src/lib.rs | 2 +- src/endpoint.rs | 50 +++- src/lib.rs | 39 ++- src/openapi/builder.rs | 4 +- src/openapi/mod.rs | 1 - src/openapi/operation.rs | 3 +- src/openapi/router.rs | 3 +- src/openapi/types.rs | 476 --------------------------------- src/response/mod.rs | 7 +- src/response/no_content.rs | 6 +- src/response/raw.rs | 4 +- src/response/redirect.rs | 4 +- src/response/result.rs | 6 +- src/response/success.rs | 8 +- src/routing.rs | 12 +- src/types.rs | 5 +- tests/async_methods.rs | 4 +- tests/sync_methods.rs | 4 +- 27 files changed, 148 insertions(+), 866 deletions(-) delete mode 100644 derive/src/openapi_type.rs delete mode 100644 src/openapi/types.rs diff --git a/Cargo.toml b/Cargo.toml index 7c0baee..787b6da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,26 +24,23 @@ futures-core = "0.3.7" futures-util = "0.3.7" gotham = { git = "https://github.com/gotham-rs/gotham", default-features = false } gotham_derive = "0.5.0" -gotham_restful_derive = "0.2.0" +gotham_restful_derive = "0.3.0-dev" log = "0.4.8" mime = "0.3.16" serde = { version = "1.0.110", features = ["derive"] } serde_json = "1.0.58" thiserror = "1.0" -# features -chrono = { version = "0.4.19", features = ["serde"], optional = true } -uuid = { version = "0.8.1", optional = true } - # non-feature optional dependencies base64 = { version = "0.13.0", optional = true } cookie = { version = "0.15", optional = true } -gotham_middleware_diesel = { version = "0.2.0", optional = true } +gotham_middleware_diesel = { git = "https://github.com/gotham-rs/gotham", optional = true } indexmap = { version = "1.3.2", optional = true } indoc = { version = "1.0", optional = true } jsonwebtoken = { version = "7.1.0", optional = true } once_cell = { version = "1.5", optional = true } openapiv3 = { version = "=0.3.2", optional = true } +openapi_type = { version = "0.1.0-dev", optional = true } regex = { version = "1.4", optional = true } sha2 = { version = "0.9.3", optional = true } @@ -58,7 +55,7 @@ trybuild = "1.0.27" [features] default = ["cors", "errorlog", "without-openapi"] -full = ["auth", "chrono", "cors", "database", "errorlog", "openapi", "uuid"] +full = ["auth", "cors", "database", "errorlog", "openapi"] auth = ["gotham_restful_derive/auth", "base64", "cookie", "jsonwebtoken"] cors = [] @@ -67,7 +64,7 @@ errorlog = [] # These features are exclusive - https://gitlab.com/msrd0/gotham-restful/-/issues/4 without-openapi = [] -openapi = ["gotham_restful_derive/openapi", "base64", "indexmap", "indoc", "once_cell", "openapiv3", "regex", "sha2"] +openapi = ["gotham_restful_derive/openapi", "base64", "indexmap", "indoc", "once_cell", "openapiv3", "openapi_type", "regex", "sha2"] [package.metadata.docs.rs] no-default-features = true diff --git a/README.md b/README.md index 76da543..1cb82c1 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ use gotham_restful::gotham::hyper::Method; struct CustomResource; /// This type is used to parse path parameters. -#[derive(Deserialize, StateData, StaticResponseExtender)] +#[derive(Clone, Deserialize, StateData, StaticResponseExtender)] struct CustomPath { name: String } @@ -310,9 +310,9 @@ carefully both as a binary as well as a library author to avoid unwanted suprise In order to automatically create an openapi specification, gotham-restful needs knowledge over all routes and the types returned. `serde` does a great job at serialization but doesn't give -enough type information, so all types used in the router need to implement `OpenapiType`. This -can be derived for almoust any type and there should be no need to implement it manually. A simple -example looks like this: +enough type information, so all types used in the router need to implement +`OpenapiType`[openapi_type::OpenapiType]. This can be derived for almoust any type and there +should be no need to implement it manually. A simple example looks like this: ```rust #[derive(Resource)] @@ -350,15 +350,15 @@ clients in different languages without worying to exactly replicate your api in languages. However, please note that by default, the `without-openapi` feature of this crate is enabled. -Disabling it in favour of the `openapi` feature will add an additional type bound, [`OpenapiType`], -on some of the types in [`Endpoint`] and related traits. This means that some code might only -compile on either feature, but not on both. If you are writing a library that uses gotham-restful, -it is strongly recommended to pass both features through and conditionally enable the openapi -code, like this: +Disabling it in favour of the `openapi` feature will add an additional type bound, +[`OpenapiType`][openapi_type::OpenapiType], on some of the types in [`Endpoint`] and related +traits. This means that some code might only compile on either feature, but not on both. If you +are writing a library that uses gotham-restful, it is strongly recommended to pass both features +through and conditionally enable the openapi code, like this: ```rust #[derive(Deserialize, Serialize)] -#[cfg_attr(feature = "openapi", derive(OpenapiType))] +#[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Foo; ``` diff --git a/derive/Cargo.toml b/derive/Cargo.toml index e06f2b0..58c877e 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "gotham_restful_derive" -version = "0.2.0" +version = "0.3.0-dev" authors = ["Dominic Meiser "] edition = "2018" description = "Derive macros for gotham_restful" diff --git a/derive/src/endpoint.rs b/derive/src/endpoint.rs index f6f143b..457f8ee 100644 --- a/derive/src/endpoint.rs +++ b/derive/src/endpoint.rs @@ -128,14 +128,14 @@ impl EndpointType { fn placeholders_ty(&self, arg_ty: Option<&Type>) -> TokenStream { match self { Self::ReadAll | Self::Search | Self::Create | Self::UpdateAll | Self::DeleteAll => { - quote!(::gotham_restful::gotham::extractor::NoopPathExtractor) + quote!(::gotham_restful::NoopExtractor) }, Self::Read | Self::Update | Self::Delete => quote!(::gotham_restful::private::IdPlaceholder::<#arg_ty>), Self::Custom { .. } => { if self.has_placeholders().value { arg_ty.to_token_stream() } else { - quote!(::gotham_restful::gotham::extractor::NoopPathExtractor) + quote!(::gotham_restful::NoopExtractor) } }, } @@ -163,14 +163,14 @@ impl EndpointType { fn params_ty(&self, arg_ty: Option<&Type>) -> TokenStream { match self { Self::ReadAll | Self::Read | Self::Create | Self::UpdateAll | Self::Update | Self::DeleteAll | Self::Delete => { - quote!(::gotham_restful::gotham::extractor::NoopQueryStringExtractor) + quote!(::gotham_restful::NoopExtractor) }, Self::Search => quote!(#arg_ty), Self::Custom { .. } => { if self.needs_params().value { arg_ty.to_token_stream() } else { - quote!(::gotham_restful::gotham::extractor::NoopQueryStringExtractor) + quote!(::gotham_restful::NoopExtractor) } }, } @@ -201,7 +201,7 @@ impl EndpointType { if self.needs_body().value { arg_ty.to_token_stream() } else { - quote!(::gotham_restful::gotham::extractor::NoopPathExtractor) + quote!(()) } }, } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 39e2855..59ee8b6 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -24,11 +24,6 @@ use resource::expand_resource; mod resource_error; use resource_error::expand_resource_error; -#[cfg(feature = "openapi")] -mod openapi_type; -#[cfg(feature = "openapi")] -use openapi_type::expand_openapi_type; - mod private_openapi_trait; use private_openapi_trait::expand_private_openapi_trait; @@ -66,12 +61,6 @@ pub fn derive_from_body(input: TokenStream) -> TokenStream { expand_derive(input, expand_from_body) } -#[cfg(feature = "openapi")] -#[proc_macro_derive(OpenapiType, attributes(openapi))] -pub fn derive_openapi_type(input: TokenStream) -> TokenStream { - expand_derive(input, expand_openapi_type) -} - #[proc_macro_derive(RequestBody, attributes(supported_types))] pub fn derive_request_body(input: TokenStream) -> TokenStream { expand_derive(input, expand_request_body) diff --git a/derive/src/openapi_type.rs b/derive/src/openapi_type.rs deleted file mode 100644 index 4b4530d..0000000 --- a/derive/src/openapi_type.rs +++ /dev/null @@ -1,289 +0,0 @@ -use crate::util::{remove_parens, CollectToResult}; -use proc_macro2::{Ident, TokenStream}; -use quote::quote; -use syn::{ - parse_macro_input, spanned::Spanned, Attribute, AttributeArgs, Data, DataEnum, DataStruct, DeriveInput, Error, Field, - Fields, GenericParam, Generics, Lit, LitStr, Meta, NestedMeta, Path, PathSegment, PredicateType, Result, TraitBound, - TraitBoundModifier, Type, TypeParamBound, TypePath, Variant, WhereClause, WherePredicate -}; - -pub fn expand_openapi_type(input: DeriveInput) -> Result { - match (input.ident, input.generics, input.attrs, input.data) { - (ident, generics, attrs, Data::Enum(inum)) => expand_enum(ident, generics, attrs, inum), - (ident, generics, attrs, Data::Struct(strukt)) => expand_struct(ident, generics, attrs, strukt), - (_, _, _, Data::Union(uni)) => Err(Error::new( - uni.union_token.span(), - "#[derive(OpenapiType)] only works for structs and enums" - )) - } -} - -fn update_generics(generics: &Generics, where_clause: &mut Option) { - if generics.params.is_empty() { - return; - } - - if where_clause.is_none() { - *where_clause = Some(WhereClause { - where_token: Default::default(), - predicates: Default::default() - }); - } - let where_clause = where_clause.as_mut().unwrap(); - - for param in &generics.params { - if let GenericParam::Type(ty_param) = param { - where_clause.predicates.push(WherePredicate::Type(PredicateType { - lifetimes: None, - bounded_ty: Type::Path(TypePath { - qself: None, - path: Path { - leading_colon: None, - segments: vec![PathSegment { - ident: ty_param.ident.clone(), - arguments: Default::default() - }] - .into_iter() - .collect() - } - }), - colon_token: Default::default(), - bounds: vec![TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path: syn::parse_str("::gotham_restful::OpenapiType").unwrap() - })] - .into_iter() - .collect() - })); - } - } -} - -#[derive(Debug, Default)] -struct Attrs { - nullable: bool, - rename: Option -} - -fn to_string(lit: &Lit) -> Result { - match lit { - Lit::Str(str) => Ok(str.value()), - _ => Err(Error::new(lit.span(), "Expected string literal")) - } -} - -fn to_bool(lit: &Lit) -> Result { - match lit { - Lit::Bool(bool) => Ok(bool.value), - _ => Err(Error::new(lit.span(), "Expected bool")) - } -} - -fn parse_attributes(input: &[Attribute]) -> Result { - let mut parsed = Attrs::default(); - for attr in input { - if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned()) { - let tokens = remove_parens(attr.tokens.clone()); - // TODO this is not public api but syn currently doesn't offer another convenient way to parse AttributeArgs - let nested = parse_macro_input::parse::(tokens.into())?; - for meta in nested { - match &meta { - NestedMeta::Meta(Meta::NameValue(kv)) => match kv.path.segments.last().map(|s| s.ident.to_string()) { - Some(key) => match key.as_ref() { - "nullable" => parsed.nullable = to_bool(&kv.lit)?, - "rename" => parsed.rename = Some(to_string(&kv.lit)?), - _ => return Err(Error::new(kv.path.span(), "Unknown key")) - }, - _ => return Err(Error::new(meta.span(), "Unexpected token")) - }, - _ => return Err(Error::new(meta.span(), "Unexpected token")) - } - } - } - } - Ok(parsed) -} - -fn expand_variant(variant: &Variant) -> Result { - if !matches!(variant.fields, Fields::Unit) { - return Err(Error::new( - variant.span(), - "#[derive(OpenapiType)] does not support enum variants with fields" - )); - } - - let ident = &variant.ident; - - let attrs = parse_attributes(&variant.attrs)?; - let name = match attrs.rename { - Some(rename) => rename, - None => ident.to_string() - }; - - Ok(quote! { - enumeration.push(#name.to_string()); - }) -} - -fn expand_enum(ident: Ident, generics: Generics, attrs: Vec, input: DataEnum) -> Result { - let krate = super::krate(); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let mut where_clause = where_clause.cloned(); - update_generics(&generics, &mut where_clause); - - let attrs = parse_attributes(&attrs)?; - let nullable = attrs.nullable; - let name = match attrs.rename { - Some(rename) => rename, - None => ident.to_string() - }; - - let variants = input.variants.iter().map(expand_variant).collect_to_result()?; - - Ok(quote! { - impl #impl_generics #krate::OpenapiType for #ident #ty_generics - #where_clause - { - fn schema() -> #krate::OpenapiSchema - { - use #krate::{private::openapi::*, OpenapiSchema}; - - let mut enumeration : Vec = Vec::new(); - - #(#variants)* - - let schema = SchemaKind::Type(Type::String(StringType { - format: VariantOrUnknownOrEmpty::Empty, - enumeration, - ..Default::default() - })); - - OpenapiSchema { - name: Some(#name.to_string()), - nullable: #nullable, - schema, - dependencies: Default::default() - } - } - } - }) -} - -fn expand_field(field: &Field) -> Result { - let ident = match &field.ident { - Some(ident) => ident, - None => { - return Err(Error::new( - field.span(), - "#[derive(OpenapiType)] does not support fields without an ident" - )) - }, - }; - let ident_str = LitStr::new(&ident.to_string(), ident.span()); - let ty = &field.ty; - - let attrs = parse_attributes(&field.attrs)?; - let nullable = attrs.nullable; - let name = match attrs.rename { - Some(rename) => rename, - None => ident.to_string() - }; - - Ok(quote! {{ - let mut schema = <#ty>::schema(); - - if schema.nullable - { - schema.nullable = false; - } - else if !#nullable - { - required.push(#ident_str.to_string()); - } - - let keys : Vec = schema.dependencies.keys().map(|k| k.to_string()).collect(); - for dep in keys - { - let dep_schema = schema.dependencies.swap_remove(&dep); - if let Some(dep_schema) = dep_schema - { - dependencies.insert(dep, dep_schema); - } - } - - match schema.name.clone() { - Some(schema_name) => { - properties.insert( - #name.to_string(), - ReferenceOr::Reference { reference: format!("#/components/schemas/{}", schema_name) } - ); - dependencies.insert(schema_name, schema); - }, - None => { - properties.insert( - #name.to_string(), - ReferenceOr::Item(Box::new(schema.into_schema())) - ); - } - } - }}) -} - -fn expand_struct(ident: Ident, generics: Generics, attrs: Vec, input: DataStruct) -> Result { - let krate = super::krate(); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let mut where_clause = where_clause.cloned(); - update_generics(&generics, &mut where_clause); - - let attrs = parse_attributes(&attrs)?; - let nullable = attrs.nullable; - let name = match attrs.rename { - Some(rename) => rename, - None => ident.to_string() - }; - - let fields: Vec = match input.fields { - Fields::Named(named_fields) => named_fields.named.iter().map(expand_field).collect_to_result()?, - Fields::Unnamed(fields) => { - return Err(Error::new( - fields.span(), - "#[derive(OpenapiType)] does not support unnamed fields" - )) - }, - Fields::Unit => Vec::new() - }; - - Ok(quote! { - impl #impl_generics #krate::OpenapiType for #ident #ty_generics - #where_clause - { - fn schema() -> #krate::OpenapiSchema - { - use #krate::{private::{openapi::*, IndexMap}, OpenapiSchema}; - - let mut properties : IndexMap>> = IndexMap::new(); - let mut required : Vec = Vec::new(); - let mut dependencies : IndexMap = IndexMap::new(); - - #(#fields)* - - let schema = SchemaKind::Type(Type::Object(ObjectType { - properties, - required, - additional_properties: None, - min_properties: None, - max_properties: None - })); - - OpenapiSchema { - name: Some(#name.to_string()), - nullable: #nullable, - schema, - dependencies - } - } - } - }) -} diff --git a/derive/src/request_body.rs b/derive/src/request_body.rs index 9657b21..c543dfa 100644 --- a/derive/src/request_body.rs +++ b/derive/src/request_body.rs @@ -26,18 +26,25 @@ fn impl_openapi_type(_ident: &Ident, _generics: &Generics) -> TokenStream { #[cfg(feature = "openapi")] fn impl_openapi_type(ident: &Ident, generics: &Generics) -> TokenStream { let krate = super::krate(); + let openapi = quote!(#krate::private::openapi); quote! { - impl #generics #krate::OpenapiType for #ident #generics + impl #generics #krate::private::OpenapiType for #ident #generics { - fn schema() -> #krate::OpenapiSchema + fn schema() -> #krate::private::OpenapiSchema { - use #krate::{private::openapi::*, OpenapiSchema}; - - OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { - format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary), - ..Default::default() - }))) + #krate::private::OpenapiSchema::new( + #openapi::SchemaKind::Type( + #openapi::Type::String( + #openapi::StringType { + format: #openapi::VariantOrUnknownOrEmpty::Item( + #openapi::StringFormat::Binary + ), + .. ::std::default::Default::default() + } + ) + ) + ) } } } diff --git a/openapi_type/src/impls.rs b/openapi_type/src/impls.rs index d9396fd..d46a922 100644 --- a/openapi_type/src/impls.rs +++ b/openapi_type/src/impls.rs @@ -97,7 +97,7 @@ fn str_schema(format: VariantOrUnknownOrEmpty) -> OpenapiSchema { }))) } -impl_openapi_type!(String => str_schema(VariantOrUnknownOrEmpty::Empty)); +impl_openapi_type!(String, str => str_schema(VariantOrUnknownOrEmpty::Empty)); #[cfg(feature = "chrono")] impl_openapi_type!(Date, NaiveDate => { diff --git a/openapi_type/src/lib.rs b/openapi_type/src/lib.rs index 590800b..2933027 100644 --- a/openapi_type/src/lib.rs +++ b/openapi_type/src/lib.rs @@ -78,3 +78,9 @@ struct MyResponse { pub trait OpenapiType { fn schema() -> OpenapiSchema; } + +impl<'a, T: ?Sized + OpenapiType> OpenapiType for &'a T { + fn schema() -> OpenapiSchema { + T::schema() + } +} diff --git a/openapi_type_derive/src/lib.rs b/openapi_type_derive/src/lib.rs index 6d82331..10e6c0a 100644 --- a/openapi_type_derive/src/lib.rs +++ b/openapi_type_derive/src/lib.rs @@ -55,7 +55,7 @@ fn expand_openapi_type(mut input: DeriveInput) -> syn::Result { modifier: TraitBoundModifier::None, lifetimes: None, path: path!(::openapi_type::OpenapiType) - })) + })); }); generics.split_for_impl() }; diff --git a/src/endpoint.rs b/src/endpoint.rs index d8da412..2095948 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -2,11 +2,41 @@ use crate::{IntoResponse, RequestBody}; use futures_util::future::BoxFuture; use gotham::{ extractor::{PathExtractor, QueryStringExtractor}, - hyper::{Body, Method}, - state::State + hyper::{Body, Method, Response}, + router::response::extender::StaticResponseExtender, + state::{State, StateData} }; +#[cfg(feature = "openapi")] +use openapi_type::{OpenapiSchema, OpenapiType}; +use serde::{Deserialize, Deserializer}; use std::borrow::Cow; +/// A no-op extractor that can be used as a default type for [Endpoint::Placeholders] and +/// [Endpoint::Params]. +#[derive(Debug, Clone, Copy)] +pub struct NoopExtractor; + +impl<'de> Deserialize<'de> for NoopExtractor { + fn deserialize>(_: D) -> Result { + Ok(Self) + } +} + +#[cfg(feature = "openapi")] +impl OpenapiType for NoopExtractor { + fn schema() -> OpenapiSchema { + warn!("You're asking for the OpenAPI Schema for gotham_restful::NoopExtractor. This is probably not what you want."); + <() as OpenapiType>::schema() + } +} + +impl StateData for NoopExtractor {} + +impl StaticResponseExtender for NoopExtractor { + type ResBody = Body; + fn extend(_: &mut State, _: &mut Response) {} +} + // TODO: Specify default types once https://github.com/rust-lang/rust/issues/29661 lands. #[_private_openapi_trait(EndpointWithSchema)] pub trait Endpoint { @@ -23,19 +53,19 @@ pub trait Endpoint { fn has_placeholders() -> bool { false } - /// The type that parses the URI placeholders. Use [gotham::extractor::NoopPathExtractor] - /// if `has_placeholders()` returns `false`. - #[openapi_bound("Placeholders: crate::OpenapiType")] - type Placeholders: PathExtractor + Sync; + /// The type that parses the URI placeholders. Use [NoopExtractor] if `has_placeholders()` + /// returns `false`. + #[openapi_bound("Placeholders: OpenapiType")] + type Placeholders: PathExtractor + Clone + Sync; /// Returns `true` _iff_ the request parameters should be parsed. `false` by default. fn needs_params() -> bool { false } - /// The type that parses the request parameters. Use [gotham::extractor::NoopQueryStringExtractor] - /// if `needs_params()` returns `false`. - #[openapi_bound("Params: crate::OpenapiType")] - type Params: QueryStringExtractor + Sync; + /// The type that parses the request parameters. Use [NoopExtractor] if `needs_params()` + /// returns `false`. + #[openapi_bound("Params: OpenapiType")] + type Params: QueryStringExtractor + Clone + Sync; /// Returns `true` _iff_ the request body should be parsed. `false` by default. fn needs_body() -> bool { diff --git a/src/lib.rs b/src/lib.rs index 36674c3..aea56a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,7 @@ struct FooResource; /// The return type of the foo read endpoint. #[derive(Serialize)] -# #[cfg_attr(feature = "openapi", derive(OpenapiType))] +# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Foo { id: u64 } @@ -95,8 +95,8 @@ use gotham_restful::gotham::hyper::Method; struct CustomResource; /// This type is used to parse path parameters. -#[derive(Deserialize, StateData, StaticResponseExtender)] -# #[cfg_attr(feature = "openapi", derive(OpenapiType))] +#[derive(Clone, Deserialize, StateData, StaticResponseExtender)] +# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct CustomPath { name: String } @@ -225,7 +225,7 @@ A simple example that uses only a single secret looks like this: struct SecretResource; #[derive(Serialize)] -# #[cfg_attr(feature = "openapi", derive(OpenapiType))] +# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Secret { id: u64, intended_for: String @@ -331,7 +331,7 @@ A simple non-async example looks like this: struct FooResource; #[derive(Queryable, Serialize)] -# #[cfg_attr(feature = "openapi", derive(OpenapiType))] +# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Foo { id: i64, value: String @@ -363,9 +363,9 @@ carefully both as a binary as well as a library author to avoid unwanted suprise In order to automatically create an openapi specification, gotham-restful needs knowledge over all routes and the types returned. `serde` does a great job at serialization but doesn't give -enough type information, so all types used in the router need to implement `OpenapiType`. This -can be derived for almoust any type and there should be no need to implement it manually. A simple -example looks like this: +enough type information, so all types used in the router need to implement +`OpenapiType`[openapi_type::OpenapiType]. This can be derived for almoust any type and there +should be no need to implement it manually. A simple example looks like this: ```rust,no_run # #[macro_use] extern crate gotham_restful_derive; @@ -373,6 +373,7 @@ example looks like this: # mod openapi_feature_enabled { # use gotham::{router::builder::*, state::State}; # use gotham_restful::*; +# use openapi_type::OpenapiType; # use serde::{Deserialize, Serialize}; #[derive(Resource)] #[resource(read_all)] @@ -410,17 +411,17 @@ clients in different languages without worying to exactly replicate your api in languages. However, please note that by default, the `without-openapi` feature of this crate is enabled. -Disabling it in favour of the `openapi` feature will add an additional type bound, [`OpenapiType`], -on some of the types in [`Endpoint`] and related traits. This means that some code might only -compile on either feature, but not on both. If you are writing a library that uses gotham-restful, -it is strongly recommended to pass both features through and conditionally enable the openapi -code, like this: +Disabling it in favour of the `openapi` feature will add an additional type bound, +[`OpenapiType`][openapi_type::OpenapiType], on some of the types in [`Endpoint`] and related +traits. This means that some code might only compile on either feature, but not on both. If you +are writing a library that uses gotham-restful, it is strongly recommended to pass both features +through and conditionally enable the openapi code, like this: ```rust # #[macro_use] extern crate gotham_restful; # use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] -#[cfg_attr(feature = "openapi", derive(OpenapiType))] +#[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Foo; ``` @@ -478,6 +479,8 @@ pub mod private { #[cfg(feature = "openapi")] pub use indexmap::IndexMap; #[cfg(feature = "openapi")] + pub use openapi_type::{OpenapiSchema, OpenapiType}; + #[cfg(feature = "openapi")] pub use openapiv3 as openapi; } @@ -494,16 +497,12 @@ pub use cors::{handle_cors, CorsConfig, CorsRoute}; #[cfg(feature = "openapi")] mod openapi; #[cfg(feature = "openapi")] -pub use openapi::{ - builder::OpenapiInfo, - router::GetOpenapi, - types::{OpenapiSchema, OpenapiType} -}; +pub use openapi::{builder::OpenapiInfo, router::GetOpenapi}; mod endpoint; -pub use endpoint::Endpoint; #[cfg(feature = "openapi")] pub use endpoint::EndpointWithSchema; +pub use endpoint::{Endpoint, NoopExtractor}; mod response; pub use response::{ diff --git a/src/openapi/builder.rs b/src/openapi/builder.rs index 11f79f8..4fa6a0d 100644 --- a/src/openapi/builder.rs +++ b/src/openapi/builder.rs @@ -1,5 +1,5 @@ -use crate::OpenapiSchema; use indexmap::IndexMap; +use openapi_type::OpenapiSchema; use openapiv3::{ Components, OpenAPI, PathItem, ReferenceOr, ReferenceOr::{Item, Reference}, @@ -104,7 +104,7 @@ impl OpenapiBuilder { #[allow(dead_code)] mod test { use super::*; - use crate::OpenapiType; + use openapi_type::OpenapiType; #[derive(OpenapiType)] struct Message { diff --git a/src/openapi/mod.rs b/src/openapi/mod.rs index 500d190..5eefc1f 100644 --- a/src/openapi/mod.rs +++ b/src/openapi/mod.rs @@ -4,4 +4,3 @@ pub mod builder; pub mod handler; pub mod operation; pub mod router; -pub mod types; diff --git a/src/openapi/operation.rs b/src/openapi/operation.rs index 62d06d5..1823b3c 100644 --- a/src/openapi/operation.rs +++ b/src/openapi/operation.rs @@ -1,7 +1,8 @@ use super::SECURITY_NAME; -use crate::{response::OrAllTypes, EndpointWithSchema, IntoResponse, OpenapiSchema, RequestBody, ResponseSchema}; +use crate::{response::OrAllTypes, EndpointWithSchema, IntoResponse, RequestBody, ResponseSchema}; use indexmap::IndexMap; use mime::Mime; +use openapi_type::OpenapiSchema; use openapiv3::{ MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, StatusCode, Type diff --git a/src/openapi/router.rs b/src/openapi/router.rs index 3ced31b..e6b3187 100644 --- a/src/openapi/router.rs +++ b/src/openapi/router.rs @@ -3,9 +3,10 @@ use super::{ handler::{OpenapiHandler, SwaggerUiHandler}, operation::OperationDescription }; -use crate::{routing::*, EndpointWithSchema, OpenapiType, ResourceWithSchema, ResponseSchema}; +use crate::{routing::*, EndpointWithSchema, ResourceWithSchema, ResponseSchema}; use gotham::{hyper::Method, pipeline::chain::PipelineHandleChain, router::builder::*}; use once_cell::sync::Lazy; +use openapi_type::OpenapiType; use regex::{Captures, Regex}; use std::panic::RefUnwindSafe; diff --git a/src/openapi/types.rs b/src/openapi/types.rs deleted file mode 100644 index 66bf059..0000000 --- a/src/openapi/types.rs +++ /dev/null @@ -1,476 +0,0 @@ -#[cfg(feature = "chrono")] -use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc}; -use gotham::extractor::{NoopPathExtractor, NoopQueryStringExtractor}; -use indexmap::IndexMap; -use openapiv3::{ - AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, - ReferenceOr::{Item, Reference}, - Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty -}; -use std::{ - collections::{BTreeSet, HashMap, HashSet}, - hash::BuildHasher, - num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize} -}; -#[cfg(feature = "uuid")] -use uuid::Uuid; - -/** -This struct needs to be available for every type that can be part of an OpenAPI Spec. It is -already implemented for primitive types, String, Vec, Option and the like. To have it available -for your type, simply derive from [OpenapiType]. -*/ -#[derive(Debug, Clone, PartialEq)] -pub struct OpenapiSchema { - /// The name of this schema. If it is None, the schema will be inlined. - pub name: Option, - /// Whether this particular schema is nullable. Note that there is no guarantee that this will - /// make it into the final specification, it might just be interpreted as a hint to make it - /// an optional parameter. - pub nullable: bool, - /// The actual OpenAPI schema. - pub schema: SchemaKind, - /// Other schemas that this schema depends on. They will be included in the final OpenAPI Spec - /// along with this schema. - pub dependencies: IndexMap -} - -impl OpenapiSchema { - /// Create a new schema that has no name. - pub fn new(schema: SchemaKind) -> Self { - Self { - name: None, - nullable: false, - schema, - dependencies: IndexMap::new() - } - } - - /// Convert this schema to an [openapiv3::Schema] that can be serialized to the OpenAPI Spec. - pub fn into_schema(self) -> Schema { - Schema { - schema_data: SchemaData { - nullable: self.nullable, - title: self.name, - ..Default::default() - }, - schema_kind: self.schema - } - } -} - -/** -This trait needs to be implemented by every type that is being used in the OpenAPI Spec. It gives -access to the [OpenapiSchema] of this type. It is provided for primitive types, String and the -like. For use on your own types, there is a derive macro: - -``` -# #[macro_use] extern crate gotham_restful_derive; -# -#[derive(OpenapiType)] -struct MyResponse { - message: String -} -``` -*/ -pub trait OpenapiType { - fn schema() -> OpenapiSchema; -} - -impl OpenapiType for () { - fn schema() -> OpenapiSchema { - OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType { - additional_properties: Some(AdditionalProperties::Any(false)), - ..Default::default() - }))) - } -} - -impl OpenapiType for NoopPathExtractor { - fn schema() -> OpenapiSchema { - warn!("You're asking for the OpenAPI Schema for gotham::extractor::NoopPathExtractor. This is probably not what you want."); - <()>::schema() - } -} - -impl OpenapiType for NoopQueryStringExtractor { - fn schema() -> OpenapiSchema { - warn!("You're asking for the OpenAPI Schema for gotham::extractor::NoopQueryStringExtractor. This is probably not what you want."); - <()>::schema() - } -} - -impl OpenapiType for bool { - fn schema() -> OpenapiSchema { - OpenapiSchema::new(SchemaKind::Type(Type::Boolean {})) - } -} - -macro_rules! int_types { - ($($int_ty:ty),*) => {$( - impl OpenapiType for $int_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType::default()))) - } - } - )*}; - - (unsigned $($int_ty:ty),*) => {$( - impl OpenapiType for $int_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { - minimum: Some(0), - ..Default::default() - }))) - } - } - )*}; - - (gtzero $($int_ty:ty),*) => {$( - impl OpenapiType for $int_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { - minimum: Some(1), - ..Default::default() - }))) - } - } - )*}; - - (bits = $bits:expr, $($int_ty:ty),*) => {$( - impl OpenapiType for $int_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { - format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)), - ..Default::default() - }))) - } - } - )*}; - - (unsigned bits = $bits:expr, $($int_ty:ty),*) => {$( - impl OpenapiType for $int_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { - format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)), - minimum: Some(0), - ..Default::default() - }))) - } - } - )*}; - - (gtzero bits = $bits:expr, $($int_ty:ty),*) => {$( - impl OpenapiType for $int_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { - format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)), - minimum: Some(1), - ..Default::default() - }))) - } - } - )*}; -} - -int_types!(isize); -int_types!(unsigned usize); -int_types!(gtzero NonZeroUsize); -int_types!(bits = 8, i8); -int_types!(unsigned bits = 8, u8); -int_types!(gtzero bits = 8, NonZeroU8); -int_types!(bits = 16, i16); -int_types!(unsigned bits = 16, u16); -int_types!(gtzero bits = 16, NonZeroU16); -int_types!(bits = 32, i32); -int_types!(unsigned bits = 32, u32); -int_types!(gtzero bits = 32, NonZeroU32); -int_types!(bits = 64, i64); -int_types!(unsigned bits = 64, u64); -int_types!(gtzero bits = 64, NonZeroU64); -int_types!(bits = 128, i128); -int_types!(unsigned bits = 128, u128); -int_types!(gtzero bits = 128, NonZeroU128); - -macro_rules! num_types { - ($($num_ty:ty = $num_fmt:ident),*) => {$( - impl OpenapiType for $num_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Number(NumberType { - format: VariantOrUnknownOrEmpty::Item(NumberFormat::$num_fmt), - ..Default::default() - }))) - } - } - )*} -} - -num_types!(f32 = Float, f64 = Double); - -macro_rules! str_types { - ($($str_ty:ty),*) => {$( - impl OpenapiType for $str_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::String(StringType::default()))) - } - } - )*}; - - (format = $format:ident, $($str_ty:ty),*) => {$( - impl OpenapiType for $str_ty - { - fn schema() -> OpenapiSchema - { - use openapiv3::StringFormat; - - OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { - format: VariantOrUnknownOrEmpty::Item(StringFormat::$format), - ..Default::default() - }))) - } - } - )*}; - - (format_str = $format:expr, $($str_ty:ty),*) => {$( - impl OpenapiType for $str_ty - { - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { - format: VariantOrUnknownOrEmpty::Unknown($format.to_string()), - ..Default::default() - }))) - } - } - )*}; -} - -str_types!(String, &str); - -#[cfg(feature = "chrono")] -str_types!(format = Date, Date, Date, Date, NaiveDate); -#[cfg(feature = "chrono")] -str_types!( - format = DateTime, - DateTime, - DateTime, - DateTime, - NaiveDateTime -); - -#[cfg(feature = "uuid")] -str_types!(format_str = "uuid", Uuid); - -impl OpenapiType for Option { - fn schema() -> OpenapiSchema { - let schema = T::schema(); - let mut dependencies = schema.dependencies.clone(); - let schema = match schema.name.clone() { - Some(name) => { - let reference = Reference { - reference: format!("#/components/schemas/{}", name) - }; - dependencies.insert(name, schema); - SchemaKind::AllOf { all_of: vec![reference] } - }, - None => schema.schema - }; - - OpenapiSchema { - nullable: true, - name: None, - schema, - dependencies - } - } -} - -impl OpenapiType for Vec { - fn schema() -> OpenapiSchema { - let schema = T::schema(); - let mut dependencies = schema.dependencies.clone(); - - let items = match schema.name.clone() { - Some(name) => { - let reference = Reference { - reference: format!("#/components/schemas/{}", name) - }; - dependencies.insert(name, schema); - reference - }, - None => Item(Box::new(schema.into_schema())) - }; - - OpenapiSchema { - nullable: false, - name: None, - schema: SchemaKind::Type(Type::Array(ArrayType { - items, - min_items: None, - max_items: None, - unique_items: false - })), - dependencies - } - } -} - -impl OpenapiType for BTreeSet { - fn schema() -> OpenapiSchema { - as OpenapiType>::schema() - } -} - -impl OpenapiType for HashSet { - fn schema() -> OpenapiSchema { - as OpenapiType>::schema() - } -} - -impl OpenapiType for HashMap { - fn schema() -> OpenapiSchema { - let key_schema = K::schema(); - let mut dependencies = key_schema.dependencies.clone(); - - let keys = match key_schema.name.clone() { - Some(name) => { - let reference = Reference { - reference: format!("#/components/schemas/{}", name) - }; - dependencies.insert(name, key_schema); - reference - }, - None => Item(Box::new(key_schema.into_schema())) - }; - - let schema = T::schema(); - dependencies.extend(schema.dependencies.iter().map(|(k, v)| (k.clone(), v.clone()))); - - let items = Box::new(match schema.name.clone() { - Some(name) => { - let reference = Reference { - reference: format!("#/components/schemas/{}", name) - }; - dependencies.insert(name, schema); - reference - }, - None => Item(schema.into_schema()) - }); - - let mut properties = IndexMap::new(); - properties.insert("default".to_owned(), keys); - - OpenapiSchema { - nullable: false, - name: None, - schema: SchemaKind::Type(Type::Object(ObjectType { - properties, - required: vec!["default".to_owned()], - additional_properties: Some(AdditionalProperties::Schema(items)), - ..Default::default() - })), - dependencies - } - } -} - -impl OpenapiType for serde_json::Value { - fn schema() -> OpenapiSchema { - OpenapiSchema { - nullable: true, - name: None, - schema: SchemaKind::Any(Default::default()), - dependencies: Default::default() - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use serde_json::Value; - - type Unit = (); - - macro_rules! assert_schema { - ($ty:ident $(<$($generic:ident),+>)* => $json:expr) => { - paste::item! { - #[test] - fn []() - { - let schema = <$ty $(<$($generic),+>)* as OpenapiType>::schema().into_schema(); - let schema_json = serde_json::to_string(&schema).expect(&format!("Unable to serialize schema for {}", stringify!($ty))); - assert_eq!(schema_json, $json); - } - } - }; - } - - assert_schema!(Unit => r#"{"type":"object","additionalProperties":false}"#); - assert_schema!(bool => r#"{"type":"boolean"}"#); - - assert_schema!(isize => r#"{"type":"integer"}"#); - assert_schema!(usize => r#"{"type":"integer","minimum":0}"#); - assert_schema!(i8 => r#"{"type":"integer","format":"int8"}"#); - assert_schema!(u8 => r#"{"type":"integer","format":"int8","minimum":0}"#); - assert_schema!(i16 => r#"{"type":"integer","format":"int16"}"#); - assert_schema!(u16 => r#"{"type":"integer","format":"int16","minimum":0}"#); - assert_schema!(i32 => r#"{"type":"integer","format":"int32"}"#); - assert_schema!(u32 => r#"{"type":"integer","format":"int32","minimum":0}"#); - assert_schema!(i64 => r#"{"type":"integer","format":"int64"}"#); - assert_schema!(u64 => r#"{"type":"integer","format":"int64","minimum":0}"#); - assert_schema!(i128 => r#"{"type":"integer","format":"int128"}"#); - assert_schema!(u128 => r#"{"type":"integer","format":"int128","minimum":0}"#); - - assert_schema!(NonZeroUsize => r#"{"type":"integer","minimum":1}"#); - assert_schema!(NonZeroU8 => r#"{"type":"integer","format":"int8","minimum":1}"#); - assert_schema!(NonZeroU16 => r#"{"type":"integer","format":"int16","minimum":1}"#); - assert_schema!(NonZeroU32 => r#"{"type":"integer","format":"int32","minimum":1}"#); - assert_schema!(NonZeroU64 => r#"{"type":"integer","format":"int64","minimum":1}"#); - assert_schema!(NonZeroU128 => r#"{"type":"integer","format":"int128","minimum":1}"#); - - assert_schema!(f32 => r#"{"type":"number","format":"float"}"#); - assert_schema!(f64 => r#"{"type":"number","format":"double"}"#); - - assert_schema!(String => r#"{"type":"string"}"#); - - #[cfg(feature = "uuid")] - assert_schema!(Uuid => r#"{"type":"string","format":"uuid"}"#); - - #[cfg(feature = "chrono")] - mod chrono { - use super::*; - - assert_schema!(Date => r#"{"type":"string","format":"date"}"#); - assert_schema!(Date => r#"{"type":"string","format":"date"}"#); - assert_schema!(Date => r#"{"type":"string","format":"date"}"#); - assert_schema!(NaiveDate => r#"{"type":"string","format":"date"}"#); - assert_schema!(DateTime => r#"{"type":"string","format":"date-time"}"#); - assert_schema!(DateTime => r#"{"type":"string","format":"date-time"}"#); - assert_schema!(DateTime => r#"{"type":"string","format":"date-time"}"#); - assert_schema!(NaiveDateTime => r#"{"type":"string","format":"date-time"}"#); - } - - assert_schema!(Option => r#"{"nullable":true,"type":"string"}"#); - assert_schema!(Vec => r#"{"type":"array","items":{"type":"string"}}"#); - assert_schema!(BTreeSet => r#"{"type":"array","items":{"type":"string"}}"#); - assert_schema!(HashSet => r#"{"type":"array","items":{"type":"string"}}"#); - assert_schema!(HashMap => r#"{"type":"object","properties":{"default":{"type":"integer","format":"int64"}},"required":["default"],"additionalProperties":{"type":"string"}}"#); - assert_schema!(Value => r#"{"nullable":true}"#); -} diff --git a/src/response/mod.rs b/src/response/mod.rs index b2796dc..bdf7c66 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -1,6 +1,3 @@ -#[cfg(feature = "openapi")] -use crate::OpenapiSchema; - use futures_util::future::{self, BoxFuture, FutureExt}; use gotham::{ handler::HandlerError, @@ -10,6 +7,8 @@ use gotham::{ } }; use mime::{Mime, APPLICATION_JSON, STAR_STAR}; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiSchema; use serde::Serialize; use std::{ convert::Infallible, @@ -259,7 +258,7 @@ mod test { use thiserror::Error; #[derive(Debug, Default, Deserialize, Serialize)] - #[cfg_attr(feature = "openapi", derive(crate::OpenapiType))] + #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Msg { msg: String } diff --git a/src/response/no_content.rs b/src/response/no_content.rs index 73159c1..3a10b3b 100644 --- a/src/response/no_content.rs +++ b/src/response/no_content.rs @@ -1,12 +1,14 @@ use super::{handle_error, IntoResponse}; -use crate::{IntoResponseError, Response}; #[cfg(feature = "openapi")] -use crate::{OpenapiSchema, OpenapiType, ResponseSchema}; +use crate::ResponseSchema; +use crate::{IntoResponseError, Response}; use futures_util::{future, future::FutureExt}; use gotham::hyper::header::{HeaderMap, HeaderValue, IntoHeaderName}; #[cfg(feature = "openapi")] use gotham::hyper::StatusCode; use mime::Mime; +#[cfg(feature = "openapi")] +use openapi_type::{OpenapiSchema, OpenapiType}; use std::{fmt::Display, future::Future, pin::Pin}; /** diff --git a/src/response/raw.rs b/src/response/raw.rs index 6c003dc..3722146 100644 --- a/src/response/raw.rs +++ b/src/response/raw.rs @@ -1,7 +1,9 @@ use super::{handle_error, IntoResponse, IntoResponseError}; use crate::{FromBody, RequestBody, ResourceType, Response}; #[cfg(feature = "openapi")] -use crate::{IntoResponseWithSchema, OpenapiSchema, OpenapiType, ResponseSchema}; +use crate::{IntoResponseWithSchema, ResponseSchema}; +#[cfg(feature = "openapi")] +use openapi_type::{OpenapiSchema, OpenapiType}; use futures_core::future::Future; use futures_util::{future, future::FutureExt}; diff --git a/src/response/redirect.rs b/src/response/redirect.rs index 8b6e854..f1edd82 100644 --- a/src/response/redirect.rs +++ b/src/response/redirect.rs @@ -1,12 +1,14 @@ use super::{handle_error, IntoResponse}; use crate::{IntoResponseError, Response}; #[cfg(feature = "openapi")] -use crate::{NoContent, OpenapiSchema, ResponseSchema}; +use crate::{NoContent, ResponseSchema}; use futures_util::future::{BoxFuture, FutureExt, TryFutureExt}; use gotham::hyper::{ header::{InvalidHeaderValue, LOCATION}, Body, StatusCode }; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiSchema; use std::{ error::Error as StdError, fmt::{Debug, Display} diff --git a/src/response/result.rs b/src/response/result.rs index a28803f..f0ddc91 100644 --- a/src/response/result.rs +++ b/src/response/result.rs @@ -1,7 +1,9 @@ use super::{handle_error, IntoResponse, ResourceError}; #[cfg(feature = "openapi")] -use crate::{OpenapiSchema, ResponseSchema}; +use crate::ResponseSchema; use crate::{Response, ResponseBody, Success}; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiSchema; use futures_core::future::Future; use gotham::hyper::StatusCode; @@ -64,7 +66,7 @@ mod test { use thiserror::Error; #[derive(Debug, Default, Deserialize, Serialize)] - #[cfg_attr(feature = "openapi", derive(crate::OpenapiType))] + #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Msg { msg: String } diff --git a/src/response/success.rs b/src/response/success.rs index 24d6b3c..31f9374 100644 --- a/src/response/success.rs +++ b/src/response/success.rs @@ -1,6 +1,6 @@ use super::IntoResponse; #[cfg(feature = "openapi")] -use crate::{OpenapiSchema, ResponseSchema}; +use crate::ResponseSchema; use crate::{Response, ResponseBody}; use futures_util::future::{self, FutureExt}; use gotham::hyper::{ @@ -8,6 +8,8 @@ use gotham::hyper::{ StatusCode }; use mime::{Mime, APPLICATION_JSON}; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiSchema; use std::{fmt::Debug, future::Future, pin::Pin}; /** @@ -27,7 +29,7 @@ Usage example: # struct MyResource; # #[derive(Deserialize, Serialize)] -# #[cfg_attr(feature = "openapi", derive(OpenapiType))] +# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct MyResponse { message: &'static str } @@ -96,7 +98,7 @@ mod test { use gotham::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN; #[derive(Debug, Default, Serialize)] - #[cfg_attr(feature = "openapi", derive(crate::OpenapiType))] + #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))] struct Msg { msg: String } diff --git a/src/routing.rs b/src/routing.rs index f41dc93..c7cd5a6 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -4,7 +4,6 @@ use crate::openapi::{ router::OpenapiRouter }; use crate::{response::ResourceError, Endpoint, FromBody, IntoResponse, Resource, Response}; - #[cfg(feature = "cors")] use gotham::router::route::matcher::AccessControlRequestMethodMatcher; use gotham::{ @@ -20,10 +19,12 @@ use gotham::{ state::{FromState, State} }; use mime::{Mime, APPLICATION_JSON}; -use std::panic::RefUnwindSafe; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiType; +use std::{any::TypeId, panic::RefUnwindSafe}; /// Allow us to extract an id from a path. -#[derive(Debug, Deserialize, StateData, StaticResponseExtender)] +#[derive(Clone, Copy, Debug, Deserialize, StateData, StaticResponseExtender)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] pub struct PathExtractor { pub id: ID @@ -91,6 +92,11 @@ where { trace!("entering endpoint_handler"); let placeholders = E::Placeholders::take_from(state); + // workaround for E::Placeholders and E::Param being the same type + // when fixed remove `Clone` requirement on endpoint + if TypeId::of::() == TypeId::of::() { + state.put(placeholders.clone()); + } let params = E::Params::take_from(state); let body = match E::needs_body() { diff --git a/src/types.rs b/src/types.rs index ca08bec..20be58d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,8 +1,7 @@ -#[cfg(feature = "openapi")] -use crate::OpenapiType; - use gotham::hyper::body::Bytes; use mime::{Mime, APPLICATION_JSON}; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiType; use serde::{de::DeserializeOwned, Serialize}; use std::error::Error; diff --git a/tests/async_methods.rs b/tests/async_methods.rs index 21a74b5..9a1669b 100644 --- a/tests/async_methods.rs +++ b/tests/async_methods.rs @@ -9,6 +9,8 @@ use gotham::{ }; use gotham_restful::*; use mime::{APPLICATION_JSON, TEXT_PLAIN}; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiType; use serde::Deserialize; use tokio::time::{sleep, Duration}; @@ -28,7 +30,7 @@ struct FooBody { data: String } -#[derive(Deserialize, StateData, StaticResponseExtender)] +#[derive(Clone, Deserialize, StateData, StaticResponseExtender)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] #[allow(dead_code)] struct FooSearch { diff --git a/tests/sync_methods.rs b/tests/sync_methods.rs index 4e07259..2b440fa 100644 --- a/tests/sync_methods.rs +++ b/tests/sync_methods.rs @@ -4,6 +4,8 @@ extern crate gotham_derive; use gotham::{router::builder::*, test::TestServer}; use gotham_restful::*; use mime::{APPLICATION_JSON, TEXT_PLAIN}; +#[cfg(feature = "openapi")] +use openapi_type::OpenapiType; use serde::Deserialize; mod util { @@ -22,7 +24,7 @@ struct FooBody { data: String } -#[derive(Deserialize, StateData, StaticResponseExtender)] +#[derive(Clone, Deserialize, StateData, StaticResponseExtender)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] #[allow(dead_code)] struct FooSearch {