Skip to content

circular dependency detected using swagger annotation with swc #3326

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

Open
3 of 4 tasks
andreasvh-conceto opened this issue Mar 1, 2025 · 1 comment
Open
3 of 4 tasks

Comments

@andreasvh-conceto
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

Whenever using swagger annotations like @ApiProperty() or @ApiPropertyOptional on an constified version of an enum in combination with swc builder, it throws a circular dependency error:

import { ApiPropertyOptional } from '@nestjs/swagger';

export const MyEnum = {
  FOO: 'FOO',
  BAR: 'BAR',
} as const;
export type MyEnum = (typeof MyEnum)[keyof typeof MyEnum];

export class MyDto {
  @ApiPropertyOptional() // will result in circular dependency issue, same for @ApiProperty
  @IsOptional()
  someEnum?: MyEnum;
}

After building with swc and starting the application and when accessing the swagger api it crashes with a circular dependency error. The error message does not hold enough information . I configured a project which will provoke this error in the steps to reproduce section. Here is the full error message:

[Nest] 69440 - 03/01/2025, 3:12:31 PM ERROR [ExceptionsHandler] Error: A circular dependency has been detected (property key: "FOO"). Please, make sure that each side of a bidirectional relationships are using lazy resolvers ("type: () => ClassType").
at SchemaObjectFactory.createNotBuiltInTypeReference (/Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:258:19)
at SchemaObjectFactory.createSchemaMetadata (/Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:375:25)
at SchemaObjectFactory.mergePropertyWithMetadata (/Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:186:21)
at /Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:317:43
at Array.forEach ()
at SchemaObjectFactory.createFromObjectLiteral (/Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:301:24)
at SchemaObjectFactory.createSchemaMetadata (/Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:350:50)
at SchemaObjectFactory.mergePropertyWithMetadata (/Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:186:21)
at /Users/andreas.vanhelden/dev/nestjs-swagger-circular-dependency/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:115:35

Current workaround(s) are:

  1. Use different names for the enum Type and the type itself:
import { ApiPropertyOptional } from '@nestjs/swagger';
// Here MyEnum is the const itself and the same name is also used for the type
export const MyEnum2 = {
  FOO: 'FOO',
  BAR: 'BAR',
} as const;
export type MyEnum2Type = (typeof MyEnum2)[keyof typeof MyEnum2];
export class MyDto {
  @ApiPropertyOptional()
  @IsOptional()
  someEnum?: MyEnum2Type;
}
  1. Define the enum explicitly in the ApiPropertyOptional Annotation parameter:
import { ApiPropertyOptional } from '@nestjs/swagger';
// Here MyEnum is the const itself and the same name is also used for the type
export const MyEnum = {
  FOO: 'FOO',
  BAR: 'BAR',
} as const;
export type MyEnum = (typeof MyEnum)[keyof typeof MyEnum];
export class MyDto {
  @ApiPropertyOptional({ enum: MyEnum ) // or @ApiPropertyOptional({ enum: () => MyEnum })
  @IsOptional()
  someEnum?: MyEnum;
}

This issue was really hard to detect and it occured only after building and starting the application. There is just a small hint to the enum like ...Error: A circular dependency has been detected (property key: "FOO")...

It is also dangerous since this error only occurs after deploying and starting the application. On our side it was identified after the deployment on qa environment failed.

Minimum reproduction code

https://github.com/andreasvh-conceto/nestjs-swagger-circular-dependency

Steps to reproduce

  1. git clone [email protected]:andreasvh-conceto/nestjs-swagger-circular-dependency.git
  2. cd nestjs-swagger-circular-dependency
  3. npm i
  4. Go to the terminal and do npm run test:e2e
  5. Scroll up the failing app.e2e-spec.ts look at the circular dependency error.

Expected behavior

Here are several alternatives i would expect (from my favorite solution to the one i would i would at least expect):

  1. Swagger will somehow resolve that on its own. The error does not occur anymore if swagger Annotations are correctly used.
  2. The error occurs at development time, not after building the project with swc or can be at least easily been tested. The error message is improved and is giving more hints like mentioning the related Dto File, the concrete enum.
  3. The error message is improved and is giving more hints like mentioning the related Dto File and the concrete enum.

Currently we wrote an own test like in the steps to reproduce section, which will verify if Swagger could correctly load the Open Api spec. Maybe there are better ways.

Thanks!

Package version

11.0.5

NestJS version

11.0.1

Node.js version

v20.18.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

Only occurs building with swc and using swagger.

@drewish
Copy link
Contributor

drewish commented Apr 13, 2025

We've been working around this by just passing in the enum:

export class MyDto {
  @ApiPropertyOptional({ enum: MyEnum )
  @IsOptional()
  someEnum?: MyEnum;
}

And created an eslint rule to flag it... but the really annoying thing is that the only way to figure out which enum is to blame is to use the debugger. If we passed the property name down the stack to where this check happens we could have a much clearer error, or I guess we could catch the error higher up the stack where we have it and could rethrow with a clearer message.

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

No branches or pull requests

2 participants