swagger icon indicating copy to clipboard operation
swagger copied to clipboard

Numeric enum values not displayed in Swagger

Open petrzjunior opened this issue 3 years ago • 3 comments
trafficstars

Is there an existing issue for this?

  • [X] I have searched the existing issues

Current behavior

Using enums with number values results in incorrect Swagger examples. For example

export enum Size {
  SMALL = 1,
  BIG = 2,
}

export class TshirtDto {
  @IsEnum(Size)
  size: Size;
}

results in the following example:

{
  "size": "SMALL"
}

which is wrong and invalid.

Minimum reproduction code

https://github.com/petrzjunior/nestjs-swagger-enum

Steps to reproduce

  1. Clone the above repo
  2. pnpm i
  3. pnpm start:dev
  4. Open http://localhost:3000/api/#/default/AppController_getHello
  5. See the example value for the only endpoint

Expected behavior

I expect Swagger to show the actual numeric value which will pass the validation. In this case

{
  "size": 1
}

Package version

5.2.1

NestJS version

8.0.0

Node.js version

16.9.1

In which operating systems have you tested?

  • [ ] macOS
  • [ ] Windows
  • [X] Linux

Other

I suspect the culprit is this loop over Enum values: https://github.com/nestjs/swagger/blob/491b168cbff3003191e55ee96e77e69d8c1deb66/lib/utils/enum.utils.ts#L17

In JS, number keys are iterated before the string ones. The string values (from reverse mapping) are added to the list first, later ignoring the correct numeric keys.

node> for (const key in {a: 1, 1: 'a'}) { console.log(key) }
1
a

Screenshot

petrzjunior avatar Apr 10 '22 15:04 petrzjunior

but if we enable the builtin auto-validation, since we're using @IsEnum(Size), requesting with
{ "size": 1 } will give us the following validation error:

{
  "statusCode": 400,
  "message": [
    "size must be a valid enum value"
  ],
  "error": "Bad Request"
}

I didn't follow what's the issue here :thinking:


EDIT:

nvm. I got it now. The reponse object will always have the value of an entry from that enum, not the key

micalevisk avatar Apr 12 '22 18:04 micalevisk

@micalevisk yeah not only the response, but also the request body.

Sufiane avatar Apr 13 '22 08:04 Sufiane

No workaround or fix yet?

terrasoff avatar Aug 17 '22 12:08 terrasoff

I use this workaround for numeric enums:

Workaround Source
// options for custom decorator
type ApiPropertyEnumOptions<T extends { [key: string]: any; }> = {

  /**
   * Indicates if multiple enum values can be used as the same time (thus being an array).
   * Defaults to `false`.
   */
  isArray?: boolean;

  /**
   * The enum that should be represented.
   */
  enum: T;

  /**
   * A unique name for the enum. All enums that are the same should use the same enumName.
   * It's sadly not possible to autogenertate since enums are not part of the JS ecosystem.
   */
  enumName: string;

  /**
   * The type of the enum values. Defaults to `'number'`.
   */
  type?: 'string' | 'number';

};

// util method that retrieves only "real" values from an enum
function getEnumValues<T extends { [key: string]: any; }>(value: T): T[keyof T][] {
  return (Object.keys(value) as (keyof T)[])
    .filter((key): key is keyof T => isNaN(parseInt(key.toString(), 10)))
    .map((key) => value[key]);
}

// create a custom decorator for enum values
function ApiPropertyEnum<T extends { [key: string]: any; }>(options: ApiPropertyEnumOptions<T>): PropertyDecorator {
  const {
    enum: enumRef,
    enumName,
    isArray = false,
    type = 'number',
  } = options;

  const enumValues = getEnumValues(enumRef);
  const exampleValue = enumValues[0];

  return applyDecorators(
    ApiProperty({
      enum: enumValues ,
      enumName,
      isArray,
      type,
      example: isArray ? [exampleValue] : exampleValue,
    }),
  );
}
Usage example
enum MyEnum {
  FOO = 1,
  BAR = 2, 
  BAZ = 3,
}

class MyDto {
  @ApiPropertyEnum({
    enum: MyEnum,
    enumName: 'MyEnum',
  })
  value: MyEnum;
}

@Controller()
class MyController {
  @Post()
  getBody(
    @Body()
    body: MyDto,
  ) {
    return body;
  }
}

If MyController is now passed into a module that is correctly set up in a nest factory (including Swagger), you will get a numeric enum component schema in your Swagger UI.

image

Just in case someone needs it.

I also provided a repository where you can try it for yourself to verify that it works.

xDivisionByZerox avatar Nov 04 '22 15:11 xDivisionByZerox

How can I achieve this effect? Use the Key of the enumeration as the display text of the Option and the Value as the value of the Option.

enum Role {
  Admin = 1,
  User = 2,
}

In Swagger UI:

image

myesn avatar Jan 30 '23 09:01 myesn

How can I achieve this effect?

The Swagger specification does not support this behavior. You might be interested in this comment from the swagger-ui repository. Thats the closed you can get without a custom parse/builder.

xDivisionByZerox avatar Jan 30 '23 11:01 xDivisionByZerox

Let's track this here https://github.com/nestjs/swagger/pull/1898

kamilmysliwiec avatar Feb 06 '23 10:02 kamilmysliwiec