swagger-ui icon indicating copy to clipboard operation
swagger-ui copied to clipboard

Ft/3803 multiple responses using one of attribute do not appear in ui

Open VIIgit opened this issue 6 years ago • 60 comments

Description

Display oneOf/anyOf schema in Example View.

  1. Appears only on OAS3 specifications (obviously) and if an oneOf/anyOf structure is present on RequestBody or on Response
  2. By default the first oneOf/anyOf item is shown to keep the UI noise at a minimum
  3. Option to manually select oneOf/anyOf item
  4. config option to switch feature on/off (showAlternativeSchemaExample: false)
    • could be removed in case the feature becomes standard
    const ui = SwaggerUIBundle({
        ....
        showAlternativeSchemaExample: true,
        ...
  1. Supports and covered with test cases
  • oneOf:

    • of objects: example default: selects first item
    • of primitive data types: example default: selects first item
    • of arrays with oneOf subschemas: example default: selects first item
    • of arrays of an object with an oneOf subschema: example default: object with first item of subschema
    • uses discriminator's mapping values as example Swagger Inheritance and Polymorphism
  • anyOf:

    • of objects: example default: selects first item
    • of primitive data types: example default: selects first item
    • of arrays with anyOf subschemas: all items are shown as example value
    • of arrays of an object with an anyOf subschema: example default: object with first item of subschema
  • Not yet supported/considered:

    • oneOf/anyOf of arrays of an object with an oneOf/anyOf subschema: all permutation of an oneOf/anyOf could be listed
    • random selection or round-robin selection (is a bit non-deterministic and UI is leaner without)

Motivation and Context

Fixes #3803 Multiple responses using oneOf attribute do not appear in UI

How Has This Been Tested?

Swagger to demonstrate the feature

openapi: 3.0.1
info:
  title: OneOf examples
  license:
    name: Apache 2.0
    url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
  version: 1.0.0
servers: 
 - url: localhost
paths:

  /delivery-addresses/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    patch:
      summary: Update delivery address
      operationId: patchDeleiverAddress
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliveryAddress'
          application/xml:
            schema:
              $ref: '#/components/schemas/DeliveryAddress'
      responses:
        '200':
          description: successful operation
          x-content:
            application/hal-form+json:
              schema:
                type: object
              x-schemaRef: '#/components/schemas/DeliveryAddress'

    get:
      summary: Read delivery address
      operationId: getDeliveryAddress
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryAddress'

components:
  schemas:
    DeliveryAddress:
      type: object
      title: Delivery Address
      description: OneOf Example
      properties:
        address:
          type: string
        city:
          type: string
        options:
          oneOf:
            - type: object
              title: Delivery Express
              required:
                - company
              properties:
                company:
                  type: string              
                shipping:
                  type: string
                  default: STANDARD
                  example: STANDARD
                  enum:
                    - FAST
                    - STANDARD
            - type: object
              title: Parcel Quick
              required:
                - company
              properties:
                company:
                  type: string
                remarks:
                  type: string
        options2:               
          anyOf:
            - type: object
              title: US
              properties:
                state:
                  type: string
                zipCode:
                  type: string
              required:
                - zipCode
                - state
            - type: object
              title: Europe
              properties:
                county:
                  type: string
                postCode:
                  type: string
              required:
                - postCode  
 

Screenshots:

IMG_4872

Checklist

My PR contains...

  • [ ] No code changes (src/ is unmodified: changes to documentation, CI, metadata, etc.)
  • [ ] Dependency changes (any modification to dependencies in package.json)
  • [ ] Bug fixes (non-breaking change which fixes an issue)
  • [ ] Improvements (misc. changes to existing features)
  • [x] Features (non-breaking change which adds functionality)

My changes...

  • [ ] are breaking changes to a public API (config options, System API, major UI change, etc).
  • [ ] are breaking changes to a private API (Redux, component props, utility functions, etc.).
  • [ ] are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
  • [x] are not breaking changes.

Documentation

  • [ ] My changes do not require a change to the project documentation.
  • [ ] My changes require a change to the project documentation.
  • [x] If yes to above: I have updated the documentation accordingly.

Automated tests

  • [ ] My changes can not or do not need to be tested.
  • [x] My changes can and should be tested by unit and/or integration tests.
  • [x] If yes to above: I have added tests to cover my changes.
  • [ ] If yes to above: I have taken care to cover edge cases in my tests.
  • [x] All new and existing tests passed.

VIIgit avatar Aug 14 '19 21:08 VIIgit

Merge it please!

AlexNik avatar Sep 11 '19 16:09 AlexNik

Is this intended to only work with oneOf or anyOf as a child of a property definition like in the example above?

type: "object"
properties:
    "options":
        oneOf: 
            - …
            - …
        

oneOf/anyOf can appear at the top level of a schema, too.

type: "object"
oneOf:
    - …
    - …   

hannes-ucsc avatar Sep 12 '19 04:09 hannes-ucsc

oneOf/anyOf at the top level oneOf

openapi: 3.0.1
info:
  title: OneOf examples
  license:
    name: Apache 2.0
    url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
  version: 1.0.0
servers: 
 - url: localhost
paths:

  /delivery-addresses/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    patch:
      summary: Update delivery address
      operationId: patchDeleiverAddress
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliveryAddress'
          application/xml:
            schema:
              $ref: '#/components/schemas/DeliveryAddress'
      responses:
        '200':
          description: successful operation
          x-content:
            application/hal-form+json:
              schema:
                type: object
              x-schemaRef: '#/components/schemas/DeliveryAddress'

    get:
      summary: Read delivery address
      operationId: getDeliveryAddress
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryAddress'

components:
  schemas:
    DeliveryAddress:
      oneOf:
      - type: object
        title: Online Delivery
        properties:
          email:
            type: string     
            example: [email protected] 
      - type: object
        title: Delivery Address
        description: OneOf Example
        properties:
          address:
            type: string
          city:
            type: string
          options:
            oneOf:
              - type: object
                title: Delivery Express
                required:
                  - company
                properties:
                  company:
                    type: string              
                  shipping:
                    type: string
                    default: STANDARD
                    example: STANDARD
                    x-oneOf:
                      - const: FAST
                        title: 24h + EUR 40
                      - const: STANDARD
                        title: 3-5 Days
              - type: object
                title: Parcel Quick
                required:
                  - company
                properties:
                  company:
                    type: string
                    x-const: PQ
                  remarks:
                    type: string
          options2:               
            oneOf:
              - type: object
                title: US
                properties:
                  state:
                    type: string
                  zipCode:
                    type: string
                required:
                  - zipCode
                  - state
              - type: object
                title: Europe
                properties:
                  county:
                    type: string
                  postCode:
                    type: string
                required:
                  - postCode  
  

VIIgit avatar Sep 13 '19 19:09 VIIgit

Interesting. I tried your branch on my spec and couldn't get it to work. Is it because the children of my oneOf are $ref or because the parent schema has properties as well?

hannes-ucsc avatar Sep 14 '19 06:09 hannes-ucsc

Interesting. I tried your branch on my spec and couldn't get it to work. Is it because the children of my oneOf are $ref or because the parent schema has properties as well?

Upps, interesting! I'll try to consider your scenario as well...

VIIgit avatar Sep 14 '19 19:09 VIIgit

Interesting. I tried your branch on my spec and couldn't get it to work. Is it because the children of my oneOf are $ref or because the parent schema has properties as well?

Hi Hannes,

your use case works now too: 2019-09-23 um 3 06 37 PM

VIIgit avatar Sep 23 '19 13:09 VIIgit

Cool! What do you think about adding support for discriminator? With my spec this would eliminate the issue that the example value for integration_type does not change depending on the selected alternative.

Also noticed that for lists of documents whose schema has a oneOf, the examples only represent a partial set of properties.

Screen Shot 2019-09-23 at 12 14 14 PM

The "manually selected" UI element also doesn't populate a list of alternatives but I'm not sure that is needed here. It may make more sense to simply select the first alternative for the first document, the second alternative for the second document and so on.

hannes-ucsc avatar Sep 23 '19 19:09 hannes-ucsc

The above screen shot is from the 200 response for the GET /portal/{portal_id}/integration path in my spec.

hannes-ucsc avatar Sep 23 '19 19:09 hannes-ucsc

Cool! What do you think about adding support for discriminator? With my spec this would eliminate the issue that the example value for integration_type does not change depending on the selected alternative.

Also noticed that for lists of documents whose schema has a oneOf, the examples only represent a partial set of properties.

Screen Shot 2019-09-23 at 12 14 14 PM

The "manually selected" UI element also doesn't populate a list of alternatives but I'm not sure that is needed here. It may make more sense to simply select the first alternative for the first document, the second alternative for the second document and so on.

  1. Support for discriminator's mapping type added. But I've considered discriminator only at the same level as oneOf/anyOf like in the example of https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/ . I'm not sure if your spec is correct.

  2. OneOf within array/items schema is now supported too

VIIgit avatar Sep 25 '19 22:09 VIIgit

I can confirm that arrays work better now in that all item properties are populated. But the arrays have one item only. I would still consider generating multiple items and to round-rob through the oneOf alternatives. For a single item there is no choice than to pick one of the alternatives but for an array there is the opportunity to pick more than one. BTW: I would also consider using a random choice of alternative instead of the first. But maybe the non-determinism is undesirable.

The discriminator isn't working for me, even though in the latest version of my spec discriminator and oneOf are sibling properties (at the same level). An earlier version was more complicated and, as you suggest, probably incorrect. But the latest version closely resembles the example from the documentation that you refer to.

In the screen shot below (200 response from GET /portal/{portal_id}/integration/{integration_id}) the fourth oneOf alternative is selected but the discriminator property integration_type has the value mapped to the first alternative get. It should be get_manifest.

Screen Shot 2019-09-26 at 10 20 18 AM

hannes-ucsc avatar Sep 26 '19 17:09 hannes-ucsc

I would still consider generating multiple items and to round-rob through the oneOf alternatives.

I agree, it could be a solution.

BTW: I would also consider using a random choice of alternative instead of the first. But maybe the non-determinism is undesirable.

I had a random choice in my initial proposal I've presented to my colleagues, but UI get's a bit non-deterministic and UI is leaner without.

The discriminator isn't working for me, even though in the latest version of my spec discriminator and oneOf are sibling properties (at the same level). An earlier version was more complicated and, as you suggest, probably incorrect. But the latest version closely resembles the example from the documentation that you refer to.

In the screen shot below (200 response from GET /portal/{portal_id}/integration/{integration_id}) the fourth oneOf alternative is selected but the discriminator property integration_type has the value mapped to the first alternative get. It should be get_manifest.

With Pull request your example works. I'm not sure if this the only option we should support.

VIIgit avatar Sep 29 '19 13:09 VIIgit

I see. The PR pushes the property down from the super-schema into each sub-schema. It would be suboptimal if that were the only solution because the point of oneOf and schema polymorphism is to not have to repeat common properties in sub-schemas.

hannes-ucsc avatar Sep 30 '19 19:09 hannes-ucsc

Hi Hannes, with the latest update, discriminators of the super-schema are supported too :)

VIIgit avatar Oct 02 '19 21:10 VIIgit

I tried it out and it works great! Thank you!

hannes-ucsc avatar Oct 07 '19 21:10 hannes-ucsc

Hi @shockey,

The pull request has basically two parts:

  1. The sampleFromSchema function (scr/core/plugins/sample/fn) - which I've improved to handle various oneOf/anfOf use cases
  2. The visualisation part - which should/could be wrapped to enable other implementation

Is there anything I can do to help you pushing this pull request into master? What is missing?

Many thanks Erwin

VIIgit avatar Oct 12 '19 08:10 VIIgit

oneOf/anyOf at the top level oneOf

openapi: 3.0.1
info:
  title: OneOf examples
  license:
    name: Apache 2.0
    url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
  version: 1.0.0
servers: 
 - url: localhost
paths:

  /delivery-addresses/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    patch:
      summary: Update delivery address
      operationId: patchDeleiverAddress
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliveryAddress'
          application/xml:
            schema:
              $ref: '#/components/schemas/DeliveryAddress'
      responses:
        '200':
          description: successful operation
          x-content:
            application/hal-form+json:
              schema:
                type: object
              x-schemaRef: '#/components/schemas/DeliveryAddress'

    get:
      summary: Read delivery address
      operationId: getDeliveryAddress
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryAddress'

components:
  schemas:
    DeliveryAddress:
      oneOf:
      - type: object
        title: Online Delivery
        properties:
          email:
            type: string     
            example: [email protected] 
      - type: object
        title: Delivery Address
        description: OneOf Example
        properties:
          address:
            type: string
          city:
            type: string
          options:
            oneOf:
              - type: object
                title: Delivery Express
                required:
                  - company
                properties:
                  company:
                    type: string              
                  shipping:
                    type: string
                    default: STANDARD
                    example: STANDARD
                    x-oneOf:
                      - const: FAST
                        title: 24h + EUR 40
                      - const: STANDARD
                        title: 3-5 Days
              - type: object
                title: Parcel Quick
                required:
                  - company
                properties:
                  company:
                    type: string
                    x-const: PQ
                  remarks:
                    type: string
          options2:               
            oneOf:
              - type: object
                title: US
                properties:
                  state:
                    type: string
                  zipCode:
                    type: string
                required:
                  - zipCode
                  - state
              - type: object
                title: Europe
                properties:
                  county:
                    type: string
                  postCode:
                    type: string
                required:
                  - postCode  
  

Hi !,

Great job you've done here! So sorry I'm kinda new to swagger and I need to see if the multiple payload in a single endpoint would be possible,

I've tried building the latest changes you've done, but it's currently not working on my end. The steps I did was npm install npm run build then copy the dist files to my apache server then updated the index.html to point to the same yaml file listed in this quoted link.

the page i'm getting. image

Any help from you would be great !

Thanks, Cedric

cedieio avatar Dec 19 '19 16:12 cedieio

Hi !,

Great job you've done here! So sorry I'm kinda new to swagger and I need to see if the multiple payload in a single endpoint would be possible,

I've tried building the latest changes you've done, but it's currently not working on my end. The steps I did was npm install npm run build then copy the dist files to my apache server then updated the index.html to point to the same yaml file listed in this quoted link.

the page i'm getting. image

Any help from you would be great !

Thanks, Cedric

Adam prepared an published a release > https://github.com/swagger-api/swagger-ui/issues/3803#issuecomment-561344844 btw thanks Adam!

or you might just didn't realized to add the option to switch feature on/off (showAlternativeSchemaExample: false) - could be removed in case the feature becomes standard

    const ui = SwaggerUIBundle({
        ....
        showAlternativeSchemaExample: true,
        ...

VIIgit avatar Dec 20 '19 10:12 VIIgit

Ah !

I manually grepped the showAlternativeSchemaExample on the dist folder and the apache serve I'm hosting and found the issue. It seems that I was not overwritting properly on the apache server itself !

Thank you so much !

cedieio avatar Dec 20 '19 14:12 cedieio

@tim-lai @hkosova @Simran-B @shockey @webron could you merge it please?

AlexNik avatar Mar 25 '20 11:03 AlexNik

wonder why this is not merged? :- )

freelinuxer avatar Apr 02 '20 03:04 freelinuxer

@tim-lai @hkosova @Simran-B @shockey @webron what do you still need to be able to merge this PR? Let us know, so that we can help you address those things and get this awesome work, anticipated by hundreds of devs, merged.

@VIIgit special thanks for making this possible!, kudos!

dsych avatar Apr 02 '20 12:04 dsych

btw, Redoc, your competitor, advertises support for polymorphism as one of their killer features... Just saying.

dsych avatar Apr 02 '20 12:04 dsych

I'd expect the maintainers to at least clarify what they intent to do with this PR. If current workload prevents them from even looking at it, that's fine. I have a day job, too. But it takes 30s to type that into a comment. Radio silence is not in the spirit of open source, especially considering that contributions are explicitly encouraged.

hannes-ucsc avatar Apr 02 '20 21:04 hannes-ucsc

I am still getting (no example available) even after trying to follow the examples above. The schema shows the structure but there are no examples shown or options to select options Screenshot 2020-04-20 at 07 02 28

lawrence615 avatar Apr 20 '20 04:04 lawrence615

I am still getting (no example available) even after trying to follow the examples above. The schema shows the structure but there are no examples shown or options to select options Screenshot 2020-04-20 at 07 02 28

just to make sure: you might need add the option showAlternativeSchemaExample to switch feature on (could be removed in case the feature becomes standard):

const ui = SwaggerUIBundle({
    ....
    showAlternativeSchemaExample: true,
    ...

VIIgit avatar Apr 20 '20 07:04 VIIgit

I was actually testing it on the editor. But I have also tested it locally with the option and still shows (no example available). Am I missing something in my implementation?

lawrence615 avatar Apr 20 '20 07:04 lawrence615

I was actually testing it on the editor. But I have also tested it locally with the option and still shows (no example available). Am I missing something in my implementation?

see #3803 for a released and packaged Swagger UI. For the editor would you need to do it by yourself or wait til it's getting merged ....

VIIgit avatar Apr 20 '20 08:04 VIIgit

@VIIgit any plans to support xml?? I have no xml rendered:

image

image

    "CertTypeEnum":
      title: "CertTypeEnum"
      type: string
      enum:
      - "CA"
      - "CERT"
    "CertificateAuthorityBean":
      title: "CertificateAuthorityBean"
      type: object
      required:
      - "certExpiration"
      - "certName"
      - "certType"
      properties:
        "certExpiration":
          description: "Certificate expiry date"
          type: string
        "certName":
          pattern: '^(?=.*\S).+$'
          description: "Certificate name"
          type: string
        "certType":
          $ref: "#/components/schemas/CertTypeEnum"
    "CertificateOrdinaryBean":
      title: "CertificateOrdinaryBean"
      type: object
      required:
      - "certExpiration"
      - "certName"
      - "certType"
      - "parentId"
      properties:
        "certExpiration":
          description: "Certificate expiry date"
          type: string
        "certName":
          pattern: '^(?=.*\S).+$'
          description: "Certificate name"
          type: string
        "certType":
          $ref: "#/components/schemas/CertTypeEnum"
        "parentId":
          type: integer
          format: int64
    "CertificateCommand":
      oneOf:
      - $ref: "#/components/schemas/CertificateAuthorityBean"
      - $ref: "#/components/schemas/CertificateOrdinaryBean"
      discriminator:
        propertyName: "certType"
        mapping:
          "CA": "#/components/schemas/CertificateAuthorityBean"
          "CERT": "#/components/schemas/CertificateOrdinaryBean"

soberich avatar Apr 27 '20 20:04 soberich

To be honest I'm a bit disappointed as I like the SwaggerUI/SwaggerEditor very much and I miss a better support for oneOf/anyOf too. Regardless if it's this proposed solution or any other solution.

But this Pull Request is waiting for more than a half year to be merged. Until now, I haven't received any official feedback nor improvements which needs to be done before the request can be accepted. So I'm sadly not investing any more time on this repository. I may look even for alternatives

VIIgit avatar Apr 28 '20 11:04 VIIgit

@webron ping. Please.

soberich avatar Apr 28 '20 11:04 soberich