question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

DRY Specifications via $ref

See original GitHub issue

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.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:2
  • Comments:12 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
handrewscommented, Jan 23, 2018

@MikeRalphson OK the reference object rules in OAS match how JSON Schema works. In older drafts it was outside of the spec and therefore could be used anywhere, but we changed that in draft-05. We have strong reasons for not allowing $refs to things other than schemas but it’s also a long story so I’ll leave it at that 😃

2reactions
handrewscommented, Mar 2, 2018

@BenjamenMeyer I’m one of the editors of the JSON Schema spec.

There are a couple of things going on here, some due to JSON and YAML, and others specific to OAS. As phrased, none of this is actually about JSON Schema.

For all practical purposes, JSON does not allow allow duplicate keys in objects. They do not technically violate the RFC, but the RFC notes that the behavior is undefined and therefore not interoperable. Furthermore, YAML absolutely requires unique keys. So your number 3, putting multiple $ref keys in the same object, is not possible.

The correct way to have multiple $refs is to simply wrap them in an allOf:

allOf:
  - $ref: '#/components/headers/x-my-header-1'
  - $ref: '#/components/headers/x-my-header-2'
  - $ref: '#/components/headers/x-my-header-3'

which reduces your 3 to a combination of 1 and 2. Those are limitations of OAS. JSON Schema allows $ref anywhere a schema is expected, and allows allOf as a keyword in any schema object. There are a number of issues and discussions going on about whether and how to converge OAS’s schema variant and JSON Schema proper, although there is no particular timeline on that right now.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Container Specifications | ONE - Ocean Network Express
Dry ; Inside Measurement, Length (mm) Width (mm) Height (mm), 5,898 2,350 2,390 ; Door Opening, Width (mm) Height (mm), 2,340 2,280 ;...
Read more >
Container Specification - Hapag-Lloyd
This container specification booklet provides guidance on the main technical data for Hapag-Lloyd containers, with a focus on dimensions, weights and design ...
Read more >
Test Procedures and Acceptance Criteria for New Drug ...
NOTE FOR GUIDANCE SPECIFICATIONS: TEST PROCEDURES AND. ACCEPTANCE CRITERIA FOR NEW DRUG ... For new drug substance reference standards intended for use in....
Read more >
MATCRETE® Dustone Color Hardener™ Stamping Guide ...
This guide specification is intended to be used by a qualified ... just enough to bring moisture from base slab through dry-shake color...
Read more >
dry-ice-shipping.pdf - Wayne State research
Packages that are less than 5 kg (11 lbs.) only require that the package marking UN1845 be of adequate size. NOTE: If using...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found