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

Properly respect enums in OpenAPI/Swagger documentation #512

Open
ethndotsh opened this issue Mar 1, 2024 · 4 comments
Open

Properly respect enums in OpenAPI/Swagger documentation #512

ethndotsh opened this issue Mar 1, 2024 · 4 comments
Labels
enhancement New feature or request

Comments

@ethndotsh
Copy link

What is the problem this feature would solve?

Right now if a Typebox enum is passed into the response field on a route it will just be shown as "string" in Swagger UI or Scalar, and in the actual Swagger JSON it is currently represented as:

"type": {
  "anyOf": [
    {
      "const": "PUBLIC",
      "type": "string"
    },
    {
      "const": "PRIVATE",
      "type": "string"
    },
    {
      "const": "RESERVED",
      "type": "string"
    },
    {
      "const": "STUDIO",
      "type": "string"
    }
  ]
}

For it to be properly shown as an enum in Swagger UI and Scalar, this should be changed to generate as:

"type": {
  "type": "string",
  "enum": [
    "PUBLIC",
    "PRIVATE",
    "RESERVED",
    "STUDIO"
  ]
}

What is the feature you are proposing to solve the problem?

Adjusting how the values are generated for OpenAPI

What alternatives have you considered?

No response

@ethndotsh ethndotsh added the enhancement New feature or request label Mar 1, 2024
@ethndotsh
Copy link
Author

Could've sworn there was a solution here from the author of Typebox. Am I losing it or was it deleted?

@sinclairzx81
Copy link

sinclairzx81 commented Mar 6, 2024

@ethndotsh Hi, sorry, I had removed the previous snippet but meant to re-post after I had a deeper look at how Elysia was handling type registration. Here is the snippet again (slightly modified to support deriving the type keyword)

TypeScript Link Here

import { Kind, Static, TSchema, SchemaOptions, TypeRegistry } from '@sinclair/typebox'

// -------------------------------------------------------------------------------------
// TypeRegistry
// -------------------------------------------------------------------------------------
TypeRegistry.Set<TUnionEnum>('UnionEnum', (schema, value) => {
  return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value as never)
})
// -------------------------------------------------------------------------------------
// TUnionEnum
// -------------------------------------------------------------------------------------
export type TUnionValue = string | number

export interface TUnionEnum<T extends TUnionValue[] = []> extends TSchema {
  [Kind]: 'UnionEnum'
  static: T[number]
  enum: T
}
// -------------------------------------------------------------------------------------
// UnionEnum
// -------------------------------------------------------------------------------------
/** `[Elysia]` Creates a Union type with a enum array schema representation  */
export function UnionEnum<T extends TUnionValue[]>(values: [...T], options: SchemaOptions = {}) {
  const type = (
    values.every(value => typeof value === 'string') ? { type: 'string' } :
    values.every(value => typeof value === 'number') ? { type: 'number' } :
    {} // mixed string | number
  )
  return { ...options, [Kind]: 'UnionEnum', ...type, enum: values } as TUnionEnum<T>
}

// -------------------------------------------------------------------------------------
// Usage
// -------------------------------------------------------------------------------------

const T = UnionEnum(['A', 'B', 'C']) // const T = { type: 'string', enum: ['A', 'B', 'C'] }

type T = Static<typeof T>            // type T = 'A' | 'B' | 'C'

It should be possible to include the above as a module in your project; with Elysia able to use it so long as the type is registered. I had noted that there is potential here to include the type on Elysia t for the benefit of documentation (as quite a few OpenAPI / Swagger users ask for this enum representation specifically for documentation generation)

The above should provide a good reference point :)
Hope this helps!
S

@kravetsone
Copy link
Contributor

@ethndotsh Hi, sorry, I had removed the previous snippet but meant to re-post after I had a deeper look at how Elysia was handling type registration. Here is the snippet again (slightly modified to support deriving the type keyword)

TypeScript Link Here

import { Kind, Static, TSchema, SchemaOptions, TypeRegistry } from '@sinclair/typebox'

// -------------------------------------------------------------------------------------
// TypeRegistry
// -------------------------------------------------------------------------------------
TypeRegistry.Set<TUnionEnum>('UnionEnum', (schema, value) => {
  return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value as never)
})
// -------------------------------------------------------------------------------------
// TUnionEnum
// -------------------------------------------------------------------------------------
export type TUnionValue = string | number

export interface TUnionEnum<T extends TUnionValue[] = []> extends TSchema {
  [Kind]: 'UnionEnum'
  static: T[number]
  enum: T
}
// -------------------------------------------------------------------------------------
// UnionEnum
// -------------------------------------------------------------------------------------
/** `[Elysia]` Creates a Union type with a enum array schema representation  */
export function UnionEnum<T extends TUnionValue[]>(values: [...T], options: SchemaOptions = {}) {
  const type = (
    values.every(value => typeof value === 'string') ? { type: 'string' } :
    values.every(value => typeof value === 'number') ? { type: 'number' } :
    {} // mixed string | number
  )
  return { ...options, [Kind]: 'UnionEnum', ...type, enum: values } as TUnionEnum<T>
}

// -------------------------------------------------------------------------------------
// Usage
// -------------------------------------------------------------------------------------

const T = UnionEnum(['A', 'B', 'C']) // const T = { type: 'string', enum: ['A', 'B', 'C'] }

type T = Static<typeof T>            // type T = 'A' | 'B' | 'C'

It should be possible to include the above as a module in your project; with Elysia able to use it so long as the type is registered. I had noted that there is potential here to include the type on Elysia t for the benefit of documentation (as quite a few OpenAPI / Swagger users ask for this enum representation specifically for documentation generation)

The above should provide a good reference point :) Hope this helps! S

btw

Why you wont do it out of the box?

is it have some restriction?

@kravetsone
Copy link
Contributor

I add it to Elysia type-system in #808

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

No branches or pull requests

3 participants