netwo-io / apistos

Actix-web wrapper for automatic OpenAPI 3.0 documentation generation.
MIT License
148 stars 6 forks source link

Duplicated fields in the query string with `swagger-ui` & `qs_query` #135

Closed arngaillard closed 1 month ago

arngaillard commented 2 months ago

Dear maintainers,

I was curious about representing a Vec<_> as a querystring and how this was documented via apistos, especially in the SwaggerUI. I tried using web::Query or QsQuery (from serde_qs) but in both case, the generated swagger generates a query string as field=value1&field=value2 and the following doesn't get parsed correctly.

Using the petstore example, I only added the necessary code to allow for the swagger to be generated, and the query I obtain from it is http://localhost:8080/api/v3/test/pet/findByTags?tags=string1&tags=string2 which gives out Multiple values for one key: "tags"

If I try to pass only a singular pair as http://localhost:8080/api/v3/test/pet/findByTags?tags=string1 the error is invalid type: string "string1", expected a sequence.

The only way I find to make it work is with qs_query, by using the syntax http://localhost:8080/test/pet/findByTags?tags[]=string

I was wondering if this was something related to this crate or perhaps related to swagger / open api instead.

Note: In my own program, where I needed the swagger to be usable, I used a custom implementation of serde::Deserialize to store duplicated fields into my Vec, with actix::web::Query

Thank you for your time.

rlebran commented 1 month ago

Hi ! Sorry for the late reply.

Unfortunately qs_query is not actually supported by openapi when used for arrays.

At Netwo, we use a combination of the basic actix web query (param1=val1&param1=val2) for and qs query (param1[]=val1&param1[]=val2). With the pet store example, replacing the QsQuery with the actix web query should work. Did you gave it a try ?

arngaillard commented 1 month ago

Hey, thanks for the explanations.

The changes I made in the petstore example are as follows:

-pub(crate) async fn find_by_tags(_tags: QsQuery<QueryTag>, _key: ApiKey) -> Result<Option<Json<Pet>>, ErrorResponse> {
+pub(crate) async fn find_by_tags(
+  _tags: web::Query<QueryTag>,
+  _key: ApiKey,
+) -> Result<Option<Json<Pet>>, ErrorResponse> {
   todo!()
 }

And the query still isn't parsed properly

$ curl  -X 'GET'   'http://localhost:8080/test/pet/findByTags?tags=string'   -H 'accept: application/json'
Query deserialize error: invalid type: string "string", expected a sequence
$ curl -X 'GET'   'http://localhost:8080/test/pet/findByTags?tags[]=string'   -H 'accept: application/json'
Query deserialize error: missing field `tags`
$

So I might have misunderstood what you suggested.

rlebran commented 1 month ago

I think it's an issue with actix. We actually use actix query from actix_web_lab . Looking at the doc, I think it should resolve your issue: https://docs.rs/actix-web-lab/latest/actix_web_lab/extract/struct.Query.html#differences-from-actix_webwebquery

rlebran commented 1 month ago

After further investigations, it seems that the default actix query does not define a format to handle Vec<T> which led to the one defined in actix_web_lab.

For now I don't see a way to properly document how to pass multiple values as query param in apistos.

You could define your own query and implement both FromRequest and ApiComponent to have total control on the documentation.

arngaillard commented 1 month ago

Thanks for the investigation & suggestions. The low effort solution I used on my side what to deserialize a map such as it keeps appending to a vector, and therefore allowing field=v1&field=v2 to give me a vec!["v1", "v2"] while having the generated OpenAPI to be correct.

Thanks for your time !