Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swagger-UI fails to resolve reference when class passed as @ResponseSchema and @Body #82

Open
rdfedor opened this issue May 22, 2021 · 11 comments

Comments

@rdfedor
Copy link

rdfedor commented May 22, 2021

Have a project set up using the following structure,

./controllers/auth.ts
./validator/auth.ts
./main.ts

Here's a rough implementation of how the files are implemented,

./main.ts

import { routingControllersToSpec } from 'routing-controllers-openapi'
import { getMetadataArgsStorage } from 'routing-controllers'
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'
import { defaultMetadataStorage } from 'class-transformer/cjs/storage'
import * as swaggerUi from 'swagger-ui-express'
import { useExpressServer } from 'routing-controllers'
import * as http from 'http'
import * as express from 'express'

const app = express()

const schemas = validationMetadatasToSchemas({
  classTransformerMetadataStorage: defaultMetadataStorage,
  refPointerPrefix: '#/components/schemas/',
})

app.use(
      '/api-docs',
      swaggerUi.serve,
      swaggerUi.setup(
        routingControllersToSpec(
          getMetadataArgsStorage(),
          {
            controllers: importClassesFromDirectories(
              routerControllerOptions.controllers,
            ),
          },
          {
            openapi: '3.0.0',
            schemas,
          },
        ),
      ),
    )

useExpressServer(app, {
  controllers: [`${__dirname}/controllers/**/*.ts`]
})

server.listen(8080, () => {
  console.log(`Server started on port 8080`)
})

./validator/auth.ts

import { IsNotEmpty, IsEmail, MaxLength } from 'class-validator'

export class AuthenticateUserRequest {
  @IsNotEmpty()
  @IsEmail()
  @MaxLength(254)
  public email: string

  @IsNotEmpty()
  public password: string
}

export class LoginToken {
  accessToken: string
  refreshToken: string
  maxAge?: string | number
  iat?: number
  roles?: string[]
  userUuid: string
  email: string
  firstName?: string
  lastName?: string
  verifiedEmail?: boolean
}

./controllers/auth.ts

import {
  JsonController,
  Post,
  ContentType,
  Body,
  ForbiddenError,
} from 'routing-controllers'
import { AuthenticateUserRequest, LoginToken } from '../validator/auth'

@JsonController('/api/auth')
export default class AuthController {
  @Post('/')
  @ContentType('application/json')
  @ResponseSchema(LoginToken)
  public async authenticateUser(
    @Body() req: AuthenticateUserRequest,
  ): Promise<LoginToken> {
    try {
      return await authService.processAuthenticateUserRequest(req)
    } catch (err) {
      if (
        err instanceof AccessDeniedError ||
        err instanceof NotFoundError ||
        (err instanceof Array &&
          err.filter(er => er instanceof ValidationError).length > 0)
      ) {
        throw new ForbiddenError('Invalid email and/or password.')
      }
      throw err
    }
  }
}

However when I try to expand the authenticateUser section of the request in swagger-ui I get the following error.

image

@rdfedor
Copy link
Author

rdfedor commented May 22, 2021

I was able to fix part of the problem by moving the useExpressServer to before the app.use('/api-docs' line. It was able to get rid of the AuthenticateUserRequest error, but the LoginToken error still exists. Seems to only error out on classes that don't use class-validator.

@BlackRider97
Copy link

I am also getting schemas: {} even while using @ResponsesSchema

@judos
Copy link

judos commented Dec 7, 2021

Same issue, workaround I use now is to annotate every field with an appropriate annotation e.g. IsNotEmpty() for strings.
The dependencies we use are:

"dependencies": {
    "@google-cloud/firestore": "4.15.1",
    "bcrypt": "5.0.1",
    "body-parser": "1.19.0",
    "chalk": "4.1.2",
    "class-transformer": "^0.3.1",
    "class-validator": "^0.12.2",
    "class-validator-jsonschema": "2.2.0",
    "console-stamp": "3.0.3",
    "cors": "2.8.5",
    "express": "4.17.1",
    "express-jwt": "6.1.0",
    "jsonwebtoken": "8.5.1",
    "multer": "1.4.3",
    "routing-controllers": "0.9.0",
    "routing-controllers-openapi": "3.1.0",
    "swagger-ui-express": "4.1.3"
  },

@thebrokenbar
Copy link

@judos workaround works, thanks

Any permanent solution planned on this?

@JamesDAdams
Copy link

Same error :/

@JamesDAdams
Copy link

@oleksandr-andrushchenko
Copy link

oleksandr-andrushchenko commented Jan 18, 2024

There is not well documented property type schemas

I got
Screenshot from 2024-01-18 15-19-23

but then I just ignored it and all works well, final working variant

const { defaultMetadataStorage } = require('class-transformer/cjs/storage');
        const spec = routingControllersToSpec(getMetadataArgsStorage(), routingControllersOptions, {
            components: {
                // @ts-ignore
                schemas: validationMetadatasToSchemas({
                    classTransformerMetadataStorage: defaultMetadataStorage,
                    refPointerPrefix: '#/components/schemas/',
                })
            }
        });

deps

   "dependencies": {
      "bcrypt": "^5.1.1",
      "body-parser": "^1.20.2",
      "class-transformer": "^0.5.1",
      "class-validator": "^0.14.0",
      "class-validator-jsonschema": "^5.0.0",
      "compression": "^1.7.4",
      "event-dispatch": "^0.4.1",
      "express": "^4.18.2",
      "express-basic-auth": "^1.2.1",
      "joi": "^17.11.0",
      "jsonwebtoken": "^9.0.2",
      "morgan": "^1.10.0",
      "multer": "^1.4.5-lts.1",
      "reflect-metadata": "^0.1.14",
      "routing-controllers": "^0.10.4",
      "routing-controllers-openapi": "^4.0.0",
      "swagger-ui-express": "^5.0.0",
      "typedi": "^0.10.0",
      "winston": "^3.11.0"
   }

also, this lib relies on class-validator, so classes without this lib's annotation will be ignored

@ernestyouniverse
Copy link

@oleksandr-andrushchenko Could you please show your whole solution? For me, routingControllersToSpec() throws generateSpec.ts:341 )[index], TypeError: Cannot read properties of undefined (reading '0'), if i use @Body() of routing-controllers. All steps i made, what the documentation said. Im using Node 20, TypeScript, ESM, and a Koa2 server.

@oleksandr-andrushchenko
Copy link

oleksandr-andrushchenko commented Feb 5, 2024

@ernestyouniverse
@ALL
https://github.com/oleksandr-andrushchenko/ExamMeApi/blob/main/src/application.ts

if it helps - give me a star, thank you!

@ernestyouniverse
Copy link

@oleksandr-andrushchenko thank You for Your help! :) Indeed with a commonJS setup it works smoothly. Sadly for ESM it doesn't.

@Fortur
Copy link

Fortur commented Sep 13, 2024

Same problem. Why @ResponseSchema() does not working? I look in generated schema and see this:

{"components":{"schemas":{}}

But reference is exists:

{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}},"description":""}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants