Dorthu / openapi3

A Python3 OpenAPI 3 Spec Parser
BSD 3-Clause "New" or "Revised" License
118 stars 47 forks source link

Support for schema anyOf #101

Open yujinyuz opened 1 year ago

yujinyuz commented 1 year ago

Based on the specs here:

https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/

Given

paths:
  /pets:
    patch:
      requestBody:
        content:
          application/json:
            schema:
              anyOf:
                - $ref: '#/components/schemas/PetByAge'
                - $ref: '#/components/schemas/PetByType'
      responses:
        '200':
          description: Updated
components:
  schemas:
    PetByAge:
      type: object
      properties: 
        age: 
          type: integer
        nickname: 
          type: string
      required:
        - age

    PetByType:
      type: object
      properties:
        pet_type:
          type: string
          enum: [Cat, Dog]
        hunts:
          type: boolean
      required: 
        - pet_type

The following JSON response should be valid:

{
  "pet_type": "Cat",
  "hunts": true
}
{
  "nickname": "Fido",
  "pet_type": "Dog",
  "age": 4
}

@Dorthu would this be hard to implement? I'm trying to dive in to the code base. What do I need to do to in order to make this work?

commonism commented 1 year ago

Hi,

I already implemented this in https://github.com/Dorthu/openapi3/pull/69 - been quite a lot.

import httpx
import json

import pydantic
import pytest

from aiopenapi3 import OpenAPI

DD = """
paths:
  /pets:
    patch:
      requestBody:
        content:
          application/json:
            schema:
              anyOf:
                - $ref: '#/components/schemas/PetByAge'
                - $ref: '#/components/schemas/PetByType'
      responses:
        '200':
          description: Updated
components:
  schemas:
    PetByAge:
      type: object
      properties: 
        age: 
          type: integer
        nickname: 
          type: string
      required:
        - age

    PetByType:
      type: object
      properties:
        pet_type:
          type: string
          enum: [Cat, Dog]
        hunts:
          type: boolean
      required: 
        - pet_type

openapi: "3.1.0"
info:
  version: 1.0.0
  title: https://github.com/Dorthu/openapi3/issues/101
servers:
    - url: /       
"""

def test_anyOf(httpx_mock):
    api = OpenAPI.loads("http://localhost", DD, httpx.Client)

    # create a Request so we can use the Requests "data" property to access the body schema
    m = api.createRequest(("/pets", "patch"))
    data = json.loads("""{
      "pet_type": "Cat",
      "hunts": true
    }""")
    # use the body schema to create a pydantic model to validate the data
    data = m.data.get_type().model_validate(data)

    # same
    data = json.loads("""
    {
      "nickname": "Fido",
      "pet_type": "Dog",
      "age": 4
    }""")
    data = m.data.get_type().model_validate(data)

    # fail due to missing property
    with pytest.raises(pydantic.ValidationError):
        # missing age
        data = json.loads("""
        {
          "nickname": "Frogo",
          "pet_type": "Frog"
        }""")
        data = m.data.get_type().model_validate(data)

    data = json.loads("""
    {
      "nickname": "Frogo",
      "pet_type": "Frog",
      "age": 4,
      "wet": true
    }""")
    # works as wet is an additionalProperty
    data = m.data.get_type().model_validate(data)
    # access model via root - due to use of anyOf
    assert data.root.model_extra["wet"] is True
ghoto commented 1 month ago

69

This is a completely different project at this point. What is needed that this functionality is supported in openapi3.

commonism commented 1 month ago

7 years after the specification of OpenAPI 3.0 -the first version to feature anyOf-, I'd not wait for it to happen and reconsider the choice of tooling instead.

Want to let me know what does not work for you with aiopenapi3?