1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-05-09 08:00:41 +00:00

use openapi_type::OpenapiType for gotham_restful

This commit is contained in:
Dominic 2021-03-09 19:46:11 +01:00
parent eecd192458
commit ebea39fe0d
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
27 changed files with 148 additions and 866 deletions

View file

@ -2,7 +2,7 @@
[package]
name = "gotham_restful_derive"
version = "0.2.0"
version = "0.3.0-dev"
authors = ["Dominic Meiser <git@msrd0.de>"]
edition = "2018"
description = "Derive macros for gotham_restful"

View file

@ -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!(())
}
},
}

View file

@ -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)

View file

@ -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
}
}
}
})
}

View file

@ -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()
}
)
)
)
}
}
}