Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incomplete discriminator support ? #76

Open
eLvErDe opened this issue Jan 22, 2021 · 4 comments
Open

Incomplete discriminator support ? #76

eLvErDe opened this issue Jan 22, 2021 · 4 comments

Comments

@eLvErDe
Copy link
Contributor

eLvErDe commented Jan 22, 2021

Hello hh,

When using oneOf + discriminator, the OpenAPI spec is supposed to provide a hint to aiohttp-swagger3 on which schema should be chosen to validate provided payload. If so, I expect to receive a proper validation error related to the chosen models, but all I get is "failed to validate oneOf".

Here is an example script reproducing the issue: when POSTing on type1 or type2 route I get an helpful error message, when using the polymorphic route, I don't get any hint.

import tempfile
from aiohttp import web

from aiohttp_swagger3 import SwaggerDocs, SwaggerUiSettings


# Inspired from https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

swagger_spec = """
components:
  schemas:
    ObjectType1:
      type: object
      properties:
        type:
          type: string
          enum:
          - type1
        prop1:
          type: boolean
        prop2:
          type: string
      required:
      - prop1
      - prop2
      - type
      example:
        type: type1
        prop1: 4
        prop2: abc
    ObjectType2:
      type: object
      properties:
        type:
          type: string
          enum:
          - type2
        prop3:
          type: boolean
        prop4:
          type: string
      required:
      - type
      - prop3
      - prop4
      example:
        type: type2
        prop3: false
        prop5: abc
    ObjectAnyType:
      oneOf:
      - $ref: '#/components/schemas/ObjectType1'
      - $ref: '#/components/schemas/ObjectType2'
      discriminator:
        propertyName: type
        mapping:
          type1: '#/components/schemas/ObjectType1'
          type2: '#/components/schemas/ObjectType2'
      example:
        type: type2
        prop3: false
        prop5: abc
"""


async def post_type1(request):
    """
    Receive an object of type1
    ---
    summary: Receive an object of type1
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ObjectType1'
    responses:
      "400":
        description: 'Failing with {"body": {"prop1": "value should be type of bool"}}'
    """

    return web.json_response({"worked": True})


async def post_type2(request):
    """
    Receive an object of type2
    ---
    summary: Receive an object of type2
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ObjectType2'
    responses:
      "400":
        description: 'Failing with {"body": {"prop4": "required property"}}'
    """

    return web.json_response({"worked": True})


async def post_type_polymorphic(request):
    """
    Receive an object of type1 or type2
    ---
    summary: Receive an object of type1 or type2
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ObjectAnyType'
    responses:
      "400":
        description: >
          Failing with fail to validate oneOf while expecting proper message
          depending on selected models using discriminator
    """

    return web.json_response({"worked": True})


def main():
    app = web.Application()

    with tempfile.NamedTemporaryFile() as spec_fh:
        spec_fh.write(bytes(swagger_spec.strip(), "utf-8"))
        spec_fh.seek(0)
        swagger = SwaggerDocs(app, components=spec_fh.name, swagger_ui_settings=SwaggerUiSettings(path="/"))

    swagger.add_routes(
        [
            web.post("/post/type1", post_type1),
            web.post("/post/type2", post_type2),
            web.post("/post/polymorphic", post_type_polymorphic),
        ]
    )

    web.run_app(app)


if __name__ == "__main__":
    main()

I am not sure this is actually easy to solve, as if I understood correctly you are relying on fastjsonschema library but that would definitely be a major improvement for API users ;-)

Best regards, Adam.

@hh-h
Copy link
Owner

hh-h commented Jan 22, 2021

Hi, that's very hard to resolve AFAIR, probably a lot of refactoring should be made, I don't have time for that. Honestly, I don't think it is so important to put the effort into it.

@eLvErDe
Copy link
Contributor Author

eLvErDe commented Jan 23, 2021

Hello,

Sadly that's what I guessed :/
If you decide to invest time at some point, be sure I will be happy to sponsor it.
Do you have any idea how to workaround this manually ? I mean something like catching the exception in a middleware and re-doing validation by hand using matching schema ?

Regards, Adam.

@hh-h
Copy link
Owner

hh-h commented Jan 23, 2021

Unfortunately, it won't happen, like 95% :) It is not only hard to implement, but it is not obvious what error to display, for example, you have oneOf and 5 schemas, the user provides empty body - {}, it is not obvious what error should be displayed for the user.

Regarding middleware - it is not possible as well, there's no way to get the schema from available request and handler from middleware. So, there's only one way is disable validation for this route and parse it in the handler itself.

@eLvErDe
Copy link
Contributor Author

eLvErDe commented Jan 23, 2021

For the first case, if a discriminator is set you may just return that provided discriminator value must be one of the configured values.

That's exactly the path I took, I currently generating marshmallow models from the OpenAPI spec and implementing my own validation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants