redoc icon indicating copy to clipboard operation
redoc copied to clipboard

Discriminator / OneOf broken when using external file reference

Open tms0 opened this issue 6 years ago • 10 comments

The discriminator functionnality is broken when using with external files. Here's a reproducible test case :

openapi: 3.0.0
info:
  title: test
  version: 1.0.0
paths:
  /test:
    get:
      responses:
        '200':
          description: test
          content:
            'application/json':
              schema:
                oneOf:
                  - $ref: './external-file.yaml#/components/schemas/simpleObject'
                  - $ref: './external-file.yaml#/components/schemas/complexObject'
                discriminator:
                  propertyName: objectType

## external-file.yaml :
openapi: 3.0.0
components:
  schemas:
    simpleObject:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string
    complexObject:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string

And when we try to see the responses object in ReDoc, we have the following error :

2019-03-21 10_07_31-MPD API PRIVEE - NFC 1 3

I tried to debug it quickly, and I figured out that in this case, the oneOf variable is empty : https://github.com/Rebilly/ReDoc/blob/master/src/components/Schema/Schema.tsx#L43

Another related issue (I think) : if we remove the discriminator part, it will work but the schemas names are not displayed correctly (object instead of simpleObject for example) :

2019-03-21 10_14_57-MPD API PRIVEE - NFC 1 3

Please tell me if you need more informations to fix it, thanks !

tms0 avatar Mar 21 '19 09:03 tms0

I'm seeing a similar issue to the second when using external references for discriminator mappings in schemas.

kevin-thompson avatar Mar 22 '19 16:03 kevin-thompson

Any update about this issue ? It's really a blocker for us.

At least, can you please tell me if we are doing something wrong or if it's a real issue ?

tms0 avatar Mar 30 '19 07:03 tms0

While not a solution, this is result of few hours digging, hopefully it will help other people to code solutions.

  1. for schema with oneOf and discriminator, code here expecting objects in oneOf array has property $ref to resolve schema, but somehow (by JsonSchemaRefParser i guess) objects in oneOf array are resolved model objects.
  2. for discriminator with mapping, code here seems expecting a JsonSchemaRefParser bundled reference. But mappings do not come with property name $ref so the JsonSchemaRefParser won't process it, therefore the parser.byRef receive an invalid ref.

chikei avatar Apr 12 '19 13:04 chikei

I scoured the internets for weeks with this same issue and found a "workaround". I greatly prefer API specs that make heavy use of external $ref (especially hosted) because of extensibility, modularity, and maintainability. But, as others have mentioned, $ref doesn't play nice with all doc rendering frameworks.

Discriminators are expected to make use of the schemas in components. To accomplish this with a $ref file, you need to add those discriminators via a separate $ref. I show all this below if it's not making sense. This may seem like an unnecessary level of abstraction, but, since unused schemas are just noise, I actually find it more intuitive since you then have full control over what schemas show in the discriminator simply by editing a list.

First, consider a dir structure like:

── raw_spec.yaml
├── my-components/
│   ├── my-requests/
│   │   └── pets_request.yaml
│   ├── my-schemas/
│   │   ├── animals.yaml
│   │   └── discriminators.yaml
└── my-paths/
│   │   └── pets.yaml

The partial spec looks like this:

openapi: 3.0.0

servers:
  - url: https://pets.com

info:
  version: 0.1.0
  title: Pets API

paths:
  /pets:
    $ref: './my-paths/pets.yaml'

components:
  schemas:
    $ref: './my-components/my-schemas/discriminators.yaml'

Then, discriminators.yaml, looks like this:

Cat:
  $ref: 'animals.yaml#/Cat'

Dog:
  $ref: 'animals.yaml#/Dog'

The animals.yaml schemas look like this (note the self referencing for ease of maintenance):

animalSchema:
  type: object
  description: Top-level animal schema
  required:
    - petType
  properties:
    petType:
      type: string
      description: The type of animal
      example: Cat

Cat:
  allOf:
  - $ref: 'animals.yaml#/animalSchema'
  - type: object
    properties:
      cuddles:
        type: boolean

Dog:
  allOf:
  - $ref: 'animals.yaml#/animalSchema'
  - type: object
    properties:
      smells:
        type: boolean

To make use of this bizarre abstraction, let's look at a them in the context of a request schema, for example in a path like /pets as mentioned above:

