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

Models sections are not rendered if YAML file uses externally $ref.

Open its-atique1987 opened this issue 6 years ago • 6 comments

Q&A (please complete the following information)

  • OS: windows
  • Browser: chrome
  • Swagger-UI version: 3.22.1

Content & configuration

Example Swagger/OpenAPI definition: openapi.yaml

openapi: 3.0.0
info:
  title: Common Data Types
  version: "1.0"
paths:
  /{appId}/subscriptions:
    get:
      summary: read all of the active subscriptions for the applictaion.
      operationId: getSubscriptionsById
      parameters:
        - name: appId
          in: path
          description: App ID
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: OK (Successful)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: 'schemas.yaml#/components/schemas/Category'
        '400':
          $ref: './responses.yaml#/components/responses/E400'
        '401':
          $ref: './responses.yaml#/components/responses/E401'
        '500':
          $ref: './responses.yaml#/components/responses/E500'

schemas.yaml

openapi: 3.0.0
info:
  title: Common Data Types
  version: "1.0"
paths: {}
components:
  schemas:
      Category:
        type: "object"
        properties:
          id:
            type: "integer"
            format: "int64"
          name:
            type: "string"
        xml:
          name: "Category"
      subscription:
        type: string

responses.yaml

openapi: 3.0.0
info:
  title: Common Data Types
  version: "1.0"
paths: {}
components:
  schemas:
    ProblemDetails:
      type: object
      properties:
        title:
          type: string
          description: A short, human-readable summary of the problem
        status:
          type: integer
          description: The HTTP status code for this occurrence of the problem.
  responses:
    E400:
      description: Bad request
    E401:
      description: Unauthorized
    E500:
      description: Server Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'

Describe the bug you're encountering

I splitted my specs into multiple YAML file using $ref attribute. Able to successfully generate stubs using command line java -jar openapi-generator-cli-4.0.0-beta3.jar generate -i openapi.yaml -g spring -o ./temp. However, while rendering on Swagger UI, I see that Models are NOT displayed. And also schema names are not displayed correctly

what's wrong in referencing an external yaml file? If local references are used, UI is rendered correctly with Models section.

To reproduce...

  • npm install -g http-server
  • cd ...path_to_main_yaml_file/openapi
  • http-server -p 3333 -c-1 --cors
  • Open swagger ui (i.e. dist/index.html)
  • updated index.html with url http://localhost:3333/openapi.yaml
  • open the index.html

Expected behavior

Should see the

Models

section on swagger UI And Schema name should be Category and Not schemas.yamlCategory

Screenshots

image

its-atique1987 avatar Apr 20 '19 19:04 its-atique1987

Any update would be grateful. Thanks

its-atique1987 avatar Apr 23 '19 06:04 its-atique1987

Hi,

I also have a similar problem,

My referenced .yaml file is properly requested.

all.yaml

/api/v1/objects:
  get:
    description: Return all objects
    responses:
      200:
        description: Ok
        content:
          application/vnd.api+json:
            schema:
              $ref: 'object.yaml#/components/schemas/Object'
object.yaml

components:
  schemas:
    Object:
      type: object
      properties:
        type:
          type: string
        id:
          type: string
        name:
          type: string

But not parsed correctly, so I have the following error :

Resolver error at paths./api/v1/objects.get.responses.200.content.application/vnd.api+json.schema.$ref Could not resolve reference: Could not resolve pointer: /components/schemas/Object does not exist in document

If I replace object.yaml with the following object.json. Everything work, no problem.

object.json

{
    "components": {
        "schemas": {
            "Object": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string"
                    },
                    "id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    }
                }   
            }
        }
    }
}

I think it's related to swagger-ui requesting the object.yaml file with an accept-header set at application/json . And it'd why when I provide the json file instead, it work.

Thanks

Update : 29/05/2019 My problem is fixed. Just make sure the referenced file (object.yaml in my case) is rendered as application/yaml if you're not accessing it directly.

lanternet avatar May 27 '19 13:05 lanternet

I think this was fixed by #5334. Or at least the bit to do with the way the schema name is displayed.

mhw avatar Jun 12 '19 16:06 mhw

Q&A (please complete the following information)

  • OS: linux
  • Browser: chrome
  • Version: 85.0.4183.83 (Official Build) (64-bit)
  • Method of installation: npm dist assets
  • Swagger-UI version: "swagger-editor-dist": "^3.12.0",
  • Swagger/OpenAPI version: 3.0.0

Content & configuration

const path = require('path');
const express = require('express');
 
const pathToSwaggerUi = require('swagger-ui-dist').absolutePath();

const app = express();

app.use(express.static(pathToSwaggerUi));
// api folder contains api.yml and endpoint.yml
app.use('/api', express.static(path.join(__dirname, 'api'), {
    etag: false
}));

app.listen(3000);

Example Swagger/OpenAPI definition: The following file shows the schema tab

full.yml

openapi: 3.0.0

info:
  title: API
  description: API
  version: "1.0.0"

servers:
  - url: https://api.com/v1
    description: Production

components:
  schemas:
    A:
      type: object
      required:
        - a
      properties:
        a:
          type: string

paths:
  /endpoint:
    post:
      tags:
        - User
      summary: Summary.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/A'
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/A'

Importing an endpoint as $ref will fail to show the schema tab

partial.yml

openapi: 3.0.0

info:
  title: API
  description: API
  version: "1.0.0"

servers:
  - url: https://api.com/v1
    description: Production


paths:
  /endpoint:
    $ref: './endpoint.yml'

endpoint.yml

components:
  schemas:
    A:
      type: object
      required:
        - a
      properties:
        a:
          type: string

post:
  tags:
    - User
  summary: Summary.
  requestBody:
    required: true
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/A'
  responses:
    200:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/A'

Swagger-UI configuration options: The UI config is not changed. The script provided in the Content & configuration section will produce the exact config as mine

Describe the bug you're encountering

When using a $ref in the path, the endpoint is shown as part of the API but the schema tab is not being shown. When using a single file approach, the schema tab is shown.

To reproduce...

  • Copy the script I've provided in the Content & configuration into a folder
  • run npm init in the script's folder
  • run npm install express swagger-ui-dist --save in the script's folder
  • run node index.js in the script's folder
  • create an api folder inside the script's folder create the full.yml, partial.yml and endpoint.yml provided in the Example Swagger/OpenAPI definition section
  • open a browser at localhost:3000 and swagger UI will be shown
  • load the following URL in swagger UI http://localhost:3000/api/full.yml. The Schema tab will be shown
  • load the following URL in swagger UI http://localhost:3000/api/partial.yml. The Schema tab will be NOT be shown

Expected behavior

When using a $ref: file.yml in the paths property, the schemas used in the imported endpoints should be shown in the Schema Tab

Screenshots

2020-08-27-112237_1503x731_scrot 2020-08-27-112256_1509x586_scrot 2020-08-27-112541_1503x841_scrot

Additional context or thoughts

Notice that the endpoint is detected and the request and response is correctly mapped. But the schema tab may be trying to resolve schemas from within the context of the main file, which would be an error

javiercbk avatar Aug 27 '20 17:08 javiercbk

I "solved" the bug in a very dirty way, I believe the very first code I wrote in Pascal was not as nasty as this.

Anyway I hope this allows you to create a simpler and cleaner solution.

"Solution" in render method in models.jsx

import React, { Component } from "react"
import Im, { Map } from "immutable"
import PropTypes from "prop-types"

export default class Models extends Component {
  static propTypes = {
    getComponent: PropTypes.func,
    specSelectors: PropTypes.object,
    specActions: PropTypes.object.isRequired,
    layoutSelectors: PropTypes.object,
    layoutActions: PropTypes.object,
    getConfigs: PropTypes.func.isRequired,
    // SOLUTION: added spec required
    spec: PropTypes.func.isRequired
  }

  getSchemaBasePath = () => {
    const isOAS3 = this.props.specSelectors.isOAS3()
    return isOAS3 ? ["components", "schemas"] : ["definitions"]
  }

  getCollapsedContent = () => {
    return " "
  }

  handleToggle = (name, isExpanded) => {
    const { layoutActions } = this.props
    layoutActions.show([...this.getSchemaBasePath(), name], isExpanded)
    if(isExpanded) {
      this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
    }
  }

  onLoadModels = (ref) => {
    if (ref) {
      this.props.layoutActions.readyToScroll(this.getSchemaBasePath(), ref)
    }
  }

  onLoadModel = (ref) => {
    if (ref) {
      const name = ref.getAttribute("data-name")
      this.props.layoutActions.readyToScroll([...this.getSchemaBasePath(), name], ref)
    }
  }

