connexion icon indicating copy to clipboard operation
connexion copied to clipboard

Query parameter objects are not 'exploded'

Open johannegger opened this issue 5 years ago • 2 comments

Description

Connexion does not respect the 'explode' feature for query parameters. As an example the query object from this stackflow answer can be used.

Expected behaviour

Connexion should parse and pass 3 query parameters in flat form. genre_id=1 author_id=1 title=foobar

Actual behaviour

The endpoint returns 400 BAD REQUEST with body:

{
    "detail": "Extra query parameter(s) author_id, genre_id, title not in spec",
    "status": 400,
    "title": null,
    "type": "about:blank"
}

Steps to reproduce

  1. Create an openapi specification with a simple endpoint and this query parameter definition:
parameters:
  - in: query
    name: values
    schema:
      type: object
      properties:
        genre_id: 
          type: integer
        author_id:
          type: integer
        title:
          type: string
    style: form
    explode: true
  1. Start the connexion server - with the default validators
  2. Issue a request to the endpoint with this query: ?genre_id=1&author_id=1&title=foobar

Additional info:

openapi version is "3.0.0"

Output of the commands:

  • python --version Python 3.6.8
  • pip show connexion | grep "^Version\:" Version: 2.2.0

johannegger avatar May 28 '19 08:05 johannegger

@johannegger Looks like I'm dealing with the same issue, with the added complexity of the oneOf property in the parameters. I'm trying to be able to deal with variable parameter combinations. I stumbled upon this StackOverflow answer that describes how to accomplish this with OA3, but I still can't seem to get it to play nice with Connexion.

The endgoal is to allow conditional query parameters, in this case one of the following combinations:

  • ?sku=
  • ?retailerId=
  • ?sku=&retailerId=
  • ?sku=&regionId=

I have the following in my yaml

paths:
  /brand:
    delete:
      tags:
      - pricing
      summary: Delete existing Product Pricing
      description: Acceptable queries:<br>`sku`+`retailerId` - Delete the pricing
        of the product for a specific retailer. Removes this product from a retailer's
        site.<br>`sku`+`regionId` - Delete the pricing of the product for a specific
        region/price list. Removes this product from the region/price list.<br>`retailer`
        - Delete all pricing for a specific retailer. Removes all products from a
        retailer's site.<br>`sku` - Delete all pricing for the product. Removes the
        product from all retailers' websites and all region/price lists.
#      operationId: deletePricing
      parameters:
        - name: filter
          in: query
          style: form
          explode: true
          required: true
          schema:
            type: object
            oneOf:
              - $ref: '#/components/schemas/SingleSku'
              - $ref: '#/components/schemas/SingleRetailer'
              - $ref: '#/components/schemas/RetailerSku'
              - $ref: '#/components/schemas/RegionSku'
            additionalProperties: false
      responses:
        204:
          description: Product pricing deleted
          content: {}
        401:
          description: Invalid token supplied
          content: {}
        500:
          description: Invalid query combination
          content: {}
components:
  schemas:
    Id:
      type: integer
      format: int64
      readOnly: true
    Sku:
      type: string
    SingleSku:
      title: Single Sku for All Retailers
      description: Deletes the provided SKU from all Retailer price lists. This will remove the product from all Retailer sites.
      type: object
      properties:
        sku:
          allOf:
            - $ref: '#/components/schemas/Sku'
      required:
        - sku
    RetailerSku:
      title: Single Sku for Retailer
      description: Deletes the provided SKU from the provided Retailer's price list. This will only remove the product from that specific Retailer's site.
      type: object
      properties:
        sku:
          allOf:
            - $ref: '#/components/schemas/Sku'
        retailerId:
          allOf:
            - $ref: '#/components/schemas/Id'
      required:
        - sku
        - retailerId
    SingleRetailer:
      title: All Pricing for Single Retailer
      description: Deletes the provided Retailer's entire price list. This will remove all products from that specific Retailer's site.
      type: object
      properties:
        retailerId:
          allOf:
            - $ref: '#/components/schemas/Id'
      required:
        - retailerId
    RegionSku:
      title: Single Sku for Region
      description: Deletes the provided SKU from the provided Regional price list. This will remove the product from all  Retailers' sites that are assigned to that Regional price list.
      type: object
      properties:
        sku:
          allOf:
            - $ref: '#/components/schemas/Sku'
        regionId:
          allOf:
            - $ref: '#/components/schemas/Id'
      required:
        - sku
        - regionId

I'm using the MethodViewResolver and it routes to the following view.

from flask_restful import Resource
from flask import request

class BrandView(Resource):
    def delete(self, filter=None):
        print(filter)
        print(request.args)
        return request.args, 200

For testing, I just tried returning the args to make sure they're populating, but I never get that far. Using the first of the four variations of allowed parameter combinations (?sku=), I tried just providing sku in the swagger ui like so:

{"sku":"a"}

Which correctly corresponds to the produced request url http://127.0.0.1:5000/brand?sku=a. But, I receive the validation error response saying the parameters don't match the spec

{
  "detail": "Extra query parameter(s) sku not in spec",
  "status": 400,
  "title": null,
  "type": "about:blank"
}

I also have strict_validation set to True.

caffeinatedMike avatar Feb 13 '20 18:02 caffeinatedMike

Thanks for the report @johannegger.

Connexion currently doesn't deserialize query objects with style: form (the default) correctly, independent of the exploded value. If you control the client, you can work around this by using style: deepObject instead. More info on the style values in the openapi docs.

Next to serialization, we should support the exploded value in the strict_validation as well.

RobbeSneyders avatar Aug 16 '21 20:08 RobbeSneyders