OpenAPI-Specification icon indicating copy to clipboard operation
OpenAPI-Specification copied to clipboard

Common response/payload object but different data defined per API?

Open atdiff opened this issue 9 years ago • 4 comments
trafficstars

What is the best way to represent a generic response/payload object that has basic fields such as message, total elements, data, and status? Where the variability between each API is the data element. For instance, one API could be for permissions, so the data element would hold an array of permissions. But for another API, it would hold a different array of object types. My main goal is to have reuse with a payload object and to define the next "layer" of actual data.

Here are some JSON samples of what's been attempted but isn't rendering the way we would expect it to in Swagger UI (i.e. a flat structure of 4 elements with data defined per the API).

Example 1 - composition:

{
    "swagger": "2.0",
    "host": "localhost:8888",
    "info": {
        "version": "0.0.1",
        "title": "API"
    },
    "paths": {
        "/permissions": {
            "get": {
                "description": "Returns all permissions",
                "operationId": "getPermissions",
                "produces": [
                    "application/json"
                ],
                "responses": {
                    "200": {
                        "description": "success",
                        "schema": {
                            "$ref": "#/definitions/permissionResponse"
                    }
                }
            }
        }
        }
    },
    "definitions": {
       "response": {
           "type": "object",
           "properties": {
              "message": {
                "type": "string",
                "description": "A string indicating the response from the server."
              },
              "totalElements": {
                "type": "integer",
                "format": "int64",
                "description": "The number of items retrieved."
              },
              "status": {
                "type": "string",
                "description": "A string indicating the response from the server."
              }
            }
        },
       "permissionResponse": {
         "allOf": [
                   {
          "$ref": "#/definitions/response"
        },{
           "type": "object",
           "properties": {
              "data": {
                "type": "array",
                "description": "The collection of items returned from the API request.",
                "items": {
                  "$ref": "#/definitions/permission"
                }
              }
            }
        }
            ]
        },
       "permission": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int64",
                    "description": "Unique identifier representing a specific permission."
                },
                "label": {
                    "type": "string",
                    "description": "The label of a permission."
                },
                "description": {
                    "type": "string",
                    "description": "A description of the permission."
                },
                "active": {
                    "type": "boolean",
                    "description": "A flag indicating whether a permission is active."
                }
            },
        "required": [
          "id",
          "label",
          "description",
          "active"
        ]
        }
    }
}

Example 2 - composition variation:

{
    "swagger": "2.0",
    "host": "localhost:8888",
    "info": {
        "version": "0.0.1",
        "title": "API"
    },
    "paths": {
        "/permissions": {
            "get": {
                "description": "Returns all permissions",
                "operationId": "getPermissions",
                "produces": [
                    "application/json"
                ],
                "responses": {
                    "200": {
                        "description": "success",
                        "schema": {
                            "$ref": "#/definitions/permissionResponse"
                    }
                }
            }
        }
        }
    },
    "definitions": {
       "response": {
           "type": "object",
           "properties": {
              "message": {
                "type": "string",
                "description": "A string indicating the response from the server."
              },
              "totalElements": {
                "type": "integer",
                "format": "int64",
                "description": "The number of items retrieved."
              },
              "status": {
                "type": "string",
                "description": "A string indicating the response from the server."
              }
            }
        },
       "permissionResponse": {
          "allOf":[
            {
              "$ref": "#/definitions/response" }        ],
           "type": "object",
           "properties": {
              "data": {
                "type": "array",
                "description": "The collection of items returned from the API request.",
                "items": {
                  "$ref": "#/definitions/permission"
                }
              }
            }
        },
       "permission": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int64",
                    "description": "Unique identifier representing a specific permission."
                },
                "label": {
                    "type": "string",
                    "description": "The label of a permission."
                },
                "description": {
                    "type": "string",
                    "description": "A description of the permission."
                },
                "active": {
                    "type": "boolean",
                    "description": "A flag indicating whether a permission is active."
                }
            },
        "required": [
          "id",
          "label",
          "description",
          "active"
        ]
        }
    }
}

Example 3 - additional properties:

{
    "swagger": "2.0",
    "host": "localhost:8888",
    "info": {
        "version": "0.0.1",
        "title": "API"
    },
    "paths": {
        "/permissions": {
            "get": {
                "description": "Returns all permissions",
                "operationId": "getPermissions",
                "produces": [
                    "application/json"
                ],
                "responses": {
                    "200": {
                        "description": "success",
                        "schema": {
                            "$ref": "#/definitions/permissionResponse"
                    }
                }
            }
        }
        }
    },
    "definitions": {
       "response": {
           "type": "object",
           "properties": {
              "message": {
                "type": "string",
                "description": "A string indicating the response from the server."
              },
              "totalElements": {
                "type": "integer",
                "format": "int64",
                "description": "The number of items retrieved."
              },
              "status": {
                "type": "string",
                "description": "A string indicating the response from the server."
              }
            }
        },
       "permissionResponse": {
           "type": "object",
           "properties": {
              "data": {
                "type": "array",
                "description": "The collection of items returned from the API request.",
                "items": {
                  "$ref": "#/definitions/permission"
                }
              }
            },
            "additionalProperties": {
              "$ref": "#/definitions/response"
            }
        },
       "permission": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int64",
                    "description": "Unique identifier representing a specific permission."
                },
                "label": {
                    "type": "string",
                    "description": "The label of a permission."
                },
                "description": {
                    "type": "string",
                    "description": "A description of the permission."
                },
                "active": {
                    "type": "boolean",
                    "description": "A flag indicating whether a permission is active."
                }
            },
        "required": [
          "id",
          "label",
          "description",
          "active"
        ]
        }
    }
}

atdiff avatar Aug 30 '16 20:08 atdiff

If I understand right, you want support for generic data types. Apart from simple maps (with string keys, being represented as JSON objects) or lists (being represented as JSON arrays), OpenAPI doesn't support that yet.

A related issue I found is #519.

ePaul avatar Aug 31 '16 09:08 ePaul

@ePaul , yes sort of. I want to be able to define a data type that's generic - like a "response" that has basic fields but I want to be able to further define that content for each API (data containing permissions or roles or whatever). The composition concept in Swagger (example 1 above) works great when I run it through swagger2markup and renders the content as I would hope/expect. SwaggerUI represents it as multiple objects and some nesting as a part of it too, so it's rather odd and confusing. This leaves me questioning if it's an issue with SwaggerUI, my Swagger structure, or something else. So trying to figure out the best way to represent the scenario in Swagger.

Markup rendered: markup_rendered

Swagger UI rendered: ui_rendered

Swagger UI rendered - expanded: ui_rendered_expanded

atdiff avatar Aug 31 '16 17:08 atdiff

will this to be supported in future?

shyandsy avatar Aug 12 '17 07:08 shyandsy

I am evaluating this as a possible use case for overlays, https://github.com/OAI/Overlay-Specification/discussions/9.

I don't think overlays are needed here. I believe this can be represented in openapi3 as example (I did not test this)

{
  "openapi": "3.0.1",
  "info": {
    "title": "API",
    "version": "1.0"
  },
  "paths": {
    "/permissions": {
      "get": {
        "description": "Returns all permissions",
        "operationId": "getPermissions",
        "responses": {
          "200": {
            "description": "success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/permissionResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "permissionResponse": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "A string indicating the response from the server."
          },
          "totalElements": {
            "type": "integer",
            "description": "The number of items retrieved.",
            "format": "int64"
          },
          "status": {
            "type": "string",
            "description": "A string indicating the response from the server."
          },
          "data": {
            "type": "array",
            "description": "The collection of items returned from the API request.",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/permission",
                },
                {
                  "$ref": "#/components/schemas/role"
                }
              ]
              
            }
          }
        }
      },
      "role": {
        "type": "object",
        "properties": {
          "roleName": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "permission": {
        "required": [
          "active",
          "description",
          "id",
          "label"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier representing a specific permission.",
            "format": "int64"
          },
          "label": {
            "type": "string",
            "description": "The label of a permission."
          },
          "description": {
            "type": "string",
            "description": "A description of the permission."
          },
          "active": {
            "type": "boolean",
            "description": "A flag indicating whether a permission is active."
          }
        }
      }
    }
  }
}

which looks like this at editor.swagger.io

Screen Shot 2022-08-24 at 8 57 31 PM

kscheirer avatar Aug 25 '22 03:08 kscheirer

This has been supported in OAS 3.1 by using dynamic references to implement generic types.

handrews avatar Jan 27 '24 01:01 handrews