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

Enums are parsed to invalid Typescript #884

Closed
vonkanehoffen opened this issue Jun 28, 2023 · 17 comments · Fixed by #1341
Closed

Enums are parsed to invalid Typescript #884

vonkanehoffen opened this issue Jun 28, 2023 · 17 comments · Fixed by #1341
Assignees
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers
Milestone

Comments

@vonkanehoffen
Copy link

What are the steps to reproduce this issue?

  1. npm i this minimal example repo: https://github.com/vonkanehoffen/orval-enum-bug-repro
  2. Run npm run codegen

What happens?

If you look in generated/model/loginStatus.ts you'll notice Orval has generated invalid syntax:

export const LoginStatus = {
  '0_-_Unknown': 0 - Unknown,
  '1_-_Success': 1 - Success,
  '2_-_TwoFactorRequiredYubikey': 2 - TwoFactorRequiredYubikey,
  '3_-_TwoFactorRequiredGoogleAuthenticator': 3 - TwoFactorRequiredGoogleAuthenticator,
  '4_-_InvalidEmailOrPassword': 4 - InvalidEmailOrPassword,
  '5_-_InvalidToken': 5 - InvalidToken,
  '6_-_InvalidRefreshToken': 6 - InvalidRefreshToken,
  '7_-_InvalidTwoFactorCode': 7 - InvalidTwoFactorCode,
} as const;

This is from a valid schema:

components:
  schemas:
    LoginStatus:
      enum:
        - 0 - Unknown
        - 1 - Success
        - 2 - TwoFactorRequiredYubikey
        - 3 - TwoFactorRequiredGoogleAuthenticator
        - 4 - InvalidEmailOrPassword
        - 5 - InvalidToken
        - 6 - InvalidRefreshToken
        - 7 - InvalidTwoFactorCode
      type: integer
      format: int32

(this is part of a schema originally generated by Swashbockle in .Net)

What were you expecting to happen?

I'd expect the generation to be something like:

export const LoginStatus = {
  '0_-_Unknown': 0 ,
  '1_-_Success': 1,
  '2_-_TwoFactorRequiredYubikey': 2,
  '3_-_TwoFactorRequiredGoogleAuthenticator': 3,
  '4_-_InvalidEmailOrPassword': 4,
  '5_-_InvalidToken': 5,
  '6_-_InvalidRefreshToken': 6,
  '7_-_InvalidTwoFactorCode': 7,
} as const;

Any logs, error output, etc?

Any other comments?

The OpenAPI spec is a bit vague around enum from what I could find. The example passes validation but is there some other way it should look to fix this?

What versions are you using?

Operating System: Masc OS Ventura
Package Version: 6.16.0
Browser Version: N/A

@NimmLor
Copy link

NimmLor commented Jun 28, 2023

A workaround would be to use the x-enumNames property in your openapi spec to overwrite the enum keys.

You might want to use the transformer feature to add this to your generated spec.

example:

{
      "ChainingOperator": {
        "description": "The operator used to chain condition",
        "enum": ["&&", "||"],
        "type": "string",
        "x-enumNames": ["AND", "OR"]
      }
}

generates:

export const ChainingOperator = {
  AND: '&&',
  OR: '||',
} as const

@anymaniax This should probably added to the docs

vonkanehoffen pushed a commit to vonkanehoffen/orval-enum-bug-repro that referenced this issue Jun 28, 2023
@vonkanehoffen
Copy link
Author

Ah thanks so much @NimmLor ! That works great. I had no idea that x-enumNames was a thing 😅

For anyone else needing this, here's the fix with the regenerated code applied to my demo repo:

vonkanehoffen/orval-enum-bug-repro@master...fix/enum

@melloware melloware added documentation Improvements or additions to documentation good first issue Good for newcomers labels Nov 3, 2023
@melloware melloware assigned melloware and unassigned melloware Nov 3, 2023
@melloware melloware pinned this issue Nov 14, 2023
@zernie
Copy link
Contributor

zernie commented Nov 21, 2023

this is also happening to me

@melloware
Copy link
Collaborator

@dsthode does your latest fix help with this issue?

