mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-22 20:52:27 +00:00
use openapi_type::OpenapiType for gotham_restful
This commit is contained in:
parent
eecd192458
commit
ebea39fe0d
27 changed files with 148 additions and 866 deletions
13
Cargo.toml
13
Cargo.toml
|
@ -24,26 +24,23 @@ futures-core = "0.3.7"
|
||||||
futures-util = "0.3.7"
|
futures-util = "0.3.7"
|
||||||
gotham = { git = "https://github.com/gotham-rs/gotham", default-features = false }
|
gotham = { git = "https://github.com/gotham-rs/gotham", default-features = false }
|
||||||
gotham_derive = "0.5.0"
|
gotham_derive = "0.5.0"
|
||||||
gotham_restful_derive = "0.2.0"
|
gotham_restful_derive = "0.3.0-dev"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
serde = { version = "1.0.110", features = ["derive"] }
|
serde = { version = "1.0.110", features = ["derive"] }
|
||||||
serde_json = "1.0.58"
|
serde_json = "1.0.58"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
# features
|
|
||||||
chrono = { version = "0.4.19", features = ["serde"], optional = true }
|
|
||||||
uuid = { version = "0.8.1", optional = true }
|
|
||||||
|
|
||||||
# non-feature optional dependencies
|
# non-feature optional dependencies
|
||||||
base64 = { version = "0.13.0", optional = true }
|
base64 = { version = "0.13.0", optional = true }
|
||||||
cookie = { version = "0.15", 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 }
|
indexmap = { version = "1.3.2", optional = true }
|
||||||
indoc = { version = "1.0", optional = true }
|
indoc = { version = "1.0", optional = true }
|
||||||
jsonwebtoken = { version = "7.1.0", optional = true }
|
jsonwebtoken = { version = "7.1.0", optional = true }
|
||||||
once_cell = { version = "1.5", optional = true }
|
once_cell = { version = "1.5", optional = true }
|
||||||
openapiv3 = { version = "=0.3.2", optional = true }
|
openapiv3 = { version = "=0.3.2", optional = true }
|
||||||
|
openapi_type = { version = "0.1.0-dev", optional = true }
|
||||||
regex = { version = "1.4", optional = true }
|
regex = { version = "1.4", optional = true }
|
||||||
sha2 = { version = "0.9.3", optional = true }
|
sha2 = { version = "0.9.3", optional = true }
|
||||||
|
|
||||||
|
@ -58,7 +55,7 @@ trybuild = "1.0.27"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["cors", "errorlog", "without-openapi"]
|
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"]
|
auth = ["gotham_restful_derive/auth", "base64", "cookie", "jsonwebtoken"]
|
||||||
cors = []
|
cors = []
|
||||||
|
@ -67,7 +64,7 @@ errorlog = []
|
||||||
|
|
||||||
# These features are exclusive - https://gitlab.com/msrd0/gotham-restful/-/issues/4
|
# These features are exclusive - https://gitlab.com/msrd0/gotham-restful/-/issues/4
|
||||||
without-openapi = []
|
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]
|
[package.metadata.docs.rs]
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
|
|
20
README.md
20
README.md
|
@ -100,7 +100,7 @@ use gotham_restful::gotham::hyper::Method;
|
||||||
struct CustomResource;
|
struct CustomResource;
|
||||||
|
|
||||||
/// This type is used to parse path parameters.
|
/// This type is used to parse path parameters.
|
||||||
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]
|
||||||
struct CustomPath {
|
struct CustomPath {
|
||||||
name: String
|
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
|
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
|
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
|
enough type information, so all types used in the router need to implement
|
||||||
can be derived for almoust any type and there should be no need to implement it manually. A simple
|
`OpenapiType`[openapi_type::OpenapiType]. This can be derived for almoust any type and there
|
||||||
example looks like this:
|
should be no need to implement it manually. A simple example looks like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
@ -350,15 +350,15 @@ clients in different languages without worying to exactly replicate your api in
|
||||||
languages.
|
languages.
|
||||||
|
|
||||||
However, please note that by default, the `without-openapi` feature of this crate is enabled.
|
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`],
|
Disabling it in favour of the `openapi` feature will add an additional type bound,
|
||||||
on some of the types in [`Endpoint`] and related traits. This means that some code might only
|
[`OpenapiType`][openapi_type::OpenapiType], on some of the types in [`Endpoint`] and related
|
||||||
compile on either feature, but not on both. If you are writing a library that uses gotham-restful,
|
traits. This means that some code might only compile on either feature, but not on both. If you
|
||||||
it is strongly recommended to pass both features through and conditionally enable the openapi
|
are writing a library that uses gotham-restful, it is strongly recommended to pass both features
|
||||||
code, like this:
|
through and conditionally enable the openapi code, like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Foo;
|
struct Foo;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "gotham_restful_derive"
|
name = "gotham_restful_derive"
|
||||||
version = "0.2.0"
|
version = "0.3.0-dev"
|
||||||
authors = ["Dominic Meiser <git@msrd0.de>"]
|
authors = ["Dominic Meiser <git@msrd0.de>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Derive macros for gotham_restful"
|
description = "Derive macros for gotham_restful"
|
||||||
|
|
|
@ -128,14 +128,14 @@ impl EndpointType {
|
||||||
fn placeholders_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
|
fn placeholders_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
|
||||||
match self {
|
match self {
|
||||||
Self::ReadAll | Self::Search | Self::Create | Self::UpdateAll | Self::DeleteAll => {
|
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::Read | Self::Update | Self::Delete => quote!(::gotham_restful::private::IdPlaceholder::<#arg_ty>),
|
||||||
Self::Custom { .. } => {
|
Self::Custom { .. } => {
|
||||||
if self.has_placeholders().value {
|
if self.has_placeholders().value {
|
||||||
arg_ty.to_token_stream()
|
arg_ty.to_token_stream()
|
||||||
} else {
|
} 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 {
|
fn params_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
|
||||||
match self {
|
match self {
|
||||||
Self::ReadAll | Self::Read | Self::Create | Self::UpdateAll | Self::Update | Self::DeleteAll | Self::Delete => {
|
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::Search => quote!(#arg_ty),
|
||||||
Self::Custom { .. } => {
|
Self::Custom { .. } => {
|
||||||
if self.needs_params().value {
|
if self.needs_params().value {
|
||||||
arg_ty.to_token_stream()
|
arg_ty.to_token_stream()
|
||||||
} else {
|
} else {
|
||||||
quote!(::gotham_restful::gotham::extractor::NoopQueryStringExtractor)
|
quote!(::gotham_restful::NoopExtractor)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ impl EndpointType {
|
||||||
if self.needs_body().value {
|
if self.needs_body().value {
|
||||||
arg_ty.to_token_stream()
|
arg_ty.to_token_stream()
|
||||||
} else {
|
} else {
|
||||||
quote!(::gotham_restful::gotham::extractor::NoopPathExtractor)
|
quote!(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,6 @@ use resource::expand_resource;
|
||||||
mod resource_error;
|
mod resource_error;
|
||||||
use resource_error::expand_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;
|
mod private_openapi_trait;
|
||||||
use private_openapi_trait::expand_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)
|
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))]
|
#[proc_macro_derive(RequestBody, attributes(supported_types))]
|
||||||
pub fn derive_request_body(input: TokenStream) -> TokenStream {
|
pub fn derive_request_body(input: TokenStream) -> TokenStream {
|
||||||
expand_derive(input, expand_request_body)
|
expand_derive(input, expand_request_body)
|
||||||
|
|
|
@ -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<TokenStream> {
|
|
||||||
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<WhereClause>) {
|
|
||||||
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<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_string(lit: &Lit) -> Result<String> {
|
|
||||||
match lit {
|
|
||||||
Lit::Str(str) => Ok(str.value()),
|
|
||||||
_ => Err(Error::new(lit.span(), "Expected string literal"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_bool(lit: &Lit) -> Result<bool> {
|
|
||||||
match lit {
|
|
||||||
Lit::Bool(bool) => Ok(bool.value),
|
|
||||||
_ => Err(Error::new(lit.span(), "Expected bool"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_attributes(input: &[Attribute]) -> Result<Attrs> {
|
|
||||||
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::<AttributeArgs>(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<TokenStream> {
|
|
||||||
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<Attribute>, input: DataEnum) -> Result<TokenStream> {
|
|
||||||
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<String> = 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<TokenStream> {
|
|
||||||
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<String> = 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<Attribute>, input: DataStruct) -> Result<TokenStream> {
|
|
||||||
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<TokenStream> = 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<String, ReferenceOr<Box<Schema>>> = IndexMap::new();
|
|
||||||
let mut required : Vec<String> = Vec::new();
|
|
||||||
let mut dependencies : IndexMap<String, OpenapiSchema> = 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -26,18 +26,25 @@ fn impl_openapi_type(_ident: &Ident, _generics: &Generics) -> TokenStream {
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn impl_openapi_type(ident: &Ident, generics: &Generics) -> TokenStream {
|
fn impl_openapi_type(ident: &Ident, generics: &Generics) -> TokenStream {
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
|
let openapi = quote!(#krate::private::openapi);
|
||||||
|
|
||||||
quote! {
|
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};
|
#krate::private::OpenapiSchema::new(
|
||||||
|
#openapi::SchemaKind::Type(
|
||||||
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
|
#openapi::Type::String(
|
||||||
format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
|
#openapi::StringType {
|
||||||
..Default::default()
|
format: #openapi::VariantOrUnknownOrEmpty::Item(
|
||||||
})))
|
#openapi::StringFormat::Binary
|
||||||
|
),
|
||||||
|
.. ::std::default::Default::default()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ fn str_schema(format: VariantOrUnknownOrEmpty<StringFormat>) -> OpenapiSchema {
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_openapi_type!(String => str_schema(VariantOrUnknownOrEmpty::Empty));
|
impl_openapi_type!(String, str => str_schema(VariantOrUnknownOrEmpty::Empty));
|
||||||
|
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
impl_openapi_type!(Date<T: TimeZone>, NaiveDate => {
|
impl_openapi_type!(Date<T: TimeZone>, NaiveDate => {
|
||||||
|
|
|
@ -78,3 +78,9 @@ struct MyResponse {
|
||||||
pub trait OpenapiType {
|
pub trait OpenapiType {
|
||||||
fn schema() -> OpenapiSchema;
|
fn schema() -> OpenapiSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized + OpenapiType> OpenapiType for &'a T {
|
||||||
|
fn schema() -> OpenapiSchema {
|
||||||
|
T::schema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ fn expand_openapi_type(mut input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||||
modifier: TraitBoundModifier::None,
|
modifier: TraitBoundModifier::None,
|
||||||
lifetimes: None,
|
lifetimes: None,
|
||||||
path: path!(::openapi_type::OpenapiType)
|
path: path!(::openapi_type::OpenapiType)
|
||||||
}))
|
}));
|
||||||
});
|
});
|
||||||
generics.split_for_impl()
|
generics.split_for_impl()
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,11 +2,41 @@ use crate::{IntoResponse, RequestBody};
|
||||||
use futures_util::future::BoxFuture;
|
use futures_util::future::BoxFuture;
|
||||||
use gotham::{
|
use gotham::{
|
||||||
extractor::{PathExtractor, QueryStringExtractor},
|
extractor::{PathExtractor, QueryStringExtractor},
|
||||||
hyper::{Body, Method},
|
hyper::{Body, Method, Response},
|
||||||
state::State
|
router::response::extender::StaticResponseExtender,
|
||||||
|
state::{State, StateData}
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::{OpenapiSchema, OpenapiType};
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::borrow::Cow;
|
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: Deserializer<'de>>(_: D) -> Result<Self, D::Error> {
|
||||||
|
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<Body>) {}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Specify default types once https://github.com/rust-lang/rust/issues/29661 lands.
|
// TODO: Specify default types once https://github.com/rust-lang/rust/issues/29661 lands.
|
||||||
#[_private_openapi_trait(EndpointWithSchema)]
|
#[_private_openapi_trait(EndpointWithSchema)]
|
||||||
pub trait Endpoint {
|
pub trait Endpoint {
|
||||||
|
@ -23,19 +53,19 @@ pub trait Endpoint {
|
||||||
fn has_placeholders() -> bool {
|
fn has_placeholders() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
/// The type that parses the URI placeholders. Use [gotham::extractor::NoopPathExtractor]
|
/// The type that parses the URI placeholders. Use [NoopExtractor] if `has_placeholders()`
|
||||||
/// if `has_placeholders()` returns `false`.
|
/// returns `false`.
|
||||||
#[openapi_bound("Placeholders: crate::OpenapiType")]
|
#[openapi_bound("Placeholders: OpenapiType")]
|
||||||
type Placeholders: PathExtractor<Body> + Sync;
|
type Placeholders: PathExtractor<Body> + Clone + Sync;
|
||||||
|
|
||||||
/// Returns `true` _iff_ the request parameters should be parsed. `false` by default.
|
/// Returns `true` _iff_ the request parameters should be parsed. `false` by default.
|
||||||
fn needs_params() -> bool {
|
fn needs_params() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
/// The type that parses the request parameters. Use [gotham::extractor::NoopQueryStringExtractor]
|
/// The type that parses the request parameters. Use [NoopExtractor] if `needs_params()`
|
||||||
/// if `needs_params()` returns `false`.
|
/// returns `false`.
|
||||||
#[openapi_bound("Params: crate::OpenapiType")]
|
#[openapi_bound("Params: OpenapiType")]
|
||||||
type Params: QueryStringExtractor<Body> + Sync;
|
type Params: QueryStringExtractor<Body> + Clone + Sync;
|
||||||
|
|
||||||
/// Returns `true` _iff_ the request body should be parsed. `false` by default.
|
/// Returns `true` _iff_ the request body should be parsed. `false` by default.
|
||||||
fn needs_body() -> bool {
|
fn needs_body() -> bool {
|
||||||
|
|
39
src/lib.rs
39
src/lib.rs
|
@ -60,7 +60,7 @@ struct FooResource;
|
||||||
|
|
||||||
/// The return type of the foo read endpoint.
|
/// The return type of the foo read endpoint.
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
# #[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Foo {
|
struct Foo {
|
||||||
id: u64
|
id: u64
|
||||||
}
|
}
|
||||||
|
@ -95,8 +95,8 @@ use gotham_restful::gotham::hyper::Method;
|
||||||
struct CustomResource;
|
struct CustomResource;
|
||||||
|
|
||||||
/// This type is used to parse path parameters.
|
/// This type is used to parse path parameters.
|
||||||
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]
|
||||||
# #[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct CustomPath {
|
struct CustomPath {
|
||||||
name: String
|
name: String
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ A simple example that uses only a single secret looks like this:
|
||||||
struct SecretResource;
|
struct SecretResource;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
# #[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Secret {
|
struct Secret {
|
||||||
id: u64,
|
id: u64,
|
||||||
intended_for: String
|
intended_for: String
|
||||||
|
@ -331,7 +331,7 @@ A simple non-async example looks like this:
|
||||||
struct FooResource;
|
struct FooResource;
|
||||||
|
|
||||||
#[derive(Queryable, Serialize)]
|
#[derive(Queryable, Serialize)]
|
||||||
# #[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Foo {
|
struct Foo {
|
||||||
id: i64,
|
id: i64,
|
||||||
value: String
|
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
|
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
|
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
|
enough type information, so all types used in the router need to implement
|
||||||
can be derived for almoust any type and there should be no need to implement it manually. A simple
|
`OpenapiType`[openapi_type::OpenapiType]. This can be derived for almoust any type and there
|
||||||
example looks like this:
|
should be no need to implement it manually. A simple example looks like this:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
# #[macro_use] extern crate gotham_restful_derive;
|
# #[macro_use] extern crate gotham_restful_derive;
|
||||||
|
@ -373,6 +373,7 @@ example looks like this:
|
||||||
# mod openapi_feature_enabled {
|
# mod openapi_feature_enabled {
|
||||||
# use gotham::{router::builder::*, state::State};
|
# use gotham::{router::builder::*, state::State};
|
||||||
# use gotham_restful::*;
|
# use gotham_restful::*;
|
||||||
|
# use openapi_type::OpenapiType;
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[resource(read_all)]
|
#[resource(read_all)]
|
||||||
|
@ -410,17 +411,17 @@ clients in different languages without worying to exactly replicate your api in
|
||||||
languages.
|
languages.
|
||||||
|
|
||||||
However, please note that by default, the `without-openapi` feature of this crate is enabled.
|
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`],
|
Disabling it in favour of the `openapi` feature will add an additional type bound,
|
||||||
on some of the types in [`Endpoint`] and related traits. This means that some code might only
|
[`OpenapiType`][openapi_type::OpenapiType], on some of the types in [`Endpoint`] and related
|
||||||
compile on either feature, but not on both. If you are writing a library that uses gotham-restful,
|
traits. This means that some code might only compile on either feature, but not on both. If you
|
||||||
it is strongly recommended to pass both features through and conditionally enable the openapi
|
are writing a library that uses gotham-restful, it is strongly recommended to pass both features
|
||||||
code, like this:
|
through and conditionally enable the openapi code, like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate gotham_restful;
|
# #[macro_use] extern crate gotham_restful;
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Foo;
|
struct Foo;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -478,6 +479,8 @@ pub mod private {
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
pub use indexmap::IndexMap;
|
pub use indexmap::IndexMap;
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
|
pub use openapi_type::{OpenapiSchema, OpenapiType};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
pub use openapiv3 as openapi;
|
pub use openapiv3 as openapi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,16 +497,12 @@ pub use cors::{handle_cors, CorsConfig, CorsRoute};
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
mod openapi;
|
mod openapi;
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
pub use openapi::{
|
pub use openapi::{builder::OpenapiInfo, router::GetOpenapi};
|
||||||
builder::OpenapiInfo,
|
|
||||||
router::GetOpenapi,
|
|
||||||
types::{OpenapiSchema, OpenapiType}
|
|
||||||
};
|
|
||||||
|
|
||||||
mod endpoint;
|
mod endpoint;
|
||||||
pub use endpoint::Endpoint;
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
pub use endpoint::EndpointWithSchema;
|
pub use endpoint::EndpointWithSchema;
|
||||||
|
pub use endpoint::{Endpoint, NoopExtractor};
|
||||||
|
|
||||||
mod response;
|
mod response;
|
||||||
pub use response::{
|
pub use response::{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::OpenapiSchema;
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use openapi_type::OpenapiSchema;
|
||||||
use openapiv3::{
|
use openapiv3::{
|
||||||
Components, OpenAPI, PathItem, ReferenceOr,
|
Components, OpenAPI, PathItem, ReferenceOr,
|
||||||
ReferenceOr::{Item, Reference},
|
ReferenceOr::{Item, Reference},
|
||||||
|
@ -104,7 +104,7 @@ impl OpenapiBuilder {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::OpenapiType;
|
use openapi_type::OpenapiType;
|
||||||
|
|
||||||
#[derive(OpenapiType)]
|
#[derive(OpenapiType)]
|
||||||
struct Message {
|
struct Message {
|
||||||
|
|
|
@ -4,4 +4,3 @@ pub mod builder;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod operation;
|
pub mod operation;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod types;
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use super::SECURITY_NAME;
|
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 indexmap::IndexMap;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
use openapi_type::OpenapiSchema;
|
||||||
use openapiv3::{
|
use openapiv3::{
|
||||||
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, ReferenceOr::Item,
|
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, ReferenceOr::Item,
|
||||||
RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, StatusCode, Type
|
RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, StatusCode, Type
|
||||||
|
|
|
@ -3,9 +3,10 @@ use super::{
|
||||||
handler::{OpenapiHandler, SwaggerUiHandler},
|
handler::{OpenapiHandler, SwaggerUiHandler},
|
||||||
operation::OperationDescription
|
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 gotham::{hyper::Method, pipeline::chain::PipelineHandleChain, router::builder::*};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use openapi_type::OpenapiType;
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
|
|
||||||
|
|
|
@ -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<String>,
|
|
||||||
/// 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<String, OpenapiSchema>
|
|
||||||
}
|
|
||||||
|
|
||||||
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<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate);
|
|
||||||
#[cfg(feature = "chrono")]
|
|
||||||
str_types!(
|
|
||||||
format = DateTime,
|
|
||||||
DateTime<FixedOffset>,
|
|
||||||
DateTime<Local>,
|
|
||||||
DateTime<Utc>,
|
|
||||||
NaiveDateTime
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(feature = "uuid")]
|
|
||||||
str_types!(format_str = "uuid", Uuid);
|
|
||||||
|
|
||||||
impl<T: OpenapiType> OpenapiType for Option<T> {
|
|
||||||
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<T: OpenapiType> OpenapiType for Vec<T> {
|
|
||||||
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<T: OpenapiType> OpenapiType for BTreeSet<T> {
|
|
||||||
fn schema() -> OpenapiSchema {
|
|
||||||
<Vec<T> as OpenapiType>::schema()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: OpenapiType, S: BuildHasher> OpenapiType for HashSet<T, S> {
|
|
||||||
fn schema() -> OpenapiSchema {
|
|
||||||
<Vec<T> as OpenapiType>::schema()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: OpenapiType, T: OpenapiType, S: BuildHasher> OpenapiType for HashMap<K, T, S> {
|
|
||||||
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 [<test_schema_ $ty:lower $($(_ $generic:lower)+)*>]()
|
|
||||||
{
|
|
||||||
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<FixedOffset> => r#"{"type":"string","format":"date"}"#);
|
|
||||||
assert_schema!(Date<Local> => r#"{"type":"string","format":"date"}"#);
|
|
||||||
assert_schema!(Date<Utc> => r#"{"type":"string","format":"date"}"#);
|
|
||||||
assert_schema!(NaiveDate => r#"{"type":"string","format":"date"}"#);
|
|
||||||
assert_schema!(DateTime<FixedOffset> => r#"{"type":"string","format":"date-time"}"#);
|
|
||||||
assert_schema!(DateTime<Local> => r#"{"type":"string","format":"date-time"}"#);
|
|
||||||
assert_schema!(DateTime<Utc> => r#"{"type":"string","format":"date-time"}"#);
|
|
||||||
assert_schema!(NaiveDateTime => r#"{"type":"string","format":"date-time"}"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_schema!(Option<String> => r#"{"nullable":true,"type":"string"}"#);
|
|
||||||
assert_schema!(Vec<String> => r#"{"type":"array","items":{"type":"string"}}"#);
|
|
||||||
assert_schema!(BTreeSet<String> => r#"{"type":"array","items":{"type":"string"}}"#);
|
|
||||||
assert_schema!(HashSet<String> => r#"{"type":"array","items":{"type":"string"}}"#);
|
|
||||||
assert_schema!(HashMap<i64, String> => r#"{"type":"object","properties":{"default":{"type":"integer","format":"int64"}},"required":["default"],"additionalProperties":{"type":"string"}}"#);
|
|
||||||
assert_schema!(Value => r#"{"nullable":true}"#);
|
|
||||||
}
|
|
|
@ -1,6 +1,3 @@
|
||||||
#[cfg(feature = "openapi")]
|
|
||||||
use crate::OpenapiSchema;
|
|
||||||
|
|
||||||
use futures_util::future::{self, BoxFuture, FutureExt};
|
use futures_util::future::{self, BoxFuture, FutureExt};
|
||||||
use gotham::{
|
use gotham::{
|
||||||
handler::HandlerError,
|
handler::HandlerError,
|
||||||
|
@ -10,6 +7,8 @@ use gotham::{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
use mime::{Mime, APPLICATION_JSON, STAR_STAR};
|
use mime::{Mime, APPLICATION_JSON, STAR_STAR};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::OpenapiSchema;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
|
@ -259,7 +258,7 @@ mod test {
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Msg {
|
struct Msg {
|
||||||
msg: String
|
msg: String
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use super::{handle_error, IntoResponse};
|
use super::{handle_error, IntoResponse};
|
||||||
use crate::{IntoResponseError, Response};
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::{OpenapiSchema, OpenapiType, ResponseSchema};
|
use crate::ResponseSchema;
|
||||||
|
use crate::{IntoResponseError, Response};
|
||||||
use futures_util::{future, future::FutureExt};
|
use futures_util::{future, future::FutureExt};
|
||||||
use gotham::hyper::header::{HeaderMap, HeaderValue, IntoHeaderName};
|
use gotham::hyper::header::{HeaderMap, HeaderValue, IntoHeaderName};
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use gotham::hyper::StatusCode;
|
use gotham::hyper::StatusCode;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::{OpenapiSchema, OpenapiType};
|
||||||
use std::{fmt::Display, future::Future, pin::Pin};
|
use std::{fmt::Display, future::Future, pin::Pin};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use super::{handle_error, IntoResponse, IntoResponseError};
|
use super::{handle_error, IntoResponse, IntoResponseError};
|
||||||
use crate::{FromBody, RequestBody, ResourceType, Response};
|
use crate::{FromBody, RequestBody, ResourceType, Response};
|
||||||
#[cfg(feature = "openapi")]
|
#[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_core::future::Future;
|
||||||
use futures_util::{future, future::FutureExt};
|
use futures_util::{future, future::FutureExt};
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use super::{handle_error, IntoResponse};
|
use super::{handle_error, IntoResponse};
|
||||||
use crate::{IntoResponseError, Response};
|
use crate::{IntoResponseError, Response};
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::{NoContent, OpenapiSchema, ResponseSchema};
|
use crate::{NoContent, ResponseSchema};
|
||||||
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},
|
||||||
Body, StatusCode
|
Body, StatusCode
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::OpenapiSchema;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
error::Error as StdError,
|
||||||
fmt::{Debug, Display}
|
fmt::{Debug, Display}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use super::{handle_error, IntoResponse, ResourceError};
|
use super::{handle_error, IntoResponse, ResourceError};
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::{OpenapiSchema, ResponseSchema};
|
use crate::ResponseSchema;
|
||||||
use crate::{Response, ResponseBody, Success};
|
use crate::{Response, ResponseBody, Success};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::OpenapiSchema;
|
||||||
|
|
||||||
use futures_core::future::Future;
|
use futures_core::future::Future;
|
||||||
use gotham::hyper::StatusCode;
|
use gotham::hyper::StatusCode;
|
||||||
|
@ -64,7 +66,7 @@ mod test {
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Msg {
|
struct Msg {
|
||||||
msg: String
|
msg: String
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::IntoResponse;
|
use super::IntoResponse;
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::{OpenapiSchema, ResponseSchema};
|
use crate::ResponseSchema;
|
||||||
use crate::{Response, ResponseBody};
|
use crate::{Response, ResponseBody};
|
||||||
use futures_util::future::{self, FutureExt};
|
use futures_util::future::{self, FutureExt};
|
||||||
use gotham::hyper::{
|
use gotham::hyper::{
|
||||||
|
@ -8,6 +8,8 @@ use gotham::hyper::{
|
||||||
StatusCode
|
StatusCode
|
||||||
};
|
};
|
||||||
use mime::{Mime, APPLICATION_JSON};
|
use mime::{Mime, APPLICATION_JSON};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::OpenapiSchema;
|
||||||
use std::{fmt::Debug, future::Future, pin::Pin};
|
use std::{fmt::Debug, future::Future, pin::Pin};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,7 +29,7 @@ Usage example:
|
||||||
# struct MyResource;
|
# struct MyResource;
|
||||||
#
|
#
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
# #[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
# #[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct MyResponse {
|
struct MyResponse {
|
||||||
message: &'static str
|
message: &'static str
|
||||||
}
|
}
|
||||||
|
@ -96,7 +98,7 @@ mod test {
|
||||||
use gotham::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
use gotham::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize)]
|
#[derive(Debug, Default, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(openapi_type::OpenapiType))]
|
||||||
struct Msg {
|
struct Msg {
|
||||||
msg: String
|
msg: String
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ use crate::openapi::{
|
||||||
router::OpenapiRouter
|
router::OpenapiRouter
|
||||||
};
|
};
|
||||||
use crate::{response::ResourceError, Endpoint, FromBody, IntoResponse, Resource, Response};
|
use crate::{response::ResourceError, Endpoint, FromBody, IntoResponse, Resource, Response};
|
||||||
|
|
||||||
#[cfg(feature = "cors")]
|
#[cfg(feature = "cors")]
|
||||||
use gotham::router::route::matcher::AccessControlRequestMethodMatcher;
|
use gotham::router::route::matcher::AccessControlRequestMethodMatcher;
|
||||||
use gotham::{
|
use gotham::{
|
||||||
|
@ -20,10 +19,12 @@ use gotham::{
|
||||||
state::{FromState, State}
|
state::{FromState, State}
|
||||||
};
|
};
|
||||||
use mime::{Mime, APPLICATION_JSON};
|
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.
|
/// 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))]
|
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||||
pub struct PathExtractor<ID: RefUnwindSafe + Send + 'static> {
|
pub struct PathExtractor<ID: RefUnwindSafe + Send + 'static> {
|
||||||
pub id: ID
|
pub id: ID
|
||||||
|
@ -91,6 +92,11 @@ where
|
||||||
{
|
{
|
||||||
trace!("entering endpoint_handler");
|
trace!("entering endpoint_handler");
|
||||||
let placeholders = E::Placeholders::take_from(state);
|
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::<E::Placeholders>() == TypeId::of::<E::Params>() {
|
||||||
|
state.put(placeholders.clone());
|
||||||
|
}
|
||||||
let params = E::Params::take_from(state);
|
let params = E::Params::take_from(state);
|
||||||
|
|
||||||
let body = match E::needs_body() {
|
let body = match E::needs_body() {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
#[cfg(feature = "openapi")]
|
|
||||||
use crate::OpenapiType;
|
|
||||||
|
|
||||||
use gotham::hyper::body::Bytes;
|
use gotham::hyper::body::Bytes;
|
||||||
use mime::{Mime, APPLICATION_JSON};
|
use mime::{Mime, APPLICATION_JSON};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::OpenapiType;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ use gotham::{
|
||||||
};
|
};
|
||||||
use gotham_restful::*;
|
use gotham_restful::*;
|
||||||
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::OpenapiType;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
|
@ -28,7 +30,7 @@ struct FooBody {
|
||||||
data: String
|
data: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct FooSearch {
|
struct FooSearch {
|
||||||
|
|
|
@ -4,6 +4,8 @@ extern crate gotham_derive;
|
||||||
use gotham::{router::builder::*, test::TestServer};
|
use gotham::{router::builder::*, test::TestServer};
|
||||||
use gotham_restful::*;
|
use gotham_restful::*;
|
||||||
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
use openapi_type::OpenapiType;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
mod util {
|
mod util {
|
||||||
|
@ -22,7 +24,7 @@ struct FooBody {
|
||||||
data: String
|
data: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct FooSearch {
|
struct FooSearch {
|
||||||
|
|
Loading…
Add table
Reference in a new issue