post:
  description: post a pet
  requestBody:
    $ref: '../my-components/my-requests/pets_request.yaml#/pets_request_schema'

And then the pets_request_schema will look like this:

pets_request_schema:
  content:
    application/json:
      schema:
        type: object
        description: a request schema
        required:
          - myPet
        properties:
          myPet:
            type: array
            items:
              oneOf:
              - $ref: '#/components/schemas/Cat'
              - $ref: '#/components/schemas/Dog'
              discriminator:
                propertyName: petType
                mapping:
                  Cattttt: '#/components/schemas/Cat'
                  Doggg: '#/components/schemas/Dog'

This approach of a class-like, hierarchical structure like in C/C++ can be tedious to think through and create the first time, but it generalizes exceptionally well. But, it is almost impossible to read for a user that wants a raw spec. My suggestion is to run a resolver on your spec, like json-refs, to build a full spec and then give that to ReDoc.

kcavagnolo avatar Apr 12 '19 20:04 kcavagnolo

Sorry for the late reply. Just found some time to dig into this.

@chikei is absolutelly correct. Mostly it is caused by the way how internal dereferencer works in ReDoc...

I don't know how to fix it properly in a timely manner. You can workaround this by re-referencing schemas from main openapi file components:

paths:
  /test:
    get:
      responses:
        '200':
          description: test
          content:
            'application/json':
              schema:
                oneOf:
                  - $ref: '#/components/schemas/simpleObject'
                  - $ref: '#/components/schemas/complexObject'
                discriminator:
                  propertyName: objectType
components:
  schemas:
    simpleObject: 
      $ref: './external-file.yaml#/components/schemas/simpleObject'
    complexObject:
      $ref: './external-file.yaml#/components/schemas/complexObject'

There is still one issue with discriminator titles that I just fixed (https://github.com/Rebilly/ReDoc/commit/a3d7d7a32c2e95952348c16b5f295cbc1030e697) and which will land in tomorrow mornings release.

RomanHotsiy avatar May 12 '19 21:05 RomanHotsiy

Wating fix this bug.

yuan1238y avatar Dec 26 '19 01:12 yuan1238y

This is what I get without mapping: image

And this I get with a mapping - it's completely broken, although there is no error nor warning in the cli: image

And here the schema bit:

    "getNode": {
      "oneOf": [
        {
          "$ref": "./Group/GroupSchema_v1.json#/definitions/getGroup"
        },
        {
          "$ref": "./Project/ProjectSchema_v1.json#/definitions/getProject"
        },
        {
          "$ref": "./Task/TaskSchema_v1.json#/definitions/getTask"
        },
        {
          "$ref": "./Team/TeamSchema_v1.json#/definitions/getTeam"
        },
        {
          "$ref": "./WorkPackage/WorkPackageSchema_v1.json#/definitions/getWorkPackage"
        }
      ],
      "discriminator": {
        "propertyName": "nodeType",
        "mapping": {
          "Group": "./Group/GroupSchema_v1.json#/definitions/getGroup",
          "Project": "./Project/ProjectSchema_v1.json#/definitions/getProject",
          "Task": "./Task/TaskSchema_v1.json#/definitions/getTask",
          "Team": "./Team/TeamSchema_v1.json#/definitions/getTeam",
          "Work Package": "./WorkPackage/WorkPackageSchema_v1.json#/definitions/getWorkPackage"
        }
      }
    }

I'm using the latest redoc-cli 0.10.2 with redoc 2.0.0-rc.48

uncaught avatar Nov 30 '20 16:11 uncaught

same issue

bu4ak avatar Feb 26 '21 12:02 bu4ak

I was able to work around this by bundling a dist version using https://github.com/Redocly/openapi-cli and just using that:

openapi bundle --output dist/openapi.yaml src/openapi.yaml

Since this command bundles referenced schema into a local #/components/schema instead of rendering them inline like json-refs does, discriminator will work even when used with mappings.

peakwinter avatar Mar 11 '21 17:03 peakwinter

I was able to work around this by bundling a dist version using https://github.com/Redocly/openapi-cli and just using that:

openapi bundle --output dist/openapi.yaml src/openapi.yaml

Since this command bundles referenced schema into a local #/components/schema instead of rendering them inline like json-refs does, discriminator will work even when used with mappings.

Weird how this works, but using redocly doesn't.

akindo avatar Aug 22 '23 17:08 akindo