  render(){
    let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs, spec } = this.props
    let definitions = specSelectors.definitions()
    // SOLUTION: dirty workaround
    let done = false
    const subtrees = spec().get("resolvedSubtrees")
    if (subtrees) {
      const paths = subtrees.get("paths")
      if (paths) {
        const iter = paths.keys()
        do {
          const iterRes = iter.next()
          const { value } = iterRes
          if (value) {
            const c = paths.get(value).get("components")
            if (c) {
              let def = c.get("schemas")
              // nasty...but I made it work
              let hackDef = Im.Map()
              def.entrySeq().forEach((d) => {
                const name = `paths:${value}:${d[0]}`
                const val = d[1]
                hackDef = hackDef.set(name, val)
              })
              definitions = definitions.mergeDeep(hackDef)
            }
          }
          done = iterRes.done
        } while (!done)
      }
    }
    let { docExpansion, defaultModelsExpandDepth } = getConfigs()
    if (!definitions.size || defaultModelsExpandDepth < 0) return null

    const specPathBase = this.getSchemaBasePath()
    let showModels = layoutSelectors.isShown(specPathBase, defaultModelsExpandDepth > 0 && docExpansion !== "none")
    const isOAS3 = specSelectors.isOAS3()

    const ModelWrapper = getComponent("ModelWrapper")
    const Collapse = getComponent("Collapse")
    const ModelCollapse = getComponent("ModelCollapse")
    const JumpToPath = getComponent("JumpToPath")

    return <section className={ showModels ? "models is-open" : "models"} ref={this.onLoadModels}>
      <h4 onClick={() => layoutActions.show(specPathBase, !showModels)}>
        <span>{isOAS3 ? "Schemas" : "Models" }</span>
        <svg width="20" height="20">
          <use xlinkHref={showModels ? "#large-arrow-down" : "#large-arrow"} />
        </svg>
      </h4>
      <Collapse isOpened={showModels}>
        {
          definitions.entrySeq().map(([rawName])=>{
            // SOLUTION: If you thought the above was dirty...well I have news for you dude/dudette
            const splittedName = rawName.split(':')
            const name = splittedName[splittedName.length - 1]
            let fullPath = [...specPathBase, name]
            if (splittedName.length > 1) {
              splittedName.splice(-1,1)
              fullPath = splittedName.concat(fullPath)
            }
            const specPath = Im.List(fullPath)

            let schemaValue = specSelectors.specResolvedSubtree(fullPath)
            let rawSchemaValue = specSelectors.specJson().getIn(fullPath)

            const schema = Map.isMap(schemaValue) ? schemaValue : Im.Map()
            const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map()

            const displayName = schema.get("title") || rawSchema.get("title") || name
            const isShown = layoutSelectors.isShown(fullPath, false)

            if( isShown && (schema.size === 0 && rawSchema.size > 0) ) {
              // Firing an action in a container render is not great,
              // but it works for now.
              this.props.specActions.requestResolvedSubtree(fullPath)
            }

            const content = <ModelWrapper name={ name }
              expandDepth={ defaultModelsExpandDepth }
              schema={ schema || Im.Map() }
              displayName={displayName}
              fullPath={fullPath}
              specPath={specPath}
              getComponent={ getComponent }
              specSelectors={ specSelectors }
              getConfigs = {getConfigs}
              layoutSelectors = {layoutSelectors}
              layoutActions = {layoutActions}
              includeReadOnly = {true}
              includeWriteOnly = {true}/>

            const title = <span className="model-box">
              <span className="model model-title">
                {displayName}
              </span>
            </span>

            return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }
                    data-name={name} ref={this.onLoadModel} >
              <span className="models-jump-to-path"><JumpToPath specPath={specPath} /></span>
              <ModelCollapse
                classes="model-box"
                collapsedContent={this.getCollapsedContent(name)}
                onToggle={this.handleToggle}
                title={title}
                displayName={displayName}
                modelName={name}
                specPath={specPath}
                layoutSelectors={layoutSelectors}
                layoutActions={layoutActions}
                hideSelfOnExpand={true}
                expanded={ defaultModelsExpandDepth > 0 && isShown }
                >{content}</ModelCollapse>
              </div>
          }).toArray()
        }
      </Collapse>
    </section>
  }
}


javiercbk avatar Aug 27 '20 19:08 javiercbk

Any news about that issue?

CidTori avatar Sep 10 '22 12:09 CidTori

Any update on this? still facing this issue

msalmankarim avatar Nov 06 '23 14:11 msalmankarim