plangrid/flask-rebar

Better support for OpenAPI OneOf

mbierma opened this issue · 1 comments

OpenAPI supports the OneOf keyword to allow the combining of schemas. This could be used to allow an endpoint to validate data against one of the specified schemas -- an example from the link is copied below:

paths:
      /pets:
        patch:
          requestBody:
            content:
              application/json:
                schema:
                  oneOf:
                    - $ref: '#/components/schemas/Cat'
                    - $ref: '#/components/schemas/Dog'
          responses:
            '200':
              description: Updated
    components:
      schemas:
        Dog:
          type: object
          properties:
            bark:
              type: boolean
            breed:
              type: string
              enum: [Dingo, Husky, Retriever, Shepherd]
        Cat:
          type: object
          properties:
            hunts:
              type: boolean
            age:
              type: integer

It appears that OneOf support was added in #18, but I haven't been able to get multiple input schemas to work with Marshmallow -- although I could be missing something.

As initially mentioned in #48 (comment), it would be great if something like this were possible

Closing this for now, but posting a potential workaround by using oneofschema and a custom FieldConverter:

class FooSchema(marshmallow.Schema):
    foo = marshmallow.fields.String(required=True)

class BarSchema(marshmallow.Schema):
    bar = marshmallow.fields.Integer(required=True)

class MyUberSchema(OneOfSchema):
    type_schemas = {"foo": FooSchema, "bar": BarSchema}

    def get_obj_type(self, obj):
        if isinstance(obj, Foo):
            return "foo"
        elif isinstance(obj, Bar):
            return "bar"
        else:
            raise Exception("Unknown object type: {}".format(obj.__class__.__name__))


class TestSchema(marshmallow.Schema):
    test = marshmallow.fields.Nested(MyUberSchema())


class OneOfConverter(FieldConverter):
    MARSHMALLOW_TYPE = OneOfSchema

    def convert(self, obj, context):
        subschemas = []
        for value in obj.type_schemas.values():
            subschemas.append(context.convert(value(), context))
        return {sw.one_of: subschemas}

response_converter_registry.register_types([OneOfConverter()])
request_body_converter_registry.register_types([OneOfConverter()])