Repository: juhaku/utoipa
Pull request #44 (2022-03-16 22:52:13)

Chore improve macros
* Improve utoipa::path attribute macro
* Improve component macro
* Improve macro errors
* Fix tests and update docs

Now macros have consistent better (opinionated) usage style and is similar to already existing once in Rust ecosystem e.g serde.


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index c82266a..781e87e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "utoipa"
-description = "Compile time rest API documentation for Rust"
+description = "Compile time generated OpenAPI documentation for Rust"
 version = "0.1.0-beta7"
 edition = "2021"
 license = "MIT OR Apache-2.0"
 readme = "README.md"
-keywords = ["rest-api", "openapi", "autogenerate", "documentation", "compile-time"]
+keywords = ["rest-api", "openapi", "auto-generate", "documentation", "compile-time"]
 # documentation = ""
 # homepage = ""
 repository = "https://github.com/juhaku/utoipa"
diff --git a/README.md b/README.md
index a7864d2..52e13a1 100644
--- a/README.md
+++ b/README.md
@@ -93,14 +93,14 @@ mod pet_api {
     /// Get pet from database by pet id  
     #[utoipa::path(
         get,
-        path = "/pets/{id}"
-        responses = [
+        path = "/pets/{id}",
+        responses(
             (status = 200, description = "Pet found succesfully", body = Pet),
             (status = 404, description = "Pet was not found")
-        ],
-        params = [
+        ),
+        params(
             ("id" = u64, path, description = "Pet database id to get Pet for"),
-        ]
+        )
     )]
     async fn get_pet_by_id(pet_id: u64) -> Pet {
         Pet {
@@ -117,7 +117,7 @@ Tie the `Component` and the endpoint above to the OpenApi schema with following
 use utoipa::OpenApi;
 
 #[derive(OpenApi)]
-#[openapi(handlers = [pet_api::get_pet_by_id], components = [Pet])]
+#[openapi(handlers(pet_api::get_pet_by_id), components(Pet))]
 struct ApiDoc;
 
 println!("{}", ApiDoc::openapi().to_pretty_json().unwrap());
diff --git a/src/lib.rs b/src/lib.rs
index 39a0ef2..43a65d2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,6 +15,12 @@
 //! Long term goal of the library is to be the place to go when OpenAPI documentation is needed in Rust
 //! codebase.
 //!
+//! Utoipa is framework agnostic and could be used together with any web framework or even without one. While
+//! being portable and standalone one of it's key aspects is simple integration with web frameworks.
+//!
+//! Currently utoipa provides simple integration with actix-web framework but is not limited to the actix-web
+//! framework. All functionalities are not restricted to any specific framework.
+//!
 //! # What's up with the word play?
 //!
 //! The name comes from words `utopic` and `api` where `uto` is the first three letters of utopic
@@ -79,14 +85,14 @@
 //!     /// Get pet from database by pet id  
 //!     #[utoipa::path(
 //!         get,
-//!         path = "/pets/{id}"
-//!         responses = [
+//!         path = "/pets/{id}",
+//!         responses(
 //!             (status = 200, description = "Pet found succesfully", body = Pet),
 //!             (status = 404, description = "Pet was not found")
-//!         ],
-//!         params = [
+//!         ),
+//!         params(
 //!             ("id" = u64, path, description = "Pet database id to get Pet for"),
-//!         ]
+//!         )
 //!     )]
 //!     async fn get_pet_by_id(pet_id: u64) -> Pet {
 //!         Pet {
@@ -115,14 +121,14 @@
 //! #     /// Get pet from database by pet id  
 //! #     #[utoipa::path(
 //! #         get,
-//! #         path = "/pets/{id}"
-//! #         responses = [
+//! #         path = "/pets/{id}",
+//! #         responses(
 //! #             (status = 200, description = "Pet found succesfully", body = Pet),
 //! #             (status = 404, description = "Pet was not found")
-//! #         ],
-//! #         params = [
+//! #         ),
+//! #         params(
 //! #             ("id" = u64, path, description = "Pet database id to get Pet for"),
-//! #         ]
+//! #         )
 //! #     )]
 //! #     async fn get_pet_by_id(pet_id: u64) -> Pet {
 //! #         Pet {
@@ -289,14 +295,14 @@ pub trait Component {
 /// /// Get pet from database by pet database id  
 /// #[utoipa::path(
 ///     get,
-///     path = "/pets/{id}"
-///     responses = [
+///     path = "/pets/{id}",
+///     responses(
 ///         (status = 200, description = "Pet found succesfully", body = Pet),
 ///         (status = 404, description = "Pet was not found")
-///     ],
-///     params = [
+///     ),
+///     params(
 ///         ("id" = u64, path, description = "Pet database id to get Pet for"),
-///     ]
+///     )
 /// )]
 /// async fn get_pet_by_id(pet_id: u64) -> Pet {
 ///     Pet {
diff --git a/utoipa-gen/src/component.rs b/utoipa-gen/src/component.rs
index b1f4f98..9e76c17 100644
--- a/utoipa-gen/src/component.rs
+++ b/utoipa-gen/src/component.rs
@@ -50,7 +50,6 @@ impl ToTokens for Component<'_> {
         let variant = &self.variant;
         let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
 
-        // TODO implement sub components for complex enum variants
         tokens.extend(quote! {
             impl #impl_generics utoipa::Component for #ident #ty_generics #where_clause {
                 fn component() -> utoipa::openapi::schema::Component {
diff --git a/utoipa-gen/src/component/attr.rs b/utoipa-gen/src/component/attr.rs
index 59f3775..ebca947 100644
--- a/utoipa-gen/src/component/attr.rs
+++ b/utoipa-gen/src/component/attr.rs
@@ -68,12 +68,17 @@ pub struct NamedField {
 
 impl Parse for ComponentAttr<Enum> {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str =
+            "unexpected attribute, expected any of: default, example";
         let mut enum_attr = Enum::default();
 
-        loop {
-            let ident = input
-                .parse::<Ident>()
-                .expect_or_abort("Unparseable ComponentAttr<Enum>, expected Ident");
+        while !input.is_empty() {
+            let ident = input.parse::<Ident>().map_err(|error| {
+                Error::new(
+                    error.span(),
+                    format!("{}, {}", EXPECTED_ATTRIBUTE_MESSAGE, error),
+                )
+            })?;
             let name = &*ident.to_string();
 
             match name {
@@ -87,24 +92,12 @@ impl Parse for ComponentAttr<Enum> {
                         parse_lit_or_fn_ref_as_token_stream(input, name)
                     }))
                 }
-                _ => {
-                    return Err(Error::new(
-                        ident.span(),
-                        format!(
-                            "unexpected identifer: {}, expected any of: default, example",
-                            name
-                        ),
-                    ))
-                }
+                _ => return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
             }
 
-            if input.peek(Token![,]) {
+            if !input.is_empty() {
                 input.parse::<Token![,]>().unwrap();
             }
-
-            if input.is_empty() {
-                break;
-            }
         }
         Ok(Self { inner: enum_attr })
     }
@@ -131,12 +124,17 @@ impl ComponentAttr<Struct> {
 
 impl Parse for ComponentAttr<Struct> {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str =
+            "unexpected attribute, expected any of: example, xml";
         let mut struct_ = Struct::default();
 
         while !input.is_empty() {
-            let ident = input
-                .parse::<Ident>()
-                .expect_or_abort("Unparseable ComponentAttr<Struct>, expected Ident");
+            let ident = input.parse::<Ident>().map_err(|error| {
+                Error::new(
+                    error.span(),
+                    &format!("{}, {}", EXPECTED_ATTRIBUTE_MESSAGE, error),
+                )
+            })?;
             let name = &*ident.to_string();
 
             match name {
@@ -150,12 +148,11 @@ impl Parse for ComponentAttr<Struct> {
                     parenthesized!(xml in input);
                     struct_.xml_attr = Some(xml.parse()?)
                 }
-                _ => {
-                    return Err(Error::new(
-                        ident.span(),
-                        format!("unexpected identifer: {name}, expected one of: example, xml"),
-                    ))
-                }
+                _ => return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
+            }
+
+            if !input.is_empty() {
+                input.parse::<Token![,]>().unwrap();
             }
         }
 
@@ -165,12 +162,17 @@ impl Parse for ComponentAttr<Struct> {
 
 impl Parse for ComponentAttr<UnnamedFieldStruct> {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str =
+            "unexpected attribute, expected any of: default, example";
         let mut unnamed_struct = UnnamedFieldStruct::default();
 
-        loop {
-            let attribute = input.parse::<Ident>().expect_or_abort(
-                "Unparseable ComponentAttr<UnnamedFieldStruct>, expected identifier",
-            );
+        while !input.is_empty() {
+            let attribute = input.parse::<Ident>().map_err(|error| {
+                Error::new(
+                    error.span(),
+                    format!("{}, {}", EXPECTED_ATTRIBUTE_MESSAGE, error),
+                )
+            })?;
             let name = &*attribute.to_string();
 
             match name {
@@ -184,24 +186,12 @@ impl Parse for ComponentAttr<UnnamedFieldStruct> {
                         parse_lit_or_fn_ref_as_token_stream(input, name)
                     }))
                 }
-                _ => {
-                    return Err(Error::new(
-                        attribute.span(),
-                        format!(
-                            "unexpected identifier: {}, expected any of: default, example",
-                            name
-                        ),
-                    ))
-                }
+                _ => return Err(Error::new(attribute.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
             }
 
-            if input.peek(Token![,]) {
+            if !input.is_empty() {
                 input.parse::<Token![,]>().unwrap();
             }
-
-            if input.is_empty() {
-                break;
-            }
         }
 
         Ok(Self {
@@ -255,12 +245,16 @@ impl ComponentAttr<NamedField> {
 
 impl Parse for ComponentAttr<NamedField> {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str = "unexpected attribute, expected any of: example, format, default, write_only, read_only, xml";
         let mut field = NamedField::default();
 
-        loop {
-            let ident = input
-                .parse::<Ident>()
-                .expect_or_abort("Unparseable ComponentAttr<NamedField>, expected identifier");
+        while !input.is_empty() {
+            let ident = input.parse::<Ident>().map_err(|error| {
+                Error::new(
+                    error.span(),
+                    format!("{}, {}", EXPECTED_ATTRIBUTE_MESSAGE, error),
+                )
+            })?;
             let name = &*ident.to_string();
 
             match name {
@@ -270,11 +264,9 @@ impl Parse for ComponentAttr<NamedField> {
                     }));
                 }
                 "format" => {
-                    let format = parse_utils::parse_next(input, || {
-                        input.parse::<ExprPath>().expect_or_abort(
-                            "unparseable format expected expression path e.g. ComponentFormat::String",
-                        )
-                    });
+                    let format = parse_utils::parse_next(input, || input.parse::<ExprPath>())
+                        .map_err(|error| Error::new(error.span(), 
+                        format!("unparseable format expected expression path e.g. ComponentFormat::String, {}", error)))?;
 
                     if format.path.segments.first().unwrap().ident != "utoipa" {
                         let appended_path: ExprPath = syn::parse_quote!(utoipa::openapi::#format);
@@ -295,23 +287,12 @@ impl Parse for ComponentAttr<NamedField> {
                     parenthesized!(xml in input);
                     field.xml_attr = Some(xml.parse()?)
                 }
-                _ => {
-                    return Err(Error::new(
-                        ident.span(),
-                        format!(
-                            "unexpected identifier: {}, expected any of: example, format, default, write_only, read_only, xml",
-                            name
-                        ),
-                    ))
-                }
+                _ => return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
             }
 
-            if input.peek(Token![,]) {
+            if !input.is_empty() {
                 input.parse::<Token![,]>().unwrap();
             }
-            if input.is_empty() {
-                break;
-            }
         }
 
         Ok(Self { inner: field })
diff --git a/utoipa-gen/src/component/xml.rs b/utoipa-gen/src/component/xml.rs
index 9d4f729..f89e400 100644
--- a/utoipa-gen/src/component/xml.rs
+++ b/utoipa-gen/src/component/xml.rs
@@ -1,6 +1,6 @@
 use proc_macro2::Ident;
 use quote::{quote, ToTokens};
-use syn::{parenthesized, parse::Parse, token::Paren, LitStr, Token};
+use syn::{parenthesized, parse::Parse, token::Paren, Error, LitStr, Token};
 
 use crate::parse_utils;
 
@@ -27,38 +27,57 @@ impl XmlAttr {
 
 impl Parse for XmlAttr {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str =
+            "unexpected attribute, expected any of: name, namespace, prefix, attribute, wrapped";
         let mut xml = XmlAttr::default();
 
         while !input.is_empty() {
-            let attribute = input.parse::<Ident>()?;
+            let attribute = input
+                .parse::<Ident>()
+                .map_err(|error| Error::new(error.span(), EXPECTED_ATTRIBUTE_MESSAGE))?;
             let attribute_name = &*attribute.to_string();
 
             match attribute_name {
-                "name" => xml.name = Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value()),
-                "namespace" => xml.namespace = Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value()),
-                "prefix" => xml.prefix = Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value()),
+                "name" => {
+                    xml.name =
+                        Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value())
+                }
+                "namespace" => {
+                    xml.namespace =
+                        Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value())
+                }
+                "prefix" => {
+                    xml.prefix =
+                        Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value())
+                }
                 "attribute" => xml.is_attribute = parse_utils::parse_bool_or_true(input)?,
-                // wrapped or wrapped(name = "wrap_name")
                 "wrapped" => {
+                    // wrapped or wrapped(name = "wrap_name")
                     if input.peek(Paren) {
                         let group;
                         parenthesized!(group in input);
 
-                        let wrapped_attribute =group.parse::<Ident>()?;
+                        let wrapped_attribute = group.parse::<Ident>().map_err(|error| {
+                            Error::new(
+                                error.span(),
+                                format!("unexpected attribute, expected: name, {}", error),
+                            )
+                        })?;
                         if wrapped_attribute != "name" {
-                            return Err(syn::Error::new(wrapped_attribute.span(), "unexpected wrapped attribute, expected: name"));
+                            return Err(Error::new(
+                                wrapped_attribute.span(),
+                                "unexpected wrapped attribute, expected: name",
+                            ));
                         }
                         group.parse::<Token![=]>()?;
                         xml.wrap_name = Some(group.parse::<LitStr>()?.value());
                     }
                     xml.is_wrapped = Some(attribute);
-                },
-                _ => {
-                    return Err(syn::Error::new(attribute.span(), &format!("unexpected attribute: {attribute_name}, expected one of: name, namespace, prefix, attribute, wrapped")))
-                },
+                }
+                _ => return Err(Error::new(attribute.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
             }
 
-            if input.peek(Token![,]) {
+            if !input.is_empty() {
                 input.parse::<Token![,]>().unwrap();
             }
         }
diff --git a/utoipa-gen/src/ext/actix.rs b/utoipa-gen/src/ext/actix.rs
index 8d7bba4..b8ddf47 100644
--- a/utoipa-gen/src/ext/actix.rs
+++ b/utoipa-gen/src/ext/actix.rs
@@ -1,3 +1,5 @@
+#![cfg(feature = "actix_extras")]
+
 use lazy_static::lazy_static;
 use proc_macro2::Ident;
 use proc_macro_error::abort_call_site;
@@ -11,7 +13,6 @@ use super::{
     Argument, ArgumentIn, ArgumentResolver, PathOperationResolver, PathOperations, PathResolver,
 };
 
-#[cfg(feature = "actix_extras")]
 impl ArgumentResolver for PathOperations {
     fn resolve_path_arguments(fn_args: &Punctuated<FnArg, Comma>) -> Option<Vec<Argument<'_>>> {
         if let Some((pat_type, path_segment)) = Self::find_path_pat_type_and_segment(fn_args) {
@@ -48,7 +49,6 @@ impl ArgumentResolver for PathOperations {
     }
 }
 
-#[cfg(feature = "actix_extras")]
 impl PathOperations {
     fn get_type_path(ty: &Type) -> &TypePath {
         match ty {
@@ -140,7 +140,6 @@ impl PathOperations {
     }
 }
 
-#[cfg(feature = "actix_extras")]
 impl PathOperationResolver for PathOperations {
     fn resolve_attribute(item_fn: &ItemFn) -> Option<&Attribute> {
         item_fn.attrs.iter().find_map(|attribute| {
@@ -159,7 +158,6 @@ impl PathOperationResolver for PathOperations {
     }
 }
 
-#[cfg(feature = "actix_extras")]
 impl PathResolver for PathOperations {
     fn resolve_path(operation_attribute: &Option<&Attribute>) -> Option<String> {
         operation_attribute.map(|attribute| {
diff --git a/utoipa-gen/src/lib.rs b/utoipa-gen/src/lib.rs
index 9081780..7aa8e21 100644
--- a/utoipa-gen/src/lib.rs
+++ b/utoipa-gen/src/lib.rs
@@ -246,7 +246,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 /// **Request body supports following formats:**
 ///
 /// ```text
-/// request_body = (content = String, description = "Xml as string request", content_type = "text/xml"),
+/// request_body(content = String, description = "Xml as string request", content_type = "text/xml"),
 /// request_body = Pet,
 /// request_body = Option<[Pet]>,
 /// ```
@@ -277,7 +277,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 /// **Response with all possible values:**
 /// ```text
 /// (status = 200, description = "Success response", body = Pet, content_type = "application/json",
-///     headers = [...],
+///     headers(...),
 ///     example = json!({"id": 1, "name": "bob the cat"})
 /// )
 /// ```
@@ -345,19 +345,19 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 ///    post,
 ///    operation_id = "custom_post_pet",
 ///    path = "/pet",
-///    tag = "pet_handlers"
-///    request_body = (content = Pet, description = "Pet to store the database", content_type = "application/json")
-///    responses = [
+///    tag = "pet_handlers",
+///    request_body(content = Pet, description = "Pet to store the database", content_type = "application/json"),
+///    responses(
 ///         (status = 200, description = "Pet stored successfully", body = Pet, content_type = "application/json",
-///             headers = [
+///             headers(
 ///                 ("x-cache-len" = String, description = "Cache length")
-///             ],
+///             ),
 ///             example = json!({"id": 1, "name": "bob the cat"})
 ///         ),
-///    ],
-///    params = [
+///    ),
+///    params(
 ///      ("x-csrf-token" = String, header, deprecated, description = "Current csrf token of user"),
-///    ],
+///    ),
 ///    security(
 ///        (),
 ///        ("my_auth" = ["read:items", "edit:items"]),
@@ -382,17 +382,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 /// #[utoipa::path(
 ///    post,
 ///    path = "/pet",
-///    request_body = Pet
-///    responses = [
+///    request_body = Pet,
+///    responses(
 ///         (status = 200, description = "Pet stored successfully", body = Pet,
-///             headers = [
+///             headers(
 ///                 ("x-cache-len", description = "Cache length")
-///             ]
+///             )
 ///         ),
-///    ],
-///    params = [
+///    ),
+///    params(
 ///      ("x-csrf-token", header, description = "Current csrf token of user"),
-///    ]
+///    )
 /// )]
 /// fn post_pet(pet: Pet) -> Pet {
 ///     Pet {
@@ -409,12 +409,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 ///
 /// /// Get Pet by id
 /// #[utoipa::path(
-///     responses = [
+///     responses(
 ///         (status = 200, description = "Pet found from database")
-///     ],
-///     params = [
+///     ),
+///     params(
 ///         ("id", description = "Pet id"),
-///     ]
+///     )
 /// )]
 /// #[get("/pet/{id}")]
 /// async fn get_pet_by_id(web::Path(id): web::Path<i32>) -> impl Responder {
@@ -427,12 +427,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 /// # use actix_web::{get, web, HttpResponse, Responder};
 /// # use serde_json::json;
 /// #[utoipa::path(
-///     responses = [
+///     responses(
 ///         (status = 200, description = "Pet found from database")
-///     ],
-///     params = [
+///     ),
+///     params(
 ///         ("id", description = "Pet id"),
-///     ]
+///     )
 /// )]
 /// #[get("/pet/{id}")]
 /// #[deprecated]
@@ -730,7 +730,7 @@ impl Parse for Type {
         };
 
         let ty = if input.peek(syn::Ident) {
-            let mut ident: Ident = input.parse().unwrap();
+            let mut ident: Ident = input.parse()?;
 
             // is option of type or [type]
             if (ident == "Option" && input.peek(Token![<]))
@@ -738,10 +738,10 @@ impl Parse for Type {
             {
                 is_option = true;
 
-                input.parse::<Token![<]>().unwrap();
+                input.parse::<Token![<]>()?;
 
                 if input.peek(syn::Ident) {
-                    ident = input.parse::<Ident>().unwrap();
+                    ident = input.parse::<Ident>()?;
                 } else {
                     ident = parse_array(input)?;
                 }
@@ -769,8 +769,7 @@ struct ExternalDocs {
 
 impl Parse for ExternalDocs {
     fn parse(input: ParseStream) -> syn::Result<Self> {
-        const EXPECTED_ATTRIBUTE: &str =
-            "unexpected attribute, expected one of: url or description";
+        const EXPECTED_ATTRIBUTE: &str = "unexpected attribute, expected any of: url, description";
 
         let mut external_docs = ExternalDocs::default();
 
@@ -790,7 +789,7 @@ impl Parse for ExternalDocs {
                 _ => return Err(syn::Error::new(ident.span(), EXPECTED_ATTRIBUTE)),
             }
 
-            if input.peek(Token![,]) {
+            if !input.is_empty() {
                 input.parse::<Token![,]>().unwrap();
             }
         }
@@ -894,8 +893,14 @@ mod parse_utils {
 
     pub fn parse_json_token_stream(input: ParseStream) -> Result<TokenStream, Error> {
         if input.peek(syn::Ident) && input.peek2(Token![!]) {
-            input.parse::<Ident>().unwrap();
-            input.parse::<Token![!]>().unwrap();
+            input.parse::<Ident>().and_then(|ident| {
+                if ident != "json" {
+                    return Err(Error::new(ident.span(), "unexpected token, expected: json"));
+                }
+
+                Ok(ident)
+            })?;
+            input.parse::<Token![!]>()?;
 
             Ok(input.parse::<Group>()?.stream())
         } else {
diff --git a/utoipa-gen/src/openapi.rs b/utoipa-gen/src/openapi.rs
index 32e8825..eac8032 100644
--- a/utoipa-gen/src/openapi.rs
+++ b/utoipa-gen/src/openapi.rs
@@ -38,7 +38,7 @@ pub fn parse_openapi_attrs(attrs: &[Attribute]) -> Option<OpenApiAttr> {
 impl Parse for OpenApiAttr {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
         const EXPECTED_ATTRIBUTE: &str =
-            "unexpected attribute, expected one of: handlers, components, modifiers, security or tags";
+            "unexpected attribute, expected any of: handlers, components, modifiers, security, tags";
         let mut openapi = OpenApiAttr::default();
 
         while !input.is_empty() {
@@ -141,7 +141,7 @@ struct Tag {
 impl Parse for Tag {
     fn parse(input: ParseStream) -> syn::Result<Self> {
         const EXPECTED_ATTRIBUTE: &str =
-            "unexpected token, expected one of: name, description or external_docs";
+            "unexpected token, expected any of: name, description, external_docs";
 
         let mut tag = Tag::default();
 
diff --git a/utoipa-gen/src/openapi/info.rs b/utoipa-gen/src/openapi/info.rs
index 6aa6221..6cbbac9 100644
--- a/utoipa-gen/src/openapi/info.rs
+++ b/utoipa-gen/src/openapi/info.rs
@@ -6,14 +6,14 @@ pub(crate) fn impl_info() -> TokenStream2 {
     let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default();
     let description = std::env::var("CARGO_PKG_DESCRIPTION").unwrap_or_default();
     let authors = std::env::var("CARGO_PKG_AUTHORS").unwrap_or_default();
-    let licence = std::env::var("CARGO_PKG_LICENSE").unwrap_or_default();
+    let license = std::env::var("CARGO_PKG_LICENSE").unwrap_or_default();
 
     let contact = get_contact(&authors);
 
     quote! {
         utoipa::openapi::Info::new(#name, #version)
             .with_description(#description)
-            .with_license(utoipa::openapi::License::new(#licence))
+            .with_license(utoipa::openapi::License::new(#license))
             .with_contact(#contact)
     }
 }
@@ -38,8 +38,8 @@ fn get_contact(authors: &str) -> TokenStream2 {
     if let Some((name, email)) = get_parsed_author(authors.split(',').into_iter().next()) {
         quote! {
             utoipa::openapi::Contact::new()
-            .with_name(#name)
-            .with_email(#email)
+                .with_name(#name)
+                .with_email(#email)
         }
     } else {
         quote! {
diff --git a/utoipa-gen/src/path.rs b/utoipa-gen/src/path.rs
index 69a4fde..e79713a 100644
--- a/utoipa-gen/src/path.rs
+++ b/utoipa-gen/src/path.rs
@@ -1,15 +1,9 @@
 use std::{io::Error, str::FromStr};
 
-use proc_macro2::{Group, Ident, TokenStream as TokenStream2};
-use proc_macro_error::{abort, OptionExt, ResultExt};
+use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
+use proc_macro_error::abort;
 use quote::{format_ident, quote, ToTokens};
-use syn::{
-    bracketed, parenthesized,
-    parse::{Parse, ParseStream},
-    parse2,
-    punctuated::Punctuated,
-    Token,
-};
+use syn::{parenthesized, parse::Parse, Token};
 
 use crate::{
     component_type::ComponentType,
@@ -131,24 +125,19 @@ impl PathAttr {
 
 impl Parse for PathAttr {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str = "unexpected identifier, expected any of: operation_id, path, get, post, put, delete, options, head, patch, trace, connect, request_body, responses, params, tag, security";
         let mut path_attr = PathAttr::default();
 
-        loop {
-            let ident = input
-                .parse::<Ident>()
-                .expect_or_abort("unparseable PatAttr, expected identifer");
-            let attribute = &*ident.to_string();
-
-            let parse_groups = |input: &ParseStream| {
-                parse_utils::parse_next(input, || {
-                    let content;
-                    bracketed!(content in input);
-
-                    Punctuated::<Group, Token![,]>::parse_terminated(&content)
-                })
-            };
+        while !input.is_empty() {
+            let ident = input.parse::<Ident>().map_err(|error| {
+                syn::Error::new(
+                    error.span(),
+                    format!("{}, {}", EXPECTED_ATTRIBUTE_MESSAGE, error),
+                )
+            })?;
+            let attribute_name = &*ident.to_string();
 
-            match attribute {
+            match attribute_name {
                 "operation_id" => {
                     path_attr.operation_id = Some(parse_utils::parse_next_literal_str(input)?);
                 }
@@ -156,32 +145,18 @@ impl Parse for PathAttr {
                     path_attr.path = Some(parse_utils::parse_next_literal_str(input)?);
                 }
                 "request_body" => {
-                    if input.peek(Token![=]) {
-                        input.parse::<Token![=]>().unwrap();
-                    }
-                    path_attr.request_body =
-                        Some(input.parse::<RequestBodyAttr>().unwrap_or_abort());
+                    path_attr.request_body = Some(input.parse::<RequestBodyAttr>()?);
                 }
                 "responses" => {
-                    let groups = parse_groups(&input).expect_or_abort(
-                        "unparseable responses, expected group separated by comma (,)",
-                    );
-
-                    path_attr.responses = groups
-                        .into_iter()
-                        .map(|group| syn::parse2::<Response>(group.stream()).unwrap_or_abort())
-                        .collect::<Vec<_>>();
+                    let responses;
+                    parenthesized!(responses in input);
+                    path_attr.responses =
+                        parse_utils::parse_groups::<Response, Vec<_>>(&responses)?;
                 }
                 "params" => {
-                    let groups = parse_groups(&input).expect_or_abort(
-                        "unparseable params, expected group separated by comma (,)",
-                    );
-                    path_attr.params = Some(
-                        groups
-                            .iter()
-                            .map(|group| parse2::<Parameter>(group.stream()).unwrap_or_abort())
-                            .collect::<Vec<Parameter>>(),
-                    )
+                    let params;
+                    parenthesized!(params in input);
+                    path_attr.params = Some(parse_utils::parse_groups(&params)?);
                 }
                 "tag" => {
                     path_attr.tag = Some(parse_utils::parse_next_literal_str(input)?);
@@ -194,22 +169,18 @@ impl Parse for PathAttr {
                 _ => {
                     // any other case it is expected to be path operation
                     if let Some(path_operation) =
-                        attribute.parse::<PathOperation>().into_iter().next()
+                        attribute_name.parse::<PathOperation>().into_iter().next()
                     {
                         path_attr.path_operation = Some(path_operation)
                     } else {
-                        let erro_msg = format!("unexpected identifier: {}, expected any of: operation_id, path, get, post, put, delete, options, head, patch, trace, connect, request_body, responses, params, tag, security", attribute);
-                        return Err(syn::Error::new(ident.span(), erro_msg));
+                        return Err(syn::Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE));
                     }
                 }
             }
 
-            if input.peek(Token![,]) {
+            if !input.is_empty() {
                 input.parse::<Token![,]>().unwrap();
             }
-            if input.is_empty() {
-                break;
-            }
         }
 
         Ok(path_attr)
@@ -345,7 +316,13 @@ impl ToTokens for Path {
             .operation_id
             .as_ref()
             .or(Some(&self.fn_name))
-            .expect_or_abort("expected to find operation id but was None");
+            .unwrap_or_else(|| {
+                abort! {
+                    Span::call_site(), "operation id is not defined for path";
+                    help = r###"Try to define it in #[utoipa::path(operation_id = {})]"###, &self.fn_name;
+                    help = "Did you define the #[utoipa::path(...)] over function?"
+                }
+            });
         let tag = &*self
             .path_attr
             .tag
@@ -357,13 +334,39 @@ impl ToTokens for Path {
             .path_operation
             .as_ref()
             .or_else(|| self.path_operation.as_ref())
-            .expect_or_abort("expected to find path operation but was None");
+            .unwrap_or_else(|| {
+                #[cfg(feature = "actix_extras")]
+                let help =
+                    Some("Did you forget to define operation path attribute macro e.g #[get(...)]");
+
+                #[cfg(not(feature = "actix_extras"))]
+                let help = None::<&str>;
+
+                abort! {
+                    Span::call_site(), "path operation is not defined for path";
+                    help = "Did you forget to define it in #[utoipa::path(get,...)]";
+                    help =? help
+                }
+            });
         let path = self
             .path_attr
             .path
             .as_ref()
             .or_else(|| self.path.as_ref())
-            .expect_or_abort("expected to find path but was None");
+            .unwrap_or_else(|| {
+                #[cfg(feature = "actix_extras")]
+                let help =
+                    Some("Did you forget to define operation path attribute macro e.g #[get(...)]");
+
+                #[cfg(not(feature = "actix_extras"))]
+                let help = None::<&str>;
+
+                abort! {
+                    Span::call_site(), "path is not defined for path";
+                    help = r###"Did you forget to define it in #[utoipa::path(path = "...")]"###;
+                    help =? help
+                }
+            });
 
         let operation = Operation {
             deprecated: &self.deprecated,
@@ -429,8 +432,6 @@ impl ToTokens for Operation<'_> {
             .with_responses(#responses)
         });
         if let Some(security_requirements) = self.security {
-            // let security =
-            //     security_requirement::security_requirements_to_tokens(security_requirements);
             tokens.extend(quote! {
                 .with_securities(#security_requirements)
             })
diff --git a/utoipa-gen/src/path/parameter.rs b/utoipa-gen/src/path/parameter.rs
index 826869e..58c49d0 100644
--- a/utoipa-gen/src/path/parameter.rs
+++ b/utoipa-gen/src/path/parameter.rs
@@ -1,7 +1,6 @@
 use std::str::FromStr;
 
 use proc_macro2::{Ident, Span, TokenStream};
-use proc_macro_error::ResultExt;
 use quote::{quote, ToTokens};
 use syn::{
     parse::{Parse, ParseStream},
@@ -54,63 +53,54 @@ impl Parse for Parameter {
 
         if input.peek(LitStr) {
             // parse name
-            let name = input.parse::<LitStr>().unwrap().value();
+            let name = input.parse::<LitStr>()?.value();
             parameter.name = name;
 
             if input.peek(Token![=]) {
-                parameter.parameter_type = parse_utils::parse_next(input, || {
-                    Some(input.parse().expect_or_abort(
-                            "unparseable parameter type expected: identifier or identifier within brackets",
-                        ))
-                });
+                parameter.parameter_type = Some(parse_utils::parse_next(input, || {
+                    input.parse().map_err(|error| {
+                        Error::new(
+                            error.span(),
+                            format!("unexpected token, expected type such as String, {}", error),
+                        )
+                    })
+                })?);
             }
         } else {
             return Err(input.error("unparseable parameter name, expected literal string"));
         }
 
-        input
-            .parse::<Token![,]>()
-            .expect_or_abort("expected comma after literal string");
+        input.parse::<Token![,]>()?;
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str = "unexpected attribute, expected any of: path, query, header, cookie, deprecated, description";
 
-        loop {
-            let ident = input
-                .parse::<Ident>()
-                .expect_or_abort("unparseable Parameter, expected identifier");
+        while !input.is_empty() {
+            let ident = input.parse::<Ident>().map_err(|error| {
+                Error::new(
+                    error.span(),
+                    format!("{}, {}", EXPECTED_ATTRIBUTE_MESSAGE, error),
+                )
+            })?;
             let name = &*ident.to_string();
 
             match name {
                 "path" | "query" | "header" | "cookie" => {
-                    parameter.parameter_in = name.parse::<ParameterIn>().unwrap_or_abort();
+                    parameter.parameter_in = name
+                        .parse::<ParameterIn>()
+                        .map_err(|error| Error::new(ident.span(), error))?;
                 }
                 "deprecated" => parameter.deprecated = parse_utils::parse_bool_or_true(input)?,
                 "description" => {
-                    parameter.description = parse_utils::parse_next(input, || {
-                        Some(
-                            input
-                                .parse::<LitStr>()
-                                .expect_or_abort("unparseable description, expected literal string")
-                                .value(),
-                        )
-                    })
-                }
-                _ => {
-                    return Err(Error::new(
-                        ident.span(),
-                        format!(
-                        "unexpected identifier: {}, expected any of: path, query, header, cookie, deprecated, description",
-                        name
-                    ),
-                    ))
+                    parameter.description =
+                        Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value())
                 }
+                _ => return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
             }
 
-            if input.peek(Token![,]) {
-                input.parse::<Token![,]>().unwrap();
-            }
-            if input.is_empty() {
-                break;
+            if !input.is_empty() {
+                input.parse::<Token![,]>()?;
             }
         }
+
         Ok(parameter)
     }
 }
diff --git a/utoipa-gen/src/path/request_body.rs b/utoipa-gen/src/path/request_body.rs
index 03b55ea..c6e9ce0 100644
--- a/utoipa-gen/src/path/request_body.rs
+++ b/utoipa-gen/src/path/request_body.rs
@@ -1,12 +1,6 @@
 use proc_macro2::{Ident, TokenStream as TokenStream2};
-use proc_macro_error::ResultExt;
 use quote::{quote, ToTokens};
-use syn::{
-    parenthesized,
-    parse::Parse,
-    token::{Bracket, Paren},
-    Error, Token,
-};
+use syn::{parenthesized, parse::Parse, token::Paren, Error, Token};
 
 use crate::{parse_utils, Required, Type};
 
@@ -59,6 +53,8 @@ pub struct RequestBodyAttr {
 
 impl Parse for RequestBodyAttr {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str =
+            "unexpected attribute, expected any of: content, content_type, description";
         let lookahead = input.lookahead1();
 
         if lookahead.peek(Paren) {
@@ -66,51 +62,53 @@ impl Parse for RequestBodyAttr {
             parenthesized!(group in input);
 
             let mut request_body_attr = RequestBodyAttr::default();
-            loop {
+            while !group.is_empty() {
                 let ident = group
                     .parse::<Ident>()
-                    .expect_or_abort("unparseable RequestBodyAttr, expected identifer");
-                let name = &*ident.to_string();
+                    .map_err(|error| Error::new(error.span(), EXPECTED_ATTRIBUTE_MESSAGE))?;
+                let attribute_name = &*ident.to_string();
 
-                match name {
+                match attribute_name {
                     "content" => {
-                        request_body_attr.content = Some(parse_utils::parse_next(&group, || {
-                            group.parse::<Type>().unwrap()
-                        }));
+                        request_body_attr.content = Some(
+                            parse_utils::parse_next(&group, || group.parse()).map_err(|error| {
+                                Error::new(
+                                    error.span(),
+                                    format!(
+                                        "unexpected token, expected type such as String, {}",
+                                        error
+                                    ),
+                                )
+                            })?,
+                        );
                     }
                     "content_type" => {
-                        request_body_attr.content_type = Some(parse_utils::parse_next_literal_str(
-                            &group,
-                        )?)
+                        request_body_attr.content_type =
+                            Some(parse_utils::parse_next_literal_str(&group)?)
                     }
                     "description" => {
-                        request_body_attr.description = Some(parse_utils::parse_next_literal_str(
-                            &group,
-                        )?)
-                    }
-                    _ => {
-                        return Err(Error::new(
-                            ident.span(),
-                            format!(
-                                "unexpected identifer: {}, expected any of: content, content_type, description",
-                                &name
-                            ),
-                        ))
+                        request_body_attr.description =
+                            Some(parse_utils::parse_next_literal_str(&group)?)
                     }
+                    _ => return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
                 }
 
-                if group.peek(Token![,]) {
-                    group.parse::<Token![,]>().unwrap();
-                }
-                if group.is_empty() {
-                    break;
+                if !group.is_empty() {
+                    group.parse::<Token![,]>()?;
                 }
             }
 
             Ok(request_body_attr)
-        } else if lookahead.peek(Bracket) || lookahead.peek(syn::Ident) {
+        } else if lookahead.peek(Token![=]) {
+            input.parse::<Token![=]>()?;
+
             Ok(RequestBodyAttr {
-                content: Some(input.parse().unwrap()),
+                content: Some(input.parse().map_err(|error| {
+                    Error::new(
+                        error.span(),
+                        format!("unexpected token, expected type such as String, {}", error),
+                    )
+                })?),
                 content_type: None,
                 description: None,
             })
diff --git a/utoipa-gen/src/path/response.rs b/utoipa-gen/src/path/response.rs
index a90813e..349a981 100644
--- a/utoipa-gen/src/path/response.rs
+++ b/utoipa-gen/src/path/response.rs
@@ -1,9 +1,6 @@
-use proc_macro2::{Group, Ident, TokenStream as TokenStream2};
-use proc_macro_error::ResultExt;
+use proc_macro2::{Ident, TokenStream as TokenStream2};
 use quote::{quote, ToTokens};
-use syn::{
-    bracketed, parse::Parse, punctuated::Punctuated, token::Comma, Error, LitInt, LitStr, Token,
-};
+use syn::{parenthesized, parse::Parse, Error, LitInt, LitStr, Token};
 
 use crate::{parse_utils, Example, Type};
 
@@ -74,68 +71,61 @@ pub struct Response {
 
 impl Parse for Response {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const EXPECTED_ATTRIBUTE_MESSAGE: &str = "unexpected attribute, expected any of: status, description, body, content_type, headers";
         let mut response = Response::default();
 
-        loop {
-            let ident = input
-                .parse::<Ident>()
-                .expect_or_abort("unparseable Response expected identifier");
-            let name = &*ident.to_string();
+        while !input.is_empty() {
+            let ident = input.parse::<Ident>().map_err(|error| {
+                Error::new(
+                    error.span(),
+                    format!("{}, {}", EXPECTED_ATTRIBUTE_MESSAGE, error),
+                )
+            })?;
+            let attribute_name = &*ident.to_string();
 
-            match name {
+            match attribute_name {
                 "status" => {
-                    response.status_code = parse_utils::parse_next(input, || {
-                        input
-                            .parse::<LitInt>()
-                            .unwrap()
-                            .base10_parse()
-                            .expect_or_abort("unparseable status, expected literal integer")
-                    });
+                    response.status_code =
+                        parse_utils::parse_next(input, || input.parse::<LitInt>())?
+                            .base10_parse()?;
                 }
                 "description" => {
                     response.description = parse_utils::parse_next_literal_str(input)?;
                 }
                 "body" => {
-                    response.response_type = Some(parse_utils::parse_next(input, || {
-                        input.parse::<Type>().unwrap_or_abort()
-                    }));
+                    response.response_type = Some(
+                        parse_utils::parse_next(input, || input.parse::<Type>()).map_err(
+                            |error| {
+                                Error::new(
+                                    ident.span(),
+                                    format!(
+                                        "unexpected token, expected type such as String, {}",
+                                        error
+                                    ),
+                                )
+                            },
+                        )?,
+                    );
                 }
                 "content_type" => {
                     response.content_type = Some(parse_utils::parse_next_literal_str(input)?);
                 }
                 "headers" => {
-                    let groups = parse_utils::parse_next(input, || {
-                        let content;
-                        bracketed!(content in input);
-                        Punctuated::<Group, Comma>::parse_terminated(&content)
-                    })
-                    .expect_or_abort("unparseable headers, expected group separated by comma (,)");
+                    let headers;
+                    parenthesized!(headers in input);
 
-                    response.headers = groups
-                        .into_iter()
-                        .map(|group| syn::parse2::<Header>(group.stream()).unwrap_or_abort())
-                        .collect::<Vec<_>>();
+                    response.headers = parse_utils::parse_groups(&headers)?;
                 }
                 "example" => {
                     response.example = Some(parse_utils::parse_next_lit_str_or_json_example(
                         input, &ident,
                     ));
                 }
-                _ => {
-                    let error_msg = format!(
-                        "unexpected identifer: {}, expected any of: status, description, body, content_type, headers",
-                        &name
-                    );
-
-                    return Err(Error::new(ident.span(), error_msg));
-                }
+                _ => return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
             }
 
-            if input.peek(Token![,]) {
-                input.parse::<Token![,]>().unwrap_or_abort();
-            }
-            if input.is_empty() {
-                break;
+            if !input.is_empty() {
+                input.parse::<Token![,]>()?;
             }
         }
 
@@ -289,51 +279,45 @@ struct Header {
 impl Parse for Header {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
         let mut header = Header {
-            name: input
-                .parse::<LitStr>()
-                .expect_or_abort("unexpected attribute for Header name, expected literal string")
-                .value(),
+            name: input.parse::<LitStr>()?.value(),
             ..Default::default()
         };
 
         if input.peek(Token![=]) {
-            input.parse::<Token![=]>().unwrap_or_abort();
-
-            header.value_type = Some(
-                input
-                    .parse::<Type>()
-                    .expect_or_abort("unparseable Header type, expected identifer"),
-            );
+            input.parse::<Token![=]>()?;
+
+            header.value_type = Some(input.parse::<Type>().map_err(|error| {
+                Error::new(
+                    error.span(),
+                    format!("unexpected token, expected type such as String, {}", error),
+                )
+            })?);
         }
 
-        if input.peek(Token![,]) {
-            input.parse::<Token![,]>().unwrap_or_abort();
+        if !input.is_empty() {
+            input.parse::<Token![,]>()?;
         }
 
         if input.peek(syn::Ident) {
-            let description = input
+            input
                 .parse::<Ident>()
-                .expect_or_abort("unparseable Header description, expected identifier");
-
-            if description == "description" {
-                if input.peek(Token![=]) {
-                    input.parse::<Token![=]>().unwrap_or_abort();
-                }
-
-                let description = input
-                    .parse::<LitStr>()
-                    .expect_or_abort("unparseable description, expected literal string")
-                    .value();
-                header.description = Some(description);
-            } else {
-                return Err(Error::new(
-                    description.span(),
-                    format!(
-                        "unexpected identifer: {}, expected: description",
-                        description
-                    ),
-                ));
-            }
+                .map_err(|error| {
+                    Error::new(
+                        error.span(),
+                        format!("unexpected attribute, expected: description, {}", error),
+                    )
+                })
+                .and_then(|ident| {
+                    if ident != "description" {
+                        return Err(Error::new(
+                            ident.span(),
+                            "unexpected attribute, expected: description",
+                        ));
+                    }
+                    Ok(ident)
+                })?;
+            input.parse::<Token![=]>()?;
+            header.description = Some(input.parse::<LitStr>()?.value());
         }
 
         Ok(header)


Test patch:
diff --git a/tests/path_derive.rs b/tests/path_derive.rs
index 2888b87..680ca17 100644
--- a/tests/path_derive.rs
+++ b/tests/path_derive.rs
@@ -31,10 +31,10 @@ macro_rules! test_api_fn {
                 $operation,
                 $( operation_id = $operation_id, )*
                 path = $path,
-                responses = [
+                responses(
                     (status = 200, description = "success response")
-                ],
-                $( params = $params, )*
+                ),
+                $( params $params, )*
                 $( tag = $tag, )*
             )]
             #[allow(unused)]
@@ -90,7 +90,7 @@ test_api_fn! {
     module: derive_path_with_all_info,
     operation: post,
     path: "/foo/bar/{id}",
-    params: [("id", description = "Foo bar id")],
+    params: (("id", description = "Foo bar id")),
     operation_id: "foo_bar_id",
     tag: "custom_tag";
     /// This is test operation description
@@ -152,13 +152,13 @@ fn derive_path_with_defaults_success() {
 #[utoipa::path(
     get,
     path = "/foo/{id}",
-    responses = [
+    responses(
         (status = 200, description = "success response")
-    ],
-    params = [
+    ),
+    params(
         ("id" = u64, description = "Foo database id"),
         ("since" = Option<String>, query, description = "Datetime since foo is updated")
-    ]
+    )
 )]
 #[allow(unused)]
 async fn get_foos_by_id_since() -> String {
@@ -204,9 +204,9 @@ fn derive_path_with_security_requirements() {
     #[utoipa::path(
         get,
         path = "/items",
-        responses = [
+        responses(
             (status = 200, description = "success response")
-        ],
+        ),
         security(
             (),
             ("api_oauth" = ["read:items", "edit:items"]),
diff --git a/tests/path_derive_actix.rs b/tests/path_derive_actix.rs
index a443084..1ea9bed 100644
--- a/tests/path_derive_actix.rs
+++ b/tests/path_derive_actix.rs
@@ -14,12 +14,12 @@ mod mod_derive_path_actix {
     ///
     /// Get foo by id long description
     #[utoipa::path(
-        responses = [
+        responses(
             (status = 200, description = "success response")
-        ],
-        params = [
+        ),
+        params(
             ("id", description = "Foo id"),
-        ]
+        )
     )]
     #[get("/foo/{id}")]
     #[allow(unused)]
@@ -57,12 +57,12 @@ mod mod_derive_path_unnamed_regex_actix {
     ///
     /// Get foo by id long description
     #[utoipa::path(
-        responses = [
+        responses(
             (status = 200, description = "success"),
-        ],
-        params = [
+        ),
+        params(
             ("arg0", description = "Foo path unnamed regex tail")
-        ]
+        )
     )]
     #[get("/foo/{_:.*}")]
     #[allow(unused)]
@@ -100,12 +100,12 @@ mod mod_derive_path_named_regex_actix {
     ///
     /// Get foo by id long description
     #[utoipa::path(
-        responses = [
+        responses(
             (status = 200, description = "success response")
-        ],
-        params = [
+        ),
+        params(
             ("tail", description = "Foo path named regex tail")
-        ]
+        )
     )]
     #[get("/foo/{tail:.*}")]
     #[allow(unused)]
@@ -143,9 +143,9 @@ macro_rules! test_derive_path_operations {
             use serde_json::json;
 
             #[utoipa::path(
-                responses = [
+                responses(
                     (status = 200, description = "success response")
-                ]
+                )
             )]
             #[$operation("/foo")]
             #[allow(unused)]
diff --git a/tests/path_parameter_derive_actix.rs b/tests/path_parameter_derive_actix.rs
index 0014ea2..ade46a5 100644
--- a/tests/path_parameter_derive_actix.rs
+++ b/tests/path_parameter_derive_actix.rs
@@ -15,13 +15,13 @@ mod derive_params_multiple_actix {
     #[utoipa::path(
         get,
         path = "/foo/{id}/{digest}",
-        responses = [
+        responses(
             (status = 200, description = "success response")
-        ],
-        params = [
+        ),
+        params(
             ("id", description = "Foo id"),
             ("digest", description = "Digest of foo"),
-        ]
+        )
     )]
     #[allow(unused)]
     async fn get_foo_by_id(web::Path((id, digest)): web::Path<(i32, String)>) -> impl Responder {
@@ -68,13 +68,13 @@ mod derive_parameters_multiple_no_matching_names_actix {
     #[utoipa::path(
         get,
         path = "/foo/{id}/{digest}",
-        responses = [
+        responses(
             (status = 200, description = "success response")
-        ],
-        params = [
+        ),
+        params(
             ("id" = i32, description = "Foo id"),
             ("digest" = String, description = "Digest of foo"),
-        ]
+        )
     )]
     #[allow(unused)]
     async fn get_foo_by_id(info: web::Path<(i32, String)>) -> impl Responder {
@@ -119,9 +119,9 @@ mod derive_params_from_method_args_actix {
     #[utoipa::path(
         get,
         path = "/foo/{id}/{digest}",
-        responses = [
+        responses(
             (status = 200, description = "success response")
-        ],
+        ),
     )]
     #[allow(unused)]
     async fn get_foo_by_id(web::Path((id, digest)): web::Path<(i32, String)>) -> impl Responder {
diff --git a/tests/path_parameter_derive_test.rs b/tests/path_parameter_derive_test.rs
index fd86c52..fb07973 100644
--- a/tests/path_parameter_derive_test.rs
+++ b/tests/path_parameter_derive_test.rs
@@ -12,12 +12,12 @@ mod derive_params_all_options {
     #[utoipa::path(
         get,
         path = "/foo/{id}",
-        responses = [
+        responses(
             (status = 200, description = "success"),
-        ],
-        params = [
+        ),
+        params(
             ("id" = i32, path, deprecated, description = "Search foos by ids"),
-        ]
+        )
     )]
     #[allow(unused)]
     async fn get_foo_by_id(id: i32) -> i32 {
@@ -53,12 +53,12 @@ mod derive_params_minimal {
     #[utoipa::path(
         get,
         path = "/foo/{id}",
-        responses = [
+        responses(
             (status = 200, description = "success"),
-        ],
-        params = [
+        ),
+        params(
             ("id" = i32, description = "Search foos by ids"),
-        ]
+        )
     )]
     #[allow(unused)]
     async fn get_foo_by_id(id: i32) -> i32 {
@@ -94,13 +94,13 @@ mod derive_params_multiple {
     #[utoipa::path(
         get,
         path = "/foo/{id}/{digest}",
-        responses = [
+        responses(
             (status = 200, description = "success"),
-        ],
-        params = [
+        ),
+        params(
             ("id" = i32, description = "Foo id"),
             ("digest" = String, description = "Digest of foo"),
-        ]
+        )
     )]
     #[allow(unused)]
     async fn get_foo_by_id(id: i32, digest: String) -> String {
@@ -144,16 +144,16 @@ mod mod_derive_parameters_all_types {
     #[utoipa::path(
         get,
         path = "/foo/{id}",
-        responses = [
+        responses(
             (status = 200, description = "success"),
-        ],
-        params = [
+        ),
+        params(
             ("id" = i32, path, description = "Foo id"),
             ("since" = String, deprecated, query, description = "Datetime since"),
             ("numbers" = Option<[u64]>, query, description = "Foo numbers list"),
             ("token" = String, header, deprecated, description = "Token of foo"),
             ("cookieval" = String, cookie, deprecated, description = "Foo cookie"),
-        ]
+        )
     )]
     #[allow(unused)]
     async fn get_foo_by_id(id: i32) -> i32 {
@@ -220,12 +220,12 @@ mod derive_params_without_args {
     #[utoipa::path(
         get,
         path = "/foo/{id}",
-        responses = [
+        responses(
             (status = 200, description = "success"),
-        ],
-        params = [
+        ),
+        params(
             ("id" = i32, path, description = "Foo id"),
-        ]
+        )
     )]
     #[allow(unused)]
     async fn get_foo_by_id() -> String {
diff --git a/tests/path_response_derive_test.rs b/tests/path_response_derive_test.rs
index c0cfd29..e9e0359 100644
--- a/tests/path_response_derive_test.rs
+++ b/tests/path_response_derive_test.rs
@@ -5,7 +5,7 @@ macro_rules! test_fn {
     ( module: $name:ident, responses: $($responses:tt)* ) => {
         #[allow(unused)]
         mod $name {
-            #[utoipa::path(get,path = "/foo",responses = $($responses)*)]
+            #[utoipa::path(get,path = "/foo",responses $($responses)*)]
             fn get_foo() {}
         }
     };
@@ -25,9 +25,9 @@ macro_rules! api_doc {
 
 test_fn! {
     module: simple_success_response,
-    responses: [
+    responses: (
         (status = 200, description = "success")
-    ]
+    )
 }
 
 #[test]
@@ -43,12 +43,12 @@ fn derive_path_with_simple_success_response() {
 
 test_fn! {
     module: multiple_simple_responses,
-    responses: [
+    responses: (
         (status = 200, description = "success"),
         (status = 401, description = "unauthorized"),
         (status = 404, description = "not found"),
         (status = 500, description = "server error")
-    ]
+    )
 }
 
 #[test]
@@ -78,13 +78,13 @@ macro_rules! test_response_types {
             paste::paste! {
                 test_fn! {
                     module: [<mod_ $name>],
-                    responses: [
+                    responses: (
                         (status = 200, description = "success",
                             $(body = $expected ,)*
                             $(content_type = $content_type,)*
-                            $(headers = $headers, )*
+                            $(headers $headers, )*
                         ),
-                    ]
+                    )
                 }
             }
 
@@ -143,32 +143,32 @@ object_body_override_content_type_to_xml => body: Foo, "text/xml", assert:
     "responses.200.content.application/json.schema.$ref" = r###"null"###, "Response content type"
     "responses.200.content.text/xml.schema.$ref" = r###""#/components/schemas/Foo""###, "Response content type"
     "responses.200.headers" = r###"null"###, "Response headers"
-object_body_with_simple_header => body: Foo, headers: [
+object_body_with_simple_header => body: Foo, headers: (
     ("xsrf-token")
-], assert:
+), assert:
     "responses.200.content.application/json.schema.$ref" = r###""#/components/schemas/Foo""###, "Response content type"
     "responses.200.headers.xsrf-token.schema.type" = r###""string""###, "xsrf-token header type"
     "responses.200.headers.xsrf-token.description" = r###"null"###, "xsrf-token header description"
-object_body_with_multiple_headers => body: Foo, headers: [
+object_body_with_multiple_headers => body: Foo, headers: (
     ("xsrf-token"),
     ("another-header")
-], assert:
+), assert:
     "responses.200.content.application/json.schema.$ref" = r###""#/components/schemas/Foo""###, "Response content type"
     "responses.200.headers.xsrf-token.schema.type" = r###""string""###, "xsrf-token header type"
     "responses.200.headers.xsrf-token.description" = r###"null"###, "xsrf-token header description"
     "responses.200.headers.another-header.schema.type" = r###""string""###, "another-header header type"
     "responses.200.headers.another-header.description" = r###"null"###, "another-header header description"
-object_body_with_header_with_type => body: Foo, headers: [
+object_body_with_header_with_type => body: Foo, headers: (
     ("random-digits" = [u64]),
-], assert:
+), assert:
     "responses.200.content.application/json.schema.$ref" = r###""#/components/schemas/Foo""###, "Response content type"
     "responses.200.headers.random-digits.schema.type" = r###""array""###, "random-digits header type"
     "responses.200.headers.random-digits.description" = r###"null"###, "random-digits header description"
     "responses.200.headers.random-digits.schema.items.type" = r###""integer""###, "random-digits header items type"
     "responses.200.headers.random-digits.schema.items.format" = r###""int64""###, "random-digits header items format"
-response_no_body_with_complex_header_with_description => headers: [
+response_no_body_with_complex_header_with_description => headers: (
     ("random-digits" = [u64], description = "Random digits response header"),
-], assert:
+), assert:
     "responses.200.content" = r###"null"###, "Response content type"
     "responses.200.headers.random-digits.description" = r###""Random digits response header""###, "random-digits header description"
     "responses.200.headers.random-digits.schema.type" = r###""array""###, "random-digits header type"
@@ -178,9 +178,9 @@ response_no_body_with_complex_header_with_description => headers: [
 
 test_fn! {
     module: response_with_json_example,
-    responses: [
+    responses: (
         (status = 200, description = "success", body = Foo, example = json!({"foo": "bar"}))
-    ]
+    )
 }
 
 #[test]
diff --git a/tests/path_response_derive_test_no_serde_json.rs b/tests/path_response_derive_test_no_serde_json.rs
index 91fc57e..562e23e 100644
--- a/tests/path_response_derive_test_no_serde_json.rs
+++ b/tests/path_response_derive_test_no_serde_json.rs
@@ -4,7 +4,7 @@ macro_rules! test_fn {
     ( module: $name:ident, responses: $($responses:tt)* ) => {
         #[allow(unused)]
         mod $name {
-            #[utoipa::path(get,path = "/foo",responses = $($responses)*)]
+            #[utoipa::path(get,path = "/foo",responses $($responses)*)]
             fn get_foo() {}
         }
     }
@@ -12,9 +12,9 @@ macro_rules! test_fn {
 
 test_fn! {
     module: response_with_string_example,
-    responses: [
+    responses: (
         (status = 200, description = "success", body = Foo, example = r#"{"foo": "bar"}"#)
-    ]
+    )
 }
 
 #[test]
diff --git a/tests/request_body_derive_test.rs b/tests/request_body_derive_test.rs
index cde2c24..eb6e76f 100644
--- a/tests/request_body_derive_test.rs
+++ b/tests/request_body_derive_test.rs
@@ -14,10 +14,10 @@ macro_rules! test_fn {
             #[utoipa::path(
                                                 post,
                                                 path = "/foo",
-                                                request_body = $($body)*,
-                                                responses = [
+                                                request_body $($body)*,
+                                                responses(
                                                     (status = 200, description = "success response")
-                                                ]
+                                                )
                                             )]
             fn post_foo() {}
         }
@@ -26,7 +26,7 @@ macro_rules! test_fn {
 
 test_fn! {
     module: derive_request_body_simple,
-    body: Foo
+    body: = Foo
 }
 
 #[test]
@@ -47,7 +47,7 @@ fn derive_path_request_body_simple_success() {
 
 test_fn! {
     module: derive_request_body_simple_array,
-    body: [Foo]
+    body: = [Foo]
 }
 
 #[test]
@@ -70,7 +70,7 @@ fn derive_path_request_body_simple_array_success() {
 
 test_fn! {
     module: derive_request_body_option_array,
-    body: Option<[Foo]>
+    body: = Option<[Foo]>
 }
 
 #[test]
@@ -92,7 +92,7 @@ fn derive_request_body_option_array_success() {
 }
 test_fn! {
     module: derive_request_body_primitive_simple,
-    body: String
+    body: = String
 }
 
 #[test]
@@ -115,7 +115,7 @@ fn derive_request_body_primitive_simple_success() {
 
 test_fn! {
     module: derive_request_body_primitive_simple_array,
-    body: [u64]
+    body: = [u64]
 }
 
 #[test]
diff --git a/tests/utoipa_gen_test.rs b/tests/utoipa_gen_test.rs
index 6e0eb06..d7411dc 100644
--- a/tests/utoipa_gen_test.rs
+++ b/tests/utoipa_gen_test.rs
@@ -51,14 +51,14 @@ mod pet_api {
     /// Get pet from database by pet database id  
     #[utoipa::path(
         get,
-        path = "/pets/{id}"
-        responses = [
+        path = "/pets/{id}",
+        responses(
             (status = 200, description = "Pet found succesfully", body = Pet),
             (status = 404, description = "Pet was not found")
-        ],
-        params = [
+        ),
+        params(
             ("id" = u64, path, description = "Pet database id to get Pet for"),
-        ]
+        ),
         security(
             (),
             ("my_auth" = ["read:items", "edit:items"]),