Hi,

We're working on a resource operation which might return different 
implementations of a given interface. Let's say it's an operation returning 
a Pet, which can be either a Dog, or a Cat. Additionally all my Pets have a 
common property, "noise".

I wouldn't like to go into inheritance vs composition discussion, but am 
rather searching for help on how to model this in the OpenApi schema, in 
the most correct and easiest to use way.

The four alternatives that I can think of are:

1. The resource operation returns the Pet schema which can be oneOf Dog or 
Cat. Repeat the common property on both Dog and Cat.
{
  "openapi": "3.0.1",
  "info": {
    "title": "notitle",
    "version": "1.0.0"
  },
  "paths": {
    "/pets/": {
      "get": {
        "parameters": [],
        "responses": {
          "default": {
            "description": "default response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pet"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Cat": {
        "type": "object",
        "properties": {
          "noise": {
            "type": "string"
          },
          "catProperty": {
            "type": "string"
          }
        }
      },
      "Dog": {
        "type": "object",
        "properties": {
          "noise": {
            "type": "string"
          },
          "dogProperty": {
            "type": "string"
          }
        }
      },
      "Pet": {
        "type": "object",
        "oneOf": [
          {
            "$ref": "#/components/schemas/Dog"
          },
          {
            "$ref": "#/components/schemas/Cat"
          }
        ]
      }
    }
  }
}

2. The resource operation returns oneOf Dog or Cat. This actually makes the 
Pet a hidden parent, as it's not used by any operation. I'm afraid it might 
be hard to use in strongly typed clients, as there is no common parent for 
Dog and Cat. Additionally when pasted into swagger editor, this doesn't 
model the operation result in a nice way.

{
  "openapi": "3.0.1",
  "info": {
    "title": "notitle",
    "version": "1.0.0"
  },
  "paths": {
    "/pets": {
      "get": {
        "operationId": "getPet",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "description": "ret",
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Dog"
                    },
                    {
                      "$ref": "#/components/schemas/Cat"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Cat": {
        "type": "object",
        "properties": {
          "noise": {
            "type": "string"
          },
          "catProperty": {
            "type": "string"
          }
        }
      },
      "Dog": {
        "type": "object",
        "properties": {
          "noise": {
            "type": "string"
          },
          "dogProperty": {
            "type": "string"
          }
        }
      }
    }
  }
}

3. The rest operation returns oneOf Cat/Dog as above, which are composed of 
own properties and allOf properties from the Pet model. Again, I'm afraid, 
that without polymorphism this might be hard to use in strongly typed 
clients.
{
  "openapi": "3.0.1",
  "info": {
    "title": "notitle",
    "version": "1.0.0"
  },
  "paths": {
    "/pets": {
      "get": {
        "operationId": "getPet",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "description": "ret",
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Dog"
                    },
                    {
                      "$ref": "#/components/schemas/Cat"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Cat": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/Pet"
          },
          {
            "properties": {
              "catProperty": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Dog": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/Pet"
          },
          {
            "properties": {
              "catProperty": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Pet": {
        "type": "object",
        "properties": {
          "noise": {
            "type": "string"
          }
        }
      }
    }
  }
}

4. A mix of polymorphism and aggregation. Here we have a common parent for 
Dog and Cat (the Pet model), the resource operation returns the Pet. 
Additionally we have extracted the common behaviour to PetBehaviour model 
and use allOf their properties in Cat and Dog.
{
  "openapi": "3.0.1",
  "info": {
    "title": "notitle",
    "version": "1.0.0"
  },
  "paths": {
    "/pets": {
      "get": {
        "operationId": "getPet",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pet"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PetBehaviour": {
        "type": "object",
        "properties": {
          "noise": {
            "type": "string"
          }
        }
      },
      "Cat": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/PetBehaviour"
          },
          {
            "properties": {
              "catProperty": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Dog": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/PetBehaviour"
          },
          {
            "properties": {
              "dogProperty": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Pet": {
        "type": "object",
        "oneOf": [
          {
            "$ref": "#/components/schemas/Dog"
          },
          {
            "$ref": "#/components/schemas/Cat"
          }
        ]
      }
    }
  }
}

Sorry for this long question, but I feel we're not the only ones struggling 
with modelling polymorphism and composition in the OpenApi. I didn't find a 
single example with a resource operation returning an object, which can be 
oneOf multiple implementations. It's always the response that defines oneOf.

All of the examples seems to be syntactically correct, they do properly 
validate in the swagger editor. Which one in your opinion should be 
preferred? Can you state any of them is definitely incorrect.

Best Regards,

Michal

-- 
You received this message because you are subscribed to the Google Groups 
"Swagger" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to swagger-swaggersocket+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to