OAI / OpenAPI-Specification

The OpenAPI Specification Repository
https://openapis.org
Apache License 2.0
28.83k stars 9.07k forks source link

Generic wrapper for response: questions about combining oneOf and allOf to mimick json-api #1678

Closed vanch3d closed 4 years ago

vanch3d commented 6 years ago

I have been using OpenAPI (v3) for my own project, learning it while doing so my problem might be just an oversight or a lack of practice with Swagger/OAS. I have been trying to get all my API response to issue a standard top-level wrapper, mimicking JSON-API specs (not full compliance yet) with "data", "meta" and "errors". I used to have the properties defined directly in the response schema, eg

paths:
  [...]
      responses:
              [...]
              schema:
                properties:
                  data:
                    type: string   # whatever payload
                  meta:
                    type: string   # whatever payload

but it means a lot of repetitions and no obvious semantics. So I created a proper schema for the top-level wrappers and, using model composition with the allOf keyword (and a bit of playing around), I got a more meaningful output:

components:
  schemas:
    v1.JsonApi:
      properties:
        data:
          description: ''
          type: object
        meta:
          description: ''
          type: object
        errors:
          description: ''
          type: array
          items:
            $ref: '#/components/schemas/ApiProblem'
      [...]

paths:
  [...]
      responses:
              [...]
              schema:
                allOf:
                  - $ref: '#/components/schemas/v1.JsonApi'
                  - properties:
                      data:
                        type: string  # whatever payload
                      meta:
                        type: string  # whatever payload

The combination makes clear the output is of a JSONAPI schema (and will be validated off it) then 'overrides' the properties with the local paylod. So far so good.

But I still get the errors property as part of the JSONAPI schema; as with the specs, both data+meta and errors top-level properties should be mutually exclusive. Finally, I tried to redefined the JSONAPI schema using the oneOf keywords to separate the two groups:

    v3.JsonApi:
      description: A better exclusion of the two groups of properties, usinf oneOf.
      oneOf:
      - properties:
          data:
            description: ''
            type: object
          meta:
            description: ''
            type: object
      - properties:
          errors:
            description: ''
            type: array
            items:
              $ref: '#/components/schemas/ApiProblem'
      type: object

paths:
  [...]
      responses:
              [...]
              schema:
                allOf:
                  - $ref: '#/components/schemas/v3.JsonApi'
                  - properties:
                      data:
                        type: string  # whatever payload
                      meta:
                        type: string  # whatever payload

But this time, it is not working: it is valid but the output is wrong (in swaggerUI or Hub, both in example and model).

So, three questions really:

  1. Did I miss something is tems of using json-api within OAS? I have checked both GitHub and SO, nothing pertinent.
  2. Am I using allOf properly when combining two schemas to override some of the properties or is this more of an exploit or side effect?
  3. Am I doing something wrong when trying to combine a allOf (in response) with a oneOf (in schema)? Is it just a rendering issue with SwaggerHub or a limitation of the OAS?

I've created a wee specs on SwaggerHub if you want to play around.

handrews commented 6 years ago

In terms of JSON Schema this looks mostly correct. You will get into trouble if you allOf your local format with a response that has an error, but since that response is supposed to be for a 200 status (I assume?) then that shouldn't happen- you would define your specific error responses, if any, separately.

I think there is some effort to write a JSON Schema for JSON:API somewhere (@philsturgeon was looking at it, I think?).

I can't speak to the Swagger tooling situation, though. You might want to file an issue in their repo as this repo is for spec questions.

philsturgeon commented 6 years ago

There is a JSON Schema file written to describe a valid JSON:API payload here: http://jsonapi.org/faq/#is-there-a-json-schema-describing-json-api

@handrews I was looking at getting JSON:API to recommend JSON Schema as their type system, which is a little different. :)

handrews commented 6 years ago

@philsturgeon oh, cool. Either way, I got you to link to the thing I was too lazy to look up ;-)

handrews commented 4 years ago

Given that this is well over a year old with no further comment, I'm declaring it answered.

fembuelita commented 3 years ago

I found @vanch3d's original post extremely useful, and tried to solve why the example API data would show empty instead of the examples provided. I think it's because listed above data was set to object but should be array.

Example:

paths:
  '/addresses':
    get:
      responses:
        '200':
          description: Ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/v3.JsonApi'
                  - properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/Address"
                      meta:
                        type: object

[...]
components:
  schemas:
    [...]
    v3.JsonApi:
      description: A better exclusion of the two groups of properties, using oneOf.
      oneOf:
        - properties:
            data:
              description: The matching data
              type: array
            meta:
              description: Any relevant metadata
              type: object
        - properties:
            errors:
              description: ''
              type: array
              items:
                $ref: '#/components/schemas/ApiProblem'
      type: object
    Address:
      type: object
      required:
        - id
        - street_1
        - city
        - state
        - postcode
        - country
        - addressable_type
        - addressable_id
        - created_at
        - updated_at
      properties:
        id:
          type: string
          example: "19d0c665-29f1-382a-9d67-37a0fd6a49fa"
        street_1:
          type: string
          example: "123 Main St"
        street_2:
          type: string
          example: "Apt 3"
        city:
          type: string
          example: "Chicago"
        state:
          type: string
          example: "IL"
        postcode:
          type: string
          example: "60660"
        country:
          type: string
          example: "US"
        addressable_type:
          type: string
          example: "business"
        addressable_id:
          type: string
          example: "11b7654f-41b4-3936-822b-d5eebd160d06"
        created_at:
          type: string
          example: "2021-03-07T00:29:36.000000Z"
        updated_at:
          type: string
          example: "2021-03-07T00:29:36.000000Z"
        deleted_at:
          type: string
          example: null

Then, when viewing the generated swagger docs, you can see example data showing in the 200 response preview: Screen Shot 2021-03-08 at 2 18 04 PM

When left to object for data and meta, the example shows: Screen Shot 2021-03-08 at 2 19 28 PM