Repository: wafflespeanut/paperclip
Pull request #260 (2020-11-10 17:40:32)

Make api_v2_operation to respect where clause of function
Currently when there are generics in the where caluse like below:
```rust
async fn get_pet_by_type<S>(_path: web::Path<S>) -> Result<web::Json<Vec<Pet>>, ()>
where
    S: paperclip::v2::schema::Apiv2Schema + ToString,
{
    Ok(web::Json(vec![Pet::default()]))
}
```

it will compile with exception:
```shell
error[E0599]: no function or associated item named `update_parameter` found for struct `actix_web::types::path::Path<S>` in the current scope
    --> tests/test_app.rs:1559:5
     |
1559 |     #[api_v2_operation]
     |     ^^^^^^^^^^^^^^^^^^^ function or associated item not found in `actix_web::types::path::Path<S>`
     |
    ::: /Users/max/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-web-3.0.2/src/types/path.rs:64:1
     |
64   | pub struct Path<T>(pub T);
     | -------------------------- doesn't satisfy `_: paperclip_core::v2::actix::OperationModifier`
     |
     = note: the method `update_parameter` exists but the following trait bounds were not satisfied:
             `S: paperclip_core::v2::schema::Apiv2Schema`
             which is required by `actix_web::types::path::Path<S>: paperclip_core::v2::actix::OperationModifier`
     = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
```

This change fixes adding where clause to generated Apiv2Operation
implementation.

Covered with test respectively.


Patch:
diff --git a/macros/src/actix.rs b/macros/src/actix.rs
index 3783b884..382d6692 100644
--- a/macros/src/actix.rs
+++ b/macros/src/actix.rs
@@ -41,13 +41,14 @@ pub fn emit_v2_operation(attrs: TokenStream, input: TokenStream) -> TokenStream
     let s_name = format!("paperclip_{}", item_ast.sig.ident);
     let unit_struct = Ident::new(&s_name, default_span);
     let generics = &item_ast.sig.generics;
-    let (mut struct_generics, mut generics_call) = (quote!(), quote!());
+    let mut generics_call = quote!();
     let mut struct_definition = quote!(struct #unit_struct;);
-    let generics_params = extract_generics_params(&item_ast);
-    if !generics_params.is_empty() {
-        generics_call = quote!(::<#generics_params> { p: std::marker::PhantomData });
-        struct_generics = quote!(<#generics_params>);
-        struct_definition = quote!(struct #unit_struct <#generics_params> { p: std::marker::PhantomData<(#generics_params)> } )
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+    if !generics.params.is_empty() {
+        let turbofish = ty_generics.as_turbofish();
+        let generics_params = extract_generics_params(&item_ast);
+        generics_call = quote!(#turbofish { p: std::marker::PhantomData });
+        struct_definition = quote!(struct #unit_struct #ty_generics { p: std::marker::PhantomData<(#generics_params)> } )
     }
 
     // Get rid of async prefix. In the end, we'll have them all as `impl Future` thingies.
@@ -55,7 +56,7 @@ pub fn emit_v2_operation(attrs: TokenStream, input: TokenStream) -> TokenStream
         item_ast.sig.asyncness = None;
     }
 
-    let mut wrapper = quote!(paperclip::actix::ResponseWrapper<actix_web::HttpResponse, #unit_struct #struct_generics>);
+    let mut wrapper = quote!(paperclip::actix::ResponseWrapper<actix_web::HttpResponse, #unit_struct #ty_generics>);
     let mut is_impl_trait = false;
     let mut is_responder = false;
     match &mut item_ast.sig.output {
@@ -103,7 +104,7 @@ pub fn emit_v2_operation(attrs: TokenStream, input: TokenStream) -> TokenStream
                 if !is_responder {
                     // NOTE: We're only using the box "type" to generate the operation data, we're not boxing
                     // the handlers at runtime.
-                    wrapper = quote!(paperclip::actix::ResponseWrapper<Box<#obj + std::marker::Unpin>, #unit_struct #struct_generics>);
+                    wrapper = quote!(paperclip::actix::ResponseWrapper<Box<#obj + std::marker::Unpin>, #unit_struct #ty_generics>);
                 }
             }
         }
@@ -155,7 +156,7 @@ pub fn emit_v2_operation(attrs: TokenStream, input: TokenStream) -> TokenStream
 
         #item_ast
 
-        impl #generics paperclip::v2::schema::Apiv2Operation for #unit_struct #struct_generics {
+        impl #impl_generics paperclip::v2::schema::Apiv2Operation for #unit_struct #ty_generics #where_clause {
             fn operation() -> paperclip::v2::models::DefaultOperationRaw {
                 use paperclip::actix::OperationModifier;
                 let mut op = paperclip::v2::models::DefaultOperationRaw::default();
@@ -481,27 +482,32 @@ pub fn emit_v2_definition(input: TokenStream) -> TokenStream {
     let mut props_gen = quote! {};
 
     match &item_ast.data {
-        Data::Struct(ref s) => match &s.fields {
-            Fields::Named(ref f) => handle_field_struct(f, &props, &mut props_gen),
-            Fields::Unnamed(ref f) => {
-                if f.unnamed.len() == 1 {
-                    handle_unnamed_field_struct(f, &mut props_gen)
-                } else {
+        Data::Struct(ref s) => {
+            props_gen.extend(quote!(
+                schema.data_type = Some(DataType::Object);
+            ));
+            match &s.fields {
+                Fields::Named(ref f) => handle_field_struct(f, &props, &mut props_gen),
+                Fields::Unnamed(ref f) => {
+                    if f.unnamed.len() == 1 {
+                        handle_unnamed_field_struct(f, &mut props_gen)
+                    } else {
+                        emit_warning!(
+                            f.span().unwrap(),
+                            "tuple structs do not have named fields and hence will have empty schema.";
+                            help = "{}", &*EMPTY_SCHEMA_HELP;
+                        );
+                    }
+                }
+                Fields::Unit => {
                     emit_warning!(
-                        f.span().unwrap(),
-                        "tuple structs do not have named fields and hence will have empty schema.";
+                        s.struct_token.span().unwrap(),
+                        "unit structs do not have any fields and hence will have empty schema.";
                         help = "{}", &*EMPTY_SCHEMA_HELP;
                     );
                 }
             }
-            Fields::Unit => {
-                emit_warning!(
-                    s.struct_token.span().unwrap(),
-                    "unit structs do not have any fields and hence will have empty schema.";
-                    help = "{}", &*EMPTY_SCHEMA_HELP;
-                );
-            }
-        },
+        }
         Data::Enum(ref e) => handle_enum(e, &props, &mut props_gen),
         Data::Union(ref u) => emit_error!(
             u.union_token.span().unwrap(),


Test patch:
diff --git a/tests/test_app.rs b/tests/test_app.rs
index 37f3b2c2..72c680f9 100644
--- a/tests/test_app.rs
+++ b/tests/test_app.rs
@@ -168,7 +168,8 @@ fn test_simple_app() {
                           "type": "string"
                         }
                       },
-                      "required":["birthday", "class", "name"]
+                      "required":["birthday", "class", "name"],
+                      "type":"object"
                     }
                   },
                   "paths": {
@@ -488,14 +489,16 @@ fn test_params() {
                                 },
                                 "yaml": {
                                 }
-                            }
+                            },
+                            "type":"object"
                         },
                         "BadgeBodyPatch": {
                             "properties": {
                                 "json": {
                                     "description": "JSON value",
                                 }
-                            }
+                            },
+                            "type":"object"
                         }
                     },
                     "info": {
@@ -929,7 +932,8 @@ fn test_map_in_out() {
                                        "required":[
                                           "data",
                                           "id"
-                                       ]
+                                       ],
+                                       "type":"object"
                                     },
                                     "type":"array"
                                  },
@@ -938,7 +942,8 @@ fn test_map_in_out() {
                            },
                            "required":[
                               "folders"
-                           ]
+                           ],
+                           "type":"object"
                         },
                         "Filter":{
                            "properties":{
@@ -955,7 +960,8 @@ fn test_map_in_out() {
                            },
                            "required":[
                               "folders"
-                           ]
+                           ],
+                           "type":"object"
                         },
                         "Image":{
                            "properties":{
@@ -970,7 +976,8 @@ fn test_map_in_out() {
                            "required":[
                               "data",
                               "id"
-                           ]
+                           ],
+                           "type":"object"
                         }
                      },
                      "info":{
@@ -1114,7 +1121,8 @@ fn test_serde_flatten() {
                                   "data",
                                   "id",
                                   "time"
-                                ]
+                                ],
+                                 "type":"object"
                               },
                               "type": "array"
                             },
@@ -1137,7 +1145,8 @@ fn test_serde_flatten() {
                           "required": [
                             "data",
                             "paging"
-                          ]
+                          ],
+                          "type":"object"
                         }
                       },
                       "info": {
@@ -1253,7 +1262,8 @@ fn test_list_in_out() {
                           "type": "string"
                         }
                       },
-                      "required":["birthday", "class", "name"]
+                      "required":["birthday", "class", "name"],
+                      "type":"object"
                     }
                   },
                   "paths": {
@@ -1359,7 +1369,8 @@ fn test_tags() {
                             "required":[
                                 "data",
                                 "id"
-                            ]
+                            ],
+                            "type":"object"
                         }
                     },
                     "info":{
@@ -1491,7 +1502,8 @@ fn test_impl_traits() {
                           "type": "string"
                         }
                       },
-                      "required":["birthday", "class", "name"]
+                      "required":["birthday", "class", "name"],
+                      "type":"object"
                     }
                   },
                   "paths": {
@@ -1556,6 +1568,14 @@ fn test_operation_with_generics() {
         Ok(web::Json(vec![Pet::default()]))
     }
 
+    #[api_v2_operation]
+    async fn get_pet_by_type<S>(_path: web::Path<S>) -> Result<web::Json<Vec<Pet>>, ()>
+    where
+        S: paperclip::v2::schema::Apiv2Schema + ToString,
+    {
+        Ok(web::Json(vec![Pet::default()]))
+    }
+
     run_and_check_app(
         || {
             App::new()
@@ -1566,6 +1586,10 @@ fn test_operation_with_generics() {
                     web::resource("/pet/name/{name}")
                         .route(web::get().to(get_pet_by_name::<String>)),
                 )
+                .service(
+                    web::resource("/pet/type/{type}")
+                        .route(web::get().to(get_pet_by_type::<String>)),
+                )
                 .build()
         },
         |addr| {
@@ -1614,7 +1638,8 @@ fn test_operation_with_generics() {
                              "birthday",
                               "class",
                               "name"
-                           ]
+                           ],
+                           "type":"object"
                         }
                      },
                      "info":{
@@ -1668,6 +1693,29 @@ fn test_operation_with_generics() {
                                 }
                              ]
                           },
+                        },
+                        "/pet/type/{type}":{
+                           "get":{
+                              "responses":{
+                                 "200":{
+                                    "description":"OK",
+                                    "schema":{
+                                       "items":{
+                                          "$ref":"#/definitions/Pet"
+                                       },
+                                       "type":"array"
+                                    }
+                                 }
+                              },
+                              "parameters":[
+                                {
+                                   "in":"path",
+                                   "name":"type",
+                                   "required":true,
+                                   "type":"string"
+                                }
+                             ]
+                          },
                         }
                      },
                      "swagger":"2.0"
@@ -1772,7 +1820,8 @@ fn test_operations_documentation() {
                           "type": "string"
                         }
                       },
-                      "required":["birthday", "class", "name"]
+                      "required":["birthday", "class", "name"],
+                      "type":"object"
                     }
                   },
                   "paths": {
@@ -1915,7 +1964,8 @@ fn test_operations_macro_attributes() {
                                 "birthday",
                                 "class",
                                 "name"
-                            ]
+                            ],
+                            "type":"object"
                         }
                     },
                     "info": {
@@ -2222,7 +2272,8 @@ fn test_errors_app() {
                           "type": "string"
                         }
                       },
-                      "required":["birthday", "class", "name"]
+                      "required":["birthday", "class", "name"],
+                      "type":"object"
                     }
                   },
                   "paths": {
@@ -2383,7 +2434,8 @@ fn test_security_app() {
                           "type": "string"
                         }
                       },
-                      "required":["birthday", "class", "name"]
+                      "required":["birthday", "class", "name"],
+                      "type":"object"
                     }
                   },
                   "paths": {