@zernie
Copy link
Contributor

zernie commented Nov 23, 2023

@melloware I'm not sure which fix you are talking about, has it been released?

@melloware
Copy link
Collaborator

Its not released yet but @dsthode just fixed this: #1058

@zernie
Copy link
Contributor

zernie commented Nov 23, 2023

@melloware @dsthode incredible, thank you lots

@melloware
Copy link
Collaborator

@zernie maybe you can verify if its fixed or not when 6.21.0 is released and report back?

@zernie
Copy link
Contributor

zernie commented Nov 23, 2023

@melloware sure, ping me when it's ready

@melloware
Copy link
Collaborator

6.21.0 is out if you want to try the new setting for enums.

@zernie
Copy link
Contributor

zernie commented Nov 28, 2023

@melloware unfortunately, I have a similar problem with a GetApiAdminAccountsAccountIdPlacesOrderField , GetApiAdminAccountsAccountIdPlacesOrderType consts:

/**
 * Generated by orval v6.21.0 🍺
 * Do not edit manually.
 * Amtta Admin
 * OpenAPI spec version: api
 */

export type GetApiAdminAccountsAccountIdPlacesParams = {
/**
 * Количество возвращаемых сущностей
 */
limit: number;
/**
 * Количество пропущенных сущностей
 */
offset: number;
/**
 * Поле по которому сортировать. Доступные поля для сортировки - accountId, juridicalPersonId, title, phone, rating, workStatus, takeawayDiscountPercent, promoteStatus, promotedTo, createdAt, updatedAt, id
 */
orderField?: typeof GetApiAdminAccountsAccountIdPlacesOrderField[keyof typeof GetApiAdminAccountsAccountIdPlacesOrderField] ;

// eslint-disable-next-line @typescript-eslint/no-redeclare
export const GetApiAdminAccountsAccountIdPlacesOrderField = {  id: 'id',
  accountId: 'accountId',
  juridicalPersonId: 'juridicalPersonId',
  title: 'title',
  phone: 'phone',
  rating: 'rating',
  workStatus: 'workStatus',
  promoteStatus: 'promoteStatus',
  promotedTo: 'promotedTo',
  takeawayDiscountPercent: 'takeawayDiscountPercent',
  updatedAt: 'updatedAt',
  createdAt: 'createdAt',
} as const;
/**
 * Тип сортировки ASC или DESC
 */
orderType?: typeof GetApiAdminAccountsAccountIdPlacesOrderType[keyof typeof GetApiAdminAccountsAccountIdPlacesOrderType] ;

// eslint-disable-next-line @typescript-eslint/no-redeclare
export const GetApiAdminAccountsAccountIdPlacesOrderType = {  ASC: 'ASC',
  DESC: 'DESC',
} as const;
};

Relevant swagger:

