Sage-Bionetworks / rocc-schemas

OpenAPI specification of the Registry of Open Community Challenges
Apache License 2.0
0 stars 0 forks source link

Identify query param serialization method for arrays #133

Closed tschaffter closed 3 years ago

tschaffter commented 3 years ago

https://swagger.io/docs/specification/serialization/

Related discussion on the serialization of nested objects: https://github.com/OAI/OpenAPI-Specification/issues/1706

Problem

Here is an example filter object passed as query parameter when fetching a page of challenges:

"filter": {
  "tagIds": [
    "tag1",
    "tag2"
  ]
}

This query parameter currently uses the following serialization options:

style: deepObject
explode: true

This configuration generates the follow request URL:

The error returned by the API service is: "'tag1' is not of type 'array'...

Solutions

It seems that there is no easy solution for serializing deep objects. See https://github.com/OAI/OpenAPI-Specification/issues/1706. Ideally we want to continue to leverage the automatic generation of the API service and customize its code only if required.

The easiest solution seems to flatten the query by removing the filter level and instead specify directly a query parameter named tagIds, for example.

      - description: Array of tag IDs
        explode: true
        in: query
        name: tagIds
        required: false
        schema:
          items:
            $ref: '#/components/schemas/TagId'
          type: array
        style: form

Example of query with explode: true: http://localhost:8080/api/v1/challenges?limit=10&tagIds=tag1&tagIds=tag2 Example of query with explode: false: http://localhost:8080/api/v1/challenges?limit=10&tagIds=tag1,tag2

image

Data received by the challenge controller when using style: form and explode: false (default behavior for query parameters) or explode: true.

tag_ids ['tag1', 'tag2']

Notes

Solution 1

Keep the object ChallengeFilter for now for properties that represent a single value. The following properties that were in ChallengeFilter are not single values and so are made individual query parameters:

Other properties my follow (e.g. organizers) and potentially lead to completely dropping the ChallengeFilter object and other {schema}Filter.

Solution 2

Keep ChallengeFilter and make ChallengeFilter.tagIds a single string value. The client is responsible for concatenating multiple values using a delimiter, e.g a comma. All clients and the API service must then use the same delimiter. This issue with this approach is that the OpenAPI specification can not enforce this requirement. At best this can be capture in the description of the query parameter.

Because the resulting URLs are less standard than in Solution 1, the following pain point may be introduced for the web client: when someone bookmark or share a URL that include parameter, the ideal behavior is that UI of the filters on the page is initialized based on the values of the query parameters. Similarly to the extra code that we would add in Solution 2 to concatenate multiple values, we would need to write code that un-concatenate values of query parameters when the page load.

Conclusion

I propose to adopt Solution 1, which will be implemented in this PR.

EDIT: Since most of the parameters in ChallengeFilter are meant to take arrays, I may just drop ChallengeFilter.

Tagging @vpchung @rrchai for comments

Update

tschaffter commented 3 years ago

Note on filtering by challenge status

Current query parameter:

name: status
description: Array of challenge status used to filter the results
in: query
required: false
schema:
  type: array
  items:
    $ref: ../../../components/parameters/ChallengeStatus.yaml

The Swagger UI knows that this parameter can take an array as value but the UI component does not allow to specify more than once status.

image

TODO: Check that the API service and client work as expected with the current approach. Otherwise an alternative would be to use items: type: string.

Update

Multiple status value can be passed via the URL. Validation is also nicely performed and status=plop is reported as invalid. Thus it would be a shame to have to use items: type: string and no longer benefit from the validation. Still have to test that an array can be passed to the rocc-client-angular.

Update

Here is how to filter challenges so that challenges that have AT LEAST ONE of the tag ids specified will be returned:

tag_ids_q = Q(tagIds__in=tag_ids) \
    if tag_ids is not None and len(tag_ids) > 0 else Q()

Note: The AND behavior can be achieved with tagIds__all so that all the tags of a challenge must be included in the specified list of tags.

tschaffter commented 3 years ago

Note on text searches

MongoDB has a convenient feature to search for terms among multiple properties. I'm heading toward enable the user to specify multiple search terms as a single string, which will be passed to MongoDB to search challenges that have these terms in Challenge.name and Challenge.description.