modelling polymorphism and/or composition in OpenApi

1,369 views
Skip to first unread message

michal...@gmail.com

unread,
May 11, 2018, 9:17:40 AM5/11/18
to Swagger

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. The common property is duplicated (present both in 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. We still duplicate the common property. My biggest concern here is that 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 or Dog as above, which are composed of own properties and "allOf" properties from the Pet model. We're not duplicating common properties here, which is nice. Still, 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" to compose Cat and Dog of own properties and the common ones. We have a parent and do not duplicate the common properties.

{
  
"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 is "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. Can you think of a better schema?

Best Regards,

Michal

michal...@gmail.com

unread,
May 17, 2018, 4:54:34 AM5/17/18
to Swagger
Hi,

I guess my previous question was a bit too long ;) Could I possibly just ask you to help me with the following two simple questions instead?

1. Is the following model with the a resource operation returning a Pet, which can be 'oneOf' Dog/Cat correct? I'm precisely asking if this is fine to wrap the 'oneOf' in an additional 'parent' type):
2. What is the preferred way of modelling a resource operation returning a polymorphic type with some common attributes (Pet [age], Dog [dogProperty] extends Pet, Cat [catProperty] extends Pet)?

Thanks!
Michał
Reply all
Reply to author
Forward
0 new messages