"get": {
        "tags": [
          "Places"
        ],
        "description": "Получить список Places у аккаунта",
        "parameters": [
          {
            "schema": {
              "minimum": 1,
              "maximum": 100,
              "type": "integer"
            },
            "in": "query",
            "name": "limit",
            "required": true,
            "description": "Количество возвращаемых сущностей"
          },
          {
            "schema": {
              "minimum": 0,
              "maximum": 9007199254740991,
              "type": "integer"
            },
            "in": "query",
            "name": "offset",
            "required": true,
            "description": "Количество пропущенных сущностей"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "id"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "accountId"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "juridicalPersonId"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "title"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "phone"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "rating"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "workStatus"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "promoteStatus"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "promotedTo"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "takeawayDiscountPercent"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "updatedAt"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "createdAt"
                  ]
                }
              ]
            },
            "in": "query",
            "name": "orderField",
            "required": false,
            "description": "Поле по которому сортировать. Доступные поля для сортировки - accountId, juridicalPersonId, title, phone, rating, workStatus, takeawayDiscountPercent, promoteStatus, promotedTo, createdAt, updatedAt, id"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "ASC"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "DESC"
                  ]
                }
              ]
            },
            "in": "query",
            "name": "orderType",
            "required": false,
            "description": "Тип сортировки ASC или DESC"
          },
          {
            "schema": {
              "type": "integer"
            },
            "in": "path",
            "name": "accountId",
            "required": true
          }
        ],
        "security": [
          {
            "access_token": []
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "minimum": 0,
                      "type": "integer"
                    },
                    "limit": {
                      "minimum": 1,
                      "maximum": 100,
                      "description": "Количество возвращаемых сущностей",
                      "type": "integer"
                    },
                    "offset": {
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Количество пропущенных сущностей",
                      "type": "integer"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "accountId": {
                            "type": "integer"
                          },
                          "juridicalPersonId": {
                            "type": "integer"
                          },
                          "title": {
                            "minLength": 1,
                            "maxLength": 100,
                            "description": "Название",
                            "type": "string"
                          },
                          "phone": {
                            "pattern": "^7[0-9]{10}$",
                            "type": "string"
                          },
                          "cardImageUrl": {
                            "minLength": 1,
                            "maxLength": 1024,
                            "description": "Url карточки",
                            "type": "string"
                          },
                          "backgroundImageUrl": {
                            "description": "Url бэкграунда",
                            "anyOf": [
                              {
                                "minLength": 1,
                                "maxLength": 1024,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "rating": {
                            "description": "Рейтинг",
                            "anyOf": [
                              {
                                "pattern": "^[0-5]{1}.[0-9]{2}$",
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "workStatus": {
                            "description": "Статус работы",
                            "anyOf": [
                              {
                                "type": "string",
                                "enum": [
                                  "closed"
                                ]
                              },
                              {
                                "type": "string",
                                "enum": [
                                  "temp-closed"
                                ]
                              },
                              {
                                "type": "string",
                                "enum": [
                                  "open"
                                ]
                              }
                            ]
                          },
                          "promoteStatus": {
                            "description": "Статус продвижения",
                            "anyOf": [
                              {
                                "type": "string",
                                "enum": [
                                  "new"
                                ]
                              },
                              {
                                "type": "string",
                                "enum": [
                                  "none"
                                ]
                              }
                            ]
                          },
                          "promotedTo": {
                            "description": "До какого числа действует продвижение. Формат - ISO 8601 date string",
                            "anyOf": [
                              {
                                "format": "date-time",
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "availableDeliveryTypes": {
                            "minItems": 1,
                            "type": "array",
                            "items": {
                              "description": "Тип доставки",
                              "anyOf": [
                                {
                                  "type": "string",
                                  "enum": [
                                    "takeaway"
                                  ]
                                },
                                {
                                  "type": "string",
                                  "enum": [
                                    "inhouse"
                                  ]
                                }
                              ]
                            }
                          },
                          "availablePaymentTypes": {
                            "minItems": 1,
                            "type": "array",
                            "items": {
                              "description": "Тип оплаты",
                              "anyOf": [
                                {
                                  "type": "string",
                                  "enum": [
                                    "card-online"
                                  ]
                                },
                                {
                                  "type": "string",
                                  "enum": [
                                    "card-courier"
                                  ]
                                },
                                {
                                  "type": "string",
                                  "enum": [
                                    "mobile-transfer"
                                  ]
                                },
                                {
                                  "type": "string",
                                  "enum": [
                                    "cash-courier"
                                  ]
                                }
                              ]
                            }
                          },
                          "takeawayDiscountPercent": {
                            "minimum": 0,
                            "maximum": 50,
                            "description": "Скидка при самовывозе, в процентах",
                            "type": "integer"
                          },
                          "tags": {
                            "maxItems": 20,
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "integer"
                                },
                                "name": {
                                  "minLength": 1,
                                  "maxLength": 20,
                                  "type": "string"
                                }
                              },
                              "required": [
                                "id",
                                "name"
                              ]
                            }
                          },
                          "telegramChatId": {
                            "description": "ИД телеграм чата для нотификаций",
                            "anyOf": [
                              {
                                "minLength": 1,
                                "maxLength": 256,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "address": {
                            "minLength": 1,
                            "maxLength": 1024,
                            "description": "Адрес в виде строки",
                            "type": "string"
                          },
                          "addressPoint": {
                            "type": "object",
                            "properties": {
                              "longitude": {
                                "minimum": -180,
                                "maximum": 180,
                                "description": "Долгота",
                                "type": "number"
                              },
                              "latitude": {
                                "minimum": -90,
                                "maximum": 90,
                                "description": "Широта",
                                "type": "number"
                              }
                            },
                            "required": [
                              "longitude",
                              "latitude"
                            ]
                          },
                          "addressFlat": {
                            "description": "Квартира/офис",
                            "anyOf": [
                              {
                                "minLength": 1,
                                "maxLength": 256,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "addressDoorcode": {
                            "description": "Домофон",
                            "anyOf": [
                              {
                                "minLength": 1,
                                "maxLength": 256,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "addressEntrance": {
                            "description": "Подъезд",
                            "anyOf": [
                              {
                                "minLength": 1,
                                "maxLength": 256,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "addressFloor": {
                            "description": "Этаж",
                            "anyOf": [
                              {
                                "minLength": 1,
                                "maxLength": 256,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "addressAdditionalInformation": {
                            "description": "Доп. информация",
                            "anyOf": [
                              {
                                "minLength": 1,
                                "maxLength": 256,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "updatedAt": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "createdAt": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "accountId",
                          "juridicalPersonId",
                          "title",
                          "phone",
                          "cardImageUrl",
                          "backgroundImageUrl",
                          "rating",
                          "workStatus",
                          "promoteStatus",
                          "promotedTo",
                          "availableDeliveryTypes",
                          "availablePaymentTypes",
                          "takeawayDiscountPercent",
                          "tags",
                          "telegramChatId",
                          "address",
                          "addressPoint",
                          "addressFlat",
                          "addressDoorcode",
                          "addressEntrance",
                          "addressFloor",
                          "addressAdditionalInformation",
                          "updatedAt",
                          "createdAt"
                        ]
                      }
                    }
                  },
                  "required": [
                    "count",
                    "limit",
                    "offset",
                    "data"
                  ]
                },
                "example": {
                  "count": 123,
                  "limit": 1,
                  "offset": 0,
                  "data": [
                    {
                      "id": 3456,
                      "accountId": 213,
                      "workStatus": "closed",
                      "promoteStatus": "none",
                      "promotedTo": null,
                      "rating": null,
                      "juridicalPersonId": 434,
                      "title": "Amtta",
                      "phone": "79997775544",
                      "cardImageUrl": " /static/images/d0af2028-62e2-4abb-bfca-c021e83bda47",
                      "backgroundImageUrl": null,
                      "availableDeliveryTypes": [
                        "takeaway",
                        "inhouse"
                      ],
                      "availablePaymentTypes": [
                        "cash-courier",
                        "mobile-transfer",
                        "card-online",
                        "card-courier"
                      ],
                      "takeawayDiscountPercent": 10,
                      "address": "Респ. Калмыкия, г. Элиста, проезд 11-й, д. 8",
                      "addressPoint": {
                        "longitude": 44.255008,
                        "latitude": 46.291193
                      },
                      "addressFlat": "33",
                      "addressDoorcode": "33",
                      "addressEntrance": null,
                      "addressFloor": null,
                      "addressAdditionalInformation": "Вход с торца",
                      "tags": [
                        {
                          "id": 1,
                          "name": "Калмыцкая кухня"
                        },
                        {
                          "id": 2,
                          "name": "Суши"
                        }
                      ],
                      "updatedAt": "2023-05-04T13:21:59.771Z",
                      "createdAt": "2023-05-04T13:21:59.771Z",
                      "telegramChatId": "-4051259643"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errorCode": {
                      "type": "string",
                      "enum": [
                        "BAD_REQUEST_ERROR"
                      ]
                    },
                    "statusCode": {
                      "type": "number",
                      "enum": [
                        400
                      ]
                    },
                    "details": {
                      "type": "array",
                      "items": {
                        "type": "array",
                        "items": [
                          {
                            "description": "Указатель JSON на местоположение в экземпляре данных (например, `\"body/1/subProp\"`)",
                            "type": "string"
                          },
                          {
                            "description": "Сообщение об ошибке",
                            "type": "string"
                          }
                        ],
                        "additionalItems": false,
                        "minItems": 2,
                        "maxItems": 2
                      }
                    },
                    "requestId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "errorCode",
                    "statusCode",
                    "details",
                    "requestId",
                    "message"
                  ]
                },
                "example": {
                  "errorCode": "BAD_REQUEST_ERROR",
                  "statusCode": 400,
                  "details": [
                    [
                      "body",
                      "must have have required property id"
                    ],
                    [
                      "querystring",
                      "property limit must be greater than 0"
                    ]
                  ],
                  "requestId": "d0af2028-62e2-4abb-bfca-c021e83bda47",
                  "message": "Человекочитаемое сообщение об ошибке"
                }
              }
            }
          },
          "401": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errorCode": {
                      "type": "string",
                      "enum": [
                        "UNAUTHORIZED_ERROR"
                      ]
                    },
                    "statusCode": {
                      "type": "number",
                      "enum": [
                        401
                      ]
                    },
                    "details": {
                      "type": "null"
                    },
                    "requestId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "errorCode",
                    "statusCode",
                    "details",
                    "requestId",
                    "message"
                  ]
                },
                "example": {
                  "errorCode": "UNAUTHORIZED_ERROR",
                  "statusCode": 401,
                  "details": null,
                  "requestId": "d0af2028-62e2-4abb-bfca-c021e83bda47",
                  "message": "Человекочитаемое сообщение об ошибке"
                }
              }
            }
          },
          "403": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errorCode": {
                      "type": "string",
                      "enum": [
                        "FORBIDDEN_ERROR"
                      ]
                    },
                    "statusCode": {
                      "type": "number",
                      "enum": [
                        403
                      ]
                    },
                    "details": {
                      "type": "null"
                    },
                    "requestId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "errorCode",
                    "statusCode",
                    "details",
                    "requestId",
                    "message"
                  ]
                },
                "example": {
                  "errorCode": "FORBIDDEN_ERROR",
                  "statusCode": 403,
                  "details": null,
                  "requestId": "d0af2028-62e2-4abb-bfca-c021e83bda47",
                  "message": "Человекочитаемое сообщение об ошибке"
                }
              }
            }
          },
          "500": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errorCode": {
                      "type": "string",
                      "enum": [
                        "INTERNAL_SERVER_ERROR"
                      ]
                    },
                    "statusCode": {
                      "type": "number",
                      "enum": [
                        500
                      ]
                    },
                    "details": {
                      "type": "null"
                    },
                    "requestId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "errorCode",
                    "statusCode",
                    "details",
                    "requestId",
                    "message"
                  ]
                },
                "example": {
                  "errorCode": "INTERNAL_SERVER_ERROR",
                  "statusCode": 500,
                  "details": null,
                  "requestId": "d0af2028-62e2-4abb-bfca-c021e83bda47",
                  "message": "Человекочитаемое сообщение об ошибке"
                }
              }
            }
          }
        }
      },

@melloware
Copy link
Collaborator

OK thanks for verifying will leave this bug open.

@zernie
Copy link
Contributor

zernie commented Feb 14, 2024

@melloware hey, any updates?:)

@melloware
Copy link
Collaborator

I have not looked into this further. PR's are welcome though!

@zernie
Copy link
Contributor

zernie commented Feb 14, 2024

@melloware I've no idea where to begin even. any tips?

@melloware
Copy link
Collaborator

well what I do usually grep the source code for what I am looking for like "enum" and work my way backwards to the code that is generating the enums. Then maybe you can figure out what is wrong?

@arthurfiorette
Copy link
Contributor

arthurfiorette commented Apr 30, 2024

@melloware, the problem seems to happen here:

https://github.com/anymaniax/orval/blob/6d41605a232e6efddecad64f918b2905b443e7fa/packages/core/src/getters/scalar.ts#L33

Json schemas similar to { anyOf: [] } makes item.type return undefined which the getScalar function treats as a normal object, generating a export type definition instead of a object field type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants