From d2b2ef52fc89d1cf1e0c4ddccdb0704ec0d5d83f Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Fri, 30 Aug 2024 23:33:49 +0300 Subject: [PATCH] Chore refactor `OpenApi` derive macro Remove need for `#[openapi()]` attribute for `OpenApi` derive macro. Previously it was mandatory to always provide. From now onwards this will work as well. ```rust #[derive(OpenApi)] struct Api; ``` --- utoipa-gen/src/lib.rs | 12 +---- utoipa-gen/src/openapi.rs | 106 +++++++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/utoipa-gen/src/lib.rs b/utoipa-gen/src/lib.rs index 589408d2..d0af7413 100644 --- a/utoipa-gen/src/lib.rs +++ b/utoipa-gen/src/lib.rs @@ -1718,16 +1718,8 @@ pub fn openapi(input: TokenStream) -> TokenStream { let DeriveInput { attrs, ident, .. } = syn::parse_macro_input!(input); parse_openapi_attrs(&attrs) - .and_then(|openapi_attr| { - openapi_attr.ok_or( - syn::Error::new( - ident.span(), - "expected #[openapi(...)] attribute to be present when used with OpenApi derive trait") - ) - }) - .map_or_else(syn::Error::into_compile_error, |attrs| { - OpenApi(attrs, ident).into_token_stream() - }) + .map(|openapi_attr| OpenApi(openapi_attr, ident).to_token_stream()) + .map_or_else(syn::Error::into_compile_error, ToTokens::into_token_stream) .into() } diff --git a/utoipa-gen/src/openapi.rs b/utoipa-gen/src/openapi.rs index d02db30a..33b8706b 100644 --- a/utoipa-gen/src/openapi.rs +++ b/utoipa-gen/src/openapi.rs @@ -390,17 +390,12 @@ impl Parse for ServerVariable { } } -pub(crate) struct OpenApi<'o>(pub OpenApiAttr<'o>, pub Ident); +pub(crate) struct OpenApi<'o>(pub Option>, pub Ident); impl OpenApi<'_> { fn nested_tokens(&self) -> Option { - if self.0.nested.is_empty() { - None - } else { - let nest_tokens = self - .0 - .nested - .iter() + let nested = self.0.as_ref().map(|openapi| &openapi.nested)?; + let nest_tokens = nested.iter() .map(|item| { let path = &item.path; let nest_api = &item @@ -443,6 +438,9 @@ impl OpenApi<'_> { }) .collect::(); + if nest_tokens.is_empty() { + None + } else { Some(nest_tokens) } } @@ -452,43 +450,67 @@ impl ToTokens for OpenApi<'_> { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let OpenApi(attributes, ident) = self; - let info = info::impl_info(attributes.info.clone()); - - let components_builder_stream = attributes.components.to_token_stream(); + let info = info::impl_info( + attributes + .as_ref() + .and_then(|attributes| attributes.info.clone()), + ); - let components = if !components_builder_stream.is_empty() { - Some(quote! { .components(Some(#components_builder_stream)) }) - } else { - None + let components = match attributes + .as_ref() + .map(|attributes| attributes.components.to_token_stream()) + { + Some(tokens) if !tokens.is_empty() => Some(quote! { .components(Some(#tokens)) }), + _ => None, }; - let modifiers = &attributes.modifiers; - let modifiers_len = modifiers.len(); + let path_items = impl_paths(attributes.as_ref().map(|attributes| &attributes.paths)); - let path_items = impl_paths(&attributes.paths); + let securities = attributes + .as_ref() + .and_then(|openapi_attributes| openapi_attributes.security.as_ref()) + .map(|securities| { + quote! { + .security(Some(#securities)) + } + }); + let tags = attributes + .as_ref() + .and_then(|attributes| attributes.tags.as_ref()) + .map(|tags| { + quote! { + .tags(Some(#tags)) + } + }); + let external_docs = attributes + .as_ref() + .and_then(|attributes| attributes.external_docs.as_ref()) + .map(|external_docs| { + quote! { + .external_docs(Some(#external_docs)) + } + }); - let securities = attributes.security.as_ref().map(|securities| { - quote! { - .security(Some(#securities)) - } - }); - let tags = attributes.tags.as_ref().map(|tags| { - quote! { - .tags(Some(#tags)) + let servers = match attributes.as_ref().map(|attributes| &attributes.servers) { + Some(servers) if !servers.is_empty() => { + let servers = servers.iter().collect::>(); + Some(quote! { .servers(Some(#servers)) }) } - }); - let external_docs = attributes.external_docs.as_ref().map(|external_docs| { - quote! { - .external_docs(Some(#external_docs)) - } - }); - let servers = if !attributes.servers.is_empty() { - let servers = attributes.servers.iter().collect::>(); - Some(quote! { .servers(Some(#servers)) }) - } else { - None + _ => None, }; + let modifiers_tokens = attributes + .as_ref() + .map(|attributes| &attributes.modifiers) + .map(|modifiers| { + let modifiers_len = modifiers.len(); + + quote! { + let _mods: [&dyn utoipa::Modify; #modifiers_len] = [#modifiers]; + _mods.iter().for_each(|modifier| modifier.modify(&mut openapi)); + } + }); + let nested_tokens = self .nested_tokens() .map(|tokens| quote! {openapi = openapi #tokens;}); @@ -509,8 +531,7 @@ impl ToTokens for OpenApi<'_> { .build(); #nested_tokens - let _mods: [&dyn utoipa::Modify; #modifiers_len] = [#modifiers]; - _mods.iter().for_each(|modifier| modifier.modify(&mut openapi)); + #modifiers_tokens openapi } @@ -600,9 +621,10 @@ impl ToTokens for Components { } } -fn impl_paths(handler_paths: &Punctuated) -> TokenStream { +fn impl_paths(handler_paths: Option<&Punctuated>) -> TokenStream { let handlers = handler_paths - .iter() + .into_iter() + .flatten() .map(|handler| { let segments = handler.path.segments.iter().collect::>(); let handler_fn = &segments.last().unwrap().ident; @@ -653,7 +675,7 @@ fn impl_paths(handler_paths: &Punctuated) -> TokenStream { }) .collect::(); - handler_paths.iter().fold( + handler_paths.into_iter().flatten().fold( quote! { #handlers utoipa::openapi::path::PathsBuilder::new() }, |mut paths, handler| { let segments = handler.path.segments.iter().collect::>();