Skip to content

DRY Specifications via $refย #1462

Open
Open
@BenjamenMeyer

Description

@BenjamenMeyer

So I realize there is some discussions regarding some "similar" things, but I'm not quite sure they're similar enough - if so, please say so and I'll contribute to the other discussions accordingly and close this one out. Other similar but not quite as extensive discussions I have found:

Basically, one should be able to write their OpenAPI specification in as DRY a manner as possible. Presently this is not possible because:

  1. $ref is not valid at all the right places
  2. allOf is not valid at the right places (which could make up for point 1)
  3. $ref can't be specified numerous times next to itself appropriately

Let's give this some better clarity via an example. Discussing API Header Fields provides a good example such as the following which similar to #417 but I'm going more general where that seems to be focused on path objects:

paths:
    /:
        get:
            parameters:
                - $ref: `#/components/headers/x-my-header-1`
                - $ref: `#/components/headers/x-my-header-3`
            responses:
                '401':
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-3`
                '403':
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-3`
                '200':
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-2`
                        $ref: `#/components/headers/x-my-header-3`
               '301':
                    headers:
                        allOf:
                            - $ref: `#/components/headers/x-my-header-1`
                            - $ref: `#/components/headers/x-my-header-2`
                            - $ref: `#/components/headers/x-my-header-3`
                default:
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-3`
        post:
            parameters:
                - $ref: `#/components/headers/x-my-header-1`
                - $ref: `#/components/headers/x-my-header-2`
            responses:
                '401':
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-3`
                '403':
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-3`
                '200':
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-2`
                        $ref: `#/components/headers/x-my-header-3`
                default:
                    headers:
                        $ref: `#/components/headers/x-my-header-1`
                        $ref: `#/components/headers/x-my-header-3`
    /howdy:
        delete:
            parameters:
                - $ref: `#/components/headers/x-my-header-1`
                - $ref: `#/components/headers/x-my-header-2`
            responses:
            '401':
                headers:
                    $ref: `#/components/headers/x-my-header-1`
                    $ref: `#/components/headers/x-my-header-3`
            '403':
                headers:
                    $ref: `#/components/headers/x-my-header-1`
                    $ref: `#/components/headers/x-my-header-3`
            '200':
                headers:
                    $ref: `#/components/headers/x-my-header-1`
                    $ref: `#/components/headers/x-my-header-2`
                    $ref: `#/components/headers/x-my-header-3`
            default:
                headers:
                    $ref: `#/components/headers/x-my-header-1`
                    $ref: `#/components/headers/x-my-header-3`
components:
    headers:
        x-my-header-1:
            required: true
            description: my first header
            schema:
            type: string
            format: uuid
        x-my-header-2:
            required: true
            description: my second header
            schema:
            type: string
        x-my-header-3:
            required: true
            description: my third header
            schema:
            type: integer

The above is missing a lot of stuff, but only in order to show the re-use and avoid having to put a complete spec here. I know some of the above can be fixed by moving up a layer in the referencing, e.g create a component for the header set and reference that instead; however that can then dictate a bad spec by forcing all headers to be pushed to header sets which then can also be an issue for 1-off header combinations.

Goal here is to enable spec writers to manage their specifications by creating re-usable modules that can be continuously re-used. To achieve this, $ref would need to be valid in essentially ever object to replace anything in the object. F.e if you have a description field you want to re-use, define it once and use it 10 times.

Two parallel $ref values should be valid and able to point to separate objects, neither being discarded, or allOf could be used to combine the contents of both $ref references to create the same effect (good solution for one-offs).

When building larger APIs using tooling like OpenAPI being able to be a DRY as possible is key to keeping bugs from creeping into the specs as it reduces the ability for any one instance to be mis-typed. For instance, if you had to type x-my-header-3 for every single Request object and x-my-header-2 for every single Response object - one typo of my-header-3 or xmy-header-3 could easily create something hard to detect where using the $ref objects would make it fail validation and be easily caught in gate checks (PR builders, etc).

To re-iterate - please let me know if I need to file this with JSON Reference/Schema too. I did not find anything suitably talking about these aspects while reading through any of the repos I came across, but have in general there does seem to be some of sentiment that this is an issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    re-use: ref-everywhereRequests to support referencing in more / all placesre-use: ref-group-combineRe-use requests involving grouping components and combining groups

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions