8

I'm attempting to implement an API defined in Swagger or OpenAPI 3.0 using AWS API Gateway.

One endpoint in this API takes an abstract base model (lets call it Pet to be consistent with the well-worn Swagger example), but actually expects a concrete model that is derived from Pet... Dog for example.

The concrete model can be determined by a type attribute on Pet.

The concrete model can add additional fields.

Of course, this a job for a discriminator:

definitions:
    Pet:
        discriminator: petType
        required:
        - name
        - petType # required for inheritance to work
        properties:
        name: 
            type: string
        petType:
            type: string
    Cat:
        allOf:
        - $ref: '#/definitions/Pet' # Cat has all properties of a Pet
        - properties: # extra properties only for cats
            huntingSkill:
                type: string
                default: lazy
                enum:
                - lazy
                - aggressive
    Dog:
        allOf:
        - $ref: '#/definitions/Pet' # Dog has all properties of a Pet
        - properties: # extra properties only for dogs
            packSize:
                description: The size of the pack the dog is from
                type: integer

(taken from here)

However, AWS API Gateway doesn't support discriminator (ref).

OK, annoying, but maybe a workaround is to define the API with OpenAPI 3.0, and utilise oneOf in the schema:

paths:
    /pets:
        patch:
            requestBody:
                content:
                    application/json:
                        schema:
                            oneOf:
                                - $ref: '#/components/schemas/Cat'
                                - $ref: '#/components/schemas/Dog'

However (again), AWS API Gateway doesn't support oneOf either (ref).

Does anyone know how to implement a model schema of this nature with AWS API Gateway, in particular to take advantage of body validation for an inheritance pattern (Pet <- Dog)? Or indeed a workaround without having to have methods for every concrete type?

KevinD
  • 1,769
  • 10
  • 25

2 Answers2

4

I wanted to add another answer, which is solving the issue in prettier way! The key problem is that in previous attempts I used OpenAPI 3.0.0, however 3.0.1 no longer seem to have any trouble with oneOf directive.

Here's the working example:

{
  "openapi": "3.0.1",
  "info": {
    // (...)
  },
  "paths": {
    "/pets": {
      "post": {
        "summary": "Post a pet",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PetRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            // (...)
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Cat": {
        "type": "object",
        "required": [
          "cat_stuff"
        ],
        "properties": {
          "cat_stuff": {
            "type": "string"
          }
        }
      },
      "Dog": {
        "type": "object",
        "required": [
          "dog_stuff"
        ],
        "properties": {
          "dog_stuff": {
            "type": "string"
          },
        }
      },
      "PetRequest": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/Cat"
          },
          {
            "$ref": "#/components/schemas/Dog"
          }
        ]
      }
    },
    // Many fields omitted (...)
}

Curling with payload that doesn't match one of those schemas gives following error, which proves that the oneOf is working as expected!

(...)
"error_messages": [
    "[instance failed to match exactly one schema (matched 0 out of 2)]"
],

Please test this yourself and leave some feedback.

sireliah
  • 256
  • 2
  • 7
  • 1
    Basically, the solution is instead of using `oneOf` right under `content: application/json: schema:`, wrap the response as a model (create a model in the `components` section), and use the `oneOf` in your model. That works! Now the API Gateway exported yaml will show the correct oneOf. – Shawn Jun 14 '21 at 23:15
2

This may not be fully satisfactory answer to your question, but there is a way to use oneOf with API Gateway. You can use JSON schema for individual models according to AWS.

Thanks to that fact you can update your models after API Gateway has been deployed.

# Reformatted here for readability
VALUE='"{\"$schema\": \"http://json-schema.org/draft-04/schema#\",
         \"title\": \"A Pet Request\",
         \"oneOf\":
        [{ \"$ref\": \"https://apigateway.amazonaws.com/restapis/xxxxxxx/models/Cat\" },
         { \"$ref\": \"https://apigateway.amazonaws.com/restapis/xxxxxxx/models/Dog\" }]}"'


aws apigateway update-model \
--rest-api-id xxxxxxx \
--model-name 'PetRequest' \
--patch-operations "op=replace,path=/schema,value=${VALUE}"

This solution works, however might not be greatly sustainable, since you need to execute the patch-operations after each deployment.

I'll likely update this answer if I find better way.

sireliah
  • 256
  • 2
  • 7