Repository: juhaku/utoipa (Rust)

Issue:
IntoParams doesn't work if not in the same file as handler
I have the following handler:
```rust
#[utoipa::path(
    context_path = "/api/v1/private/workers/{worker_id}/pipe_transports",
    responses(
        (status = 200, description = "Pipe Transport created"),
    ),
    tag = "pipe_transports"
  )]
#[post("")]
#[tracing::instrument(skip(state))]
pub async fn create(
    req_path: Path<request::CreatePipeTransportRequestPath>,
    state: Data<crate::state::AppStateHandle>,
) -> Result<Json<response::CreatePipeTransportResponse>, AppError> {
    .......
}
```
with the following struct for the Path **in a different file** (`request::CreatePipeTransportRequestPath`)
```rust
#[derive(Debug, Deserialize, Serialize, IntoParams)]
pub struct CreatePipeTransportRequestPath {
    pub worker_id: WorkerId,
}
```
this result in the following error:

> the trait bound `handlers::workers::pipe_transports::request::CreatePipeTransportRequestPath: utoipa::ParameterIn` is not satisfied
the trait `utoipa::ParameterIn` is not implemented for `handlers::workers::pipe_transports::request::CreatePipeTransportRequestPath`

but, if I put `CreatePipeTransportRequestPath` in the same file as my `create` handler, I don't have any error.



Discussion:
Hi, good finding. This should be fixed with next release and actually with this PR: https://github.com/juhaku/utoipa/pull/156. The `ParameterIn` trait will  be removed completely so it won't cause any furhter issues. 

New version is now released and it should fix this issue. https://crates.io/crates/utoipa
Indeed it works now! Thank you very much 😁
It actually still doesn't work. I do not get the error yes, but nothing is generated
<img width="321" alt="CleanShot 2022-06-11 at 13 50 07@2x" src="https://user-images.githubusercontent.com/8604600/173186632-ca3d63c8-fa1d-4c71-ac29-c93f2bca14db.png">
if i put the struct in the same file then it is generated as expected
<img width="601" alt="CleanShot 2022-06-11 at 13 50 30@2x" src="https://user-images.githubusercontent.com/8604600/173186646-cb95b19a-5c48-42d0-b297-66a1134d4f2b.png">
(I'm using another struct in this screenshot)
Right, now there is a another issue hindering this from functioning. Originally it was not not desingned to support such case where the type would be in another module. I will address this in next PR, but current workaround is to declare the type in same module as the handler is defined.



Patch:
diff --git a/utoipa-gen/src/ext/actix.rs b/utoipa-gen/src/ext/actix.rs
index 31bf3c56..3d84bdda 100644
--- a/utoipa-gen/src/ext/actix.rs
+++ b/utoipa-gen/src/ext/actix.rs
@@ -7,8 +7,9 @@ use proc_macro_error::{abort, abort_call_site};
 use quote::{format_ident, quote, quote_spanned};
 use regex::{Captures, Regex};
 use syn::{
-    parse::Parse, punctuated::Punctuated, token::Comma, Attribute, DeriveInput, FnArg,
-    GenericArgument, ItemFn, LitStr, Pat, PatType, PathArguments, PathSegment, Type, TypePath,
+    parse::Parse, punctuated::Punctuated, token::Comma, Attribute, DeriveInput, ExprPath, FnArg,
+    GenericArgument, ItemFn, LitStr, Pat, PatType, PathArguments, PathSegment, Type, TypeInfer,
+    TypePath,
 };
 
 use crate::{
@@ -24,8 +25,8 @@ use super::{
 
 #[cfg_attr(feature = "debug", derive(Debug))]
 enum Arg<'a> {
-    Query(&'a Ident),
-    Path(&'a Ident),
+    Query(&'a TypePath),
+    Path(&'a TypePath),
 }
 
 impl ArgumentResolver for PathOperations {
@@ -34,13 +35,13 @@ impl ArgumentResolver for PathOperations {
         resolved_path_args: Option<Vec<ResolvedArg>>,
     ) -> Option<Vec<Argument<'_>>> {
         let (primitive_args, non_primitive_args): (Vec<Arg>, Vec<Arg>) = Self::get_fn_args(fn_args)
-            .partition(|arg| matches!(arg, Arg::Path(ty) if ComponentType(ty).is_primitive()));
+            .partition(|arg| matches!(arg, Arg::Path(ty) if ComponentType(get_last_ident(ty).unwrap()).is_primitive()));
 
         if let Some(resolved_args) = resolved_path_args {
-            let primitive_args = Self::to_value_args(resolved_args, primitive_args);
+            let primitive = Self::to_value_args(resolved_args, primitive_args);
 
             Some(
-                primitive_args
+                primitive
                     .chain(Self::to_token_stream_args(non_primitive_args))
                     .collect(),
             )
@@ -60,12 +61,14 @@ impl PathOperations {
                 Arg::Query(arg) => (arg, quote! { utoipa::openapi::path::ParameterIn::Query }),
             };
 
-            let assert_ty = format_ident!("_Assert{}", &ty);
-            Argument::TokenStream(quote_spanned! {ty.span()=>
+            let ident = get_last_ident(ty).unwrap();
+            let path = ty;
+            let assert_ty = format_ident!("_Assert{}", ident);
+            Argument::TokenStream(quote_spanned! {ident.span()=>
                 {
-                    struct #assert_ty where #ty : utoipa::IntoParams;
+                    struct #assert_ty where #path : utoipa::IntoParams;
 
-                    <#ty>::into_params(|| Some(#parameter_in))
+                    <#path>::into_params(|| Some(#parameter_in))
                 }
             })
         })
@@ -87,7 +90,7 @@ impl PathOperations {
                         ),
                     },
                     ident: match primitive_arg {
-                        Arg::Path(value) => Some(value),
+                        Arg::Path(arg_type) => get_last_ident(arg_type),
                         _ => {
                             unreachable!("Arg::Query is not reachable with primitive type")
                         }
@@ -106,12 +109,10 @@ impl PathOperations {
         }
     }
 
-    fn get_argument_types(path_segment: &PathSegment) -> impl Iterator<Item = &Ident> {
+    fn get_argument_types(path_segment: &PathSegment) -> impl Iterator<Item = &TypePath> {
         match &path_segment.arguments {
-            PathArguments::AngleBracketed(angle_bracketed) => angle_bracketed
-                .args
-                .iter()
-                .flat_map(|arg| match arg {
+            PathArguments::AngleBracketed(angle_bracketed) => {
+                angle_bracketed.args.iter().flat_map(|arg| match arg {
                     GenericArgument::Type(ty) => match ty {
                         Type::Path(path) => vec![path],
                         Type::Tuple(tuple) => tuple.elems.iter().map(Self::get_type_path).collect(),
@@ -125,7 +126,7 @@ impl PathOperations {
                         )
                     }
                 })
-                .flat_map(|type_path| type_path.path.get_ident()),
+            }
             _ => {
                 abort_call_site!("unexpected argument type, expected Path<...> with angle brakets")
             }
@@ -260,3 +261,8 @@ fn is_valid_request_type(ident: Option<&Ident>) -> bool {
     matches!(ident, Some(operation) if ["get", "post", "put", "delete", "head", "connect", "options", "trace", "patch"]
         .iter().any(|expected_operation| operation == expected_operation))
 }
+
+#[inline]
+fn get_last_ident(type_path: &TypePath) -> Option<&Ident> {
+    type_path.path.segments.last().map(|segment| &segment.ident)
+}
diff --git a/utoipa-gen/src/path.rs b/utoipa-gen/src/path.rs
index 5c02465e..d2305748 100644
--- a/utoipa-gen/src/path.rs
+++ b/utoipa-gen/src/path.rs
@@ -489,8 +489,7 @@ impl ToTokens for Operation<'_> {
         let deprecated = self
             .deprecated
             .map(Into::<Deprecated>::into)
-            .or(Some(Deprecated::False))
-            .unwrap();
+            .unwrap_or(Deprecated::False);
         tokens.extend(quote! {
            .deprecated(Some(#deprecated))
         });


Test patch:
diff --git a/tests/path_derive_actix.rs b/tests/path_derive_actix.rs
index 6edbef74..d22a8501 100644
--- a/tests/path_derive_actix.rs
+++ b/tests/path_derive_actix.rs
@@ -664,6 +664,45 @@ fn derive_into_params_with_custom_attributes() {
     }
 }
 
+#[test]
+fn derive_into_params_in_another_module() {
+    use actix_web::{get, HttpResponse, Responder};
+    use utoipa::OpenApi;
+    pub mod params {
+        use serde::Deserialize;
+        use utoipa::IntoParams;
+
+        #[derive(Deserialize, IntoParams)]
+        pub struct FooParams {
+            pub id: String,
+        }
+    }
+
+    /// Foo test
+    #[utoipa::path(
+        responses(
+            (status = 200, description = "Todo foo operation success"),
+        )
+    )]
+    #[get("/todo/foo/{id}")]
+    pub async fn foo_todos(_path: Path<params::FooParams>) -> impl Responder {
+        HttpResponse::Ok()
+    }
+
+    #[derive(OpenApi, Default)]
+    #[openapi(handlers(foo_todos))]
+    struct ApiDoc;
+
+    let doc = serde_json::to_value(ApiDoc::openapi()).unwrap();
+    let parameters = common::get_json_path(&doc, "paths./todo/foo/{id}.get.parameters");
+
+    common::assert_json_array_len(parameters, 1);
+    assert_value! {parameters=>
+        "[0].in" = r#""path""#, "Parameter in"
+        "[0].name" = r#""id""#, "Parameter name"
+    }
+}
+
 macro_rules! test_derive_path_operations {
     ( $( $name:ident, $mod:ident: $operation:ident)* ) => {
         $(