Repository: wafflespeanut/paperclip (rust)

Issue:
array types in `Query` don't emit items
If a struct like
```rust
#[derive(Deserialize, Apiv2Schema)]
    struct BadgeParams {
        list: Vec<String>,
    }
```

The parameters that are emitted are:
```rust
"parameters": [
          {
            "in": "query",
            "name": "list",
            "required": true,
            "type": "array"
          },
        ],
```

it is missing a 
```rust
"items": { "type": "string" }
```

I believe that there needs to be a mapping from `Option<DefaultSchemaRaw>` to `Option<Items>` under `DefaultOperationRaw::parameters`:
https://github.com/wafflespeanut/paperclip/blob/bdf51fe24bf3e24c0438e1909209d13ea5c74876/core/src/v2/actix.rs#L316-L325

I'll try to make the change but I feel like I'm stabbing in the dark a bit at whether this is what needs to be done.

Pull request:
Define list items on query parameters
Fixes #239.

For paramters with sequences this change also adds the .items parameter of the ParameterObject and is generated recursively.

In #242 I also tried to correctly document `serde_qs::QsQuery`, but I now don't think it is possible to correctly document `serde_qs` sequences in openapi v2 and am closing that PR.

Patch:
diff --git a/core/src/v2/actix.rs b/core/src/v2/actix.rs
index 49de034e..108fdea2 100644
--- a/core/src/v2/actix.rs
+++ b/core/src/v2/actix.rs
@@ -2,8 +2,8 @@
 use super::schema::TypedData;
 use super::{
     models::{
-        DefaultOperationRaw, DefaultResponseRaw, DefaultSchemaRaw, Either, Parameter, ParameterIn,
-        Response, SecurityScheme,
+        DefaultOperationRaw, DefaultResponseRaw, DefaultSchemaRaw, Either, Items, Parameter,
+        ParameterIn, Response, SecurityScheme,
     },
     schema::{Apiv2Errors, Apiv2Operation, Apiv2Schema},
 };
@@ -329,11 +329,13 @@ macro_rules! impl_param_extractor ({ $ty:ty => $container:ident } => {
                 op.parameters.push(Either::Right(Parameter {
                     in_: ParameterIn::$container,
                     required: def.required.contains(&k),
-                    name: k,
                     data_type: v.data_type,
                     format: v.format,
                     enum_: v.enum_,
                     description: v.description,
+                    collection_format: None, // this defaults to csv
+                    items: v.items.as_deref().map(map_schema_to_items),
+                    name: k,
                     ..Default::default()
                 }));
             }
@@ -345,6 +347,20 @@ macro_rules! impl_param_extractor ({ $ty:ty => $container:ident } => {
     }
 });
 
+fn map_schema_to_items(schema: &DefaultSchemaRaw) -> Items {
+    Items {
+        data_type: schema.data_type,
+        format: schema.format.clone(),
+        collection_format: None, // this defaults to csv
+        enum_: schema.enum_.clone(),
+        items: schema
+            .items
+            .as_deref()
+            .map(|schema| Box::new(map_schema_to_items(schema))),
+        ..Default::default() // range fields are not emitted
+    }
+}
+
 /// `formData` can refer to the global definitions.
 #[cfg(feature = "nightly")]
 impl<T: Apiv2Schema> Apiv2Schema for Form<T> {
diff --git a/plugins/actix-web/src/lib.rs b/plugins/actix-web/src/lib.rs
index 736515d7..0605f9d6 100644
--- a/plugins/actix-web/src/lib.rs
+++ b/plugins/actix-web/src/lib.rs
@@ -169,12 +169,12 @@ where
     where
         F: actix_service::IntoServiceFactory<U>,
         U: ServiceFactory<
-                Config = (),
-                Request = ServiceRequest,
-                Response = ServiceResponse,
-                Error = Error,
-                InitError = (),
-            > + 'static,
+            Config = (),
+            Request = ServiceRequest,
+            Response = ServiceResponse,
+            Error = Error,
+            InitError = (),
+        > + 'static,
         U::InitError: Debug,
     {
         self.inner = self.inner.take().map(|a| a.default_service(f));
diff --git a/plugins/actix-web/src/web.rs b/plugins/actix-web/src/web.rs
index d7ec1c27..1a775e95 100644
--- a/plugins/actix-web/src/web.rs
+++ b/plugins/actix-web/src/web.rs
@@ -59,12 +59,12 @@ impl Resource {
 impl<T> HttpServiceFactory for Resource<actix_web::Resource<T>>
 where
     T: ServiceFactory<
-            Config = (),
-            Request = ServiceRequest,
-            Response = ServiceResponse,
-            Error = Error,
-            InitError = (),
-        > + 'static,
+        Config = (),
+        Request = ServiceRequest,
+        Response = ServiceResponse,
+        Error = Error,
+        InitError = (),
+    > + 'static,
 {
     fn register(self, config: &mut AppService) {
         self.inner.register(config)
@@ -74,12 +74,12 @@ where
 impl<T> IntoServiceFactory<T> for Resource<actix_web::Resource<T>>
 where
     T: ServiceFactory<
-            Config = (),
-            Request = ServiceRequest,
-            Response = ServiceResponse,
-            Error = Error,
-            InitError = (),
-        > + 'static,
+        Config = (),
+        Request = ServiceRequest,
+        Response = ServiceResponse,
+        Error = Error,
+        InitError = (),
+    > + 'static,
 {
     fn into_factory(self) -> T {
         self.inner.into_factory()
@@ -242,12 +242,12 @@ where
     where
         F: actix_service::IntoServiceFactory<U>,
         U: ServiceFactory<
-                Config = (),
-                Request = ServiceRequest,
-                Response = ServiceResponse,
-                Error = Error,
-                InitError = (),
-            > + 'static,
+            Config = (),
+            Request = ServiceRequest,
+            Response = ServiceResponse,
+            Error = Error,
+            InitError = (),
+        > + 'static,
         U::InitError: Debug,
     {
         self.inner = self.inner.default_service(f);
@@ -302,12 +302,12 @@ impl Scope {
 impl<T> HttpServiceFactory for Scope<actix_web::Scope<T>>
 where
     T: ServiceFactory<
-            Config = (),
-            Request = ServiceRequest,
-            Response = ServiceResponse,
-            Error = Error,
-            InitError = (),
-        > + 'static,
+        Config = (),
+        Request = ServiceRequest,
+        Response = ServiceResponse,
+        Error = Error,
+        InitError = (),
+    > + 'static,
 {
     fn register(self, config: &mut AppService) {
         if let Some(s) = self.inner {
@@ -382,12 +382,12 @@ where
     where
         F: actix_service::IntoServiceFactory<U>,
         U: ServiceFactory<
-                Config = (),
-                Request = ServiceRequest,
-                Response = ServiceResponse,
-                Error = Error,
-                InitError = (),
-            > + 'static,
+            Config = (),
+            Request = ServiceRequest,
+            Response = ServiceResponse,
+            Error = Error,
+            InitError = (),
+        > + 'static,
         U::InitError: Debug,
     {
         self.inner = self.inner.map(|s| s.default_service(f));


Test patch:
diff --git a/tests/test_app.rs b/tests/test_app.rs
index 69ddf537..6de23596 100644
--- a/tests/test_app.rs
+++ b/tests/test_app.rs
@@ -354,7 +354,7 @@ fn test_params() {
     #[derive(Deserialize, Apiv2Schema)]
     struct BadgeParams {
         res: Option<u16>,
-        color: String,
+        colors: Vec<String>,
     }
 
     #[derive(Deserialize, Apiv2Schema)]
@@ -531,9 +531,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -561,9 +564,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -591,9 +597,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -621,9 +630,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -651,9 +663,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -681,9 +696,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -717,9 +735,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -809,9 +830,12 @@ fn test_params() {
                                     },
                                     {
                                         "in": "query",
-                                        "name": "color",
+                                        "items": {
+                                            "type": "string"
+                                        },
+                                        "name": "colors",
                                         "required": true,
-                                        "type": "string"
+                                        "type": "array"
                                     },
                                     {
                                         "format": "int32",
@@ -2067,12 +2091,12 @@ fn test_multiple_method_routes() {
         F: Fn() -> App<T, B> + Clone + Send + Sync + 'static,
         B: MessageBody + 'static,
         T: ServiceFactory<
-                Config = (),
-                Request = ServiceRequest,
-                Response = ServiceResponse<B>,
-                Error = Error,
-                InitError = (),
-            > + 'static,
+            Config = (),
+            Request = ServiceRequest,
+            Response = ServiceResponse<B>,
+            Error = Error,
+            InitError = (),
+        > + 'static,
     {
         run_and_check_app(f, |addr| {
             let resp = CLIENT
@@ -2552,12 +2576,12 @@ where
     F: Fn() -> App<T, B> + Clone + Send + Sync + 'static,
     B: MessageBody + 'static,
     T: ServiceFactory<
-            Config = (),
-            Request = ServiceRequest,
-            Response = ServiceResponse<B>,
-            Error = Error,
-            InitError = (),
-        > + 'static,
+        Config = (),
+        Request = ServiceRequest,
+        Response = ServiceResponse<B>,
+        Error = Error,
+        InitError = (),
+    > + 'static,
     G: Fn(String) -> U,
 {
     let (tx, rx) = mpsc::channel();