diff --git a/src/type-system/index.ts b/src/type-system/index.ts index 9a9eaea9..a5a3a082 100644 --- a/src/type-system/index.ts +++ b/src/type-system/index.ts @@ -20,7 +20,8 @@ import type { JavaScriptTypeBuilder, StringOptions, TUnsafe, - Uint8ArrayOptions + Uint8ArrayOptions, + TEnum } from '@sinclair/typebox' import { @@ -40,7 +41,8 @@ import { TForm, TUnionEnum, ElysiaTransformDecodeBuilder, - TArrayBuffer + TArrayBuffer, + AssertNumericEnum } from './types' import { ELYSIA_FORM_DATA, form } from '../utils' @@ -151,6 +153,23 @@ export const ElysiaType = { .Encode((value) => value) as any as TNumber }, + NumericEnum>(item: T, property?: SchemaOptions) { + const schema = Type.Enum(item, property) + const compiler = compile(schema) + + return t + .Transform( + t.Union([t.String({ format: 'numeric' }), t.Number()], property) + ) + .Decode((value) => { + const number = +value + if (isNaN(number)) throw compiler.Error(number) + if (!compiler.Check(number)) throw compiler.Error(number) + return number + }) + .Encode((value) => value) as any as TEnum + }, + Integer: (property?: IntegerOptions): TInteger => { const schema = Type.Integer(property) const compiler = compile(schema) @@ -607,6 +626,7 @@ t.ObjectString = ElysiaType.ObjectString t.ArrayString = ElysiaType.ArrayString t.ArrayQuery = ElysiaType.ArrayQuery t.Numeric = ElysiaType.Numeric +t.NumericEnum = ElysiaType.NumericEnum t.Integer = ElysiaType.Integer t.File = (arg) => { diff --git a/src/type-system/types.ts b/src/type-system/types.ts index 03f4fbe9..37c1e0b4 100644 --- a/src/type-system/types.ts +++ b/src/type-system/types.ts @@ -238,3 +238,13 @@ export interface TTransform< [TransformKind]: TransformOptions [key: string]: any } + +export type AssertNumericEnum> = { + [K in keyof T]: K extends number + ? string + : K extends `${number}` + ? string + : K extends string + ? number + : never +} diff --git a/test/aot/has-transform.test.ts b/test/aot/has-transform.test.ts index aa36e916..9ac42a49 100644 --- a/test/aot/has-transform.test.ts +++ b/test/aot/has-transform.test.ts @@ -98,6 +98,15 @@ describe('Has Transform', () => { expect(hasTransform(schema)).toBe(true) }) + it('Found t.NumericEnum', () => { + const schema = t.Object({ + gender: t.NumericEnum({ UNKNOWN: 0, MALE: 1, FEMALE: 2 }), + liyue: t.String() + }) + + expect(hasTransform(schema)).toBe(true) + }) + it('Found t.ObjectString', () => { const schema = t.Object({ id: t.String(), diff --git a/test/validator/query.test.ts b/test/validator/query.test.ts index 6d478c8c..818f4d6a 100644 --- a/test/validator/query.test.ts +++ b/test/validator/query.test.ts @@ -142,6 +142,30 @@ describe('Query Validator', () => { expect(res.status).toBe(200) }) + it('parse numeric enum', async () => { + enum Gender { + MALE = 1, + FEMALE = 2, + UNKNOWN = 3 + } + + const app = new Elysia().get('/', ({ query }) => query, { + query: t.Object({ + name: t.String(), + gender: t.NumericEnum(Gender) + }) + }) + const res = await app.handle( + req(`/?name=sucrose&gender=${Gender.MALE}`) + ) + + expect(await res.json()).toEqual({ + name: 'sucrose', + gender: Gender.MALE + }) + expect(res.status).toBe(200) + }) + it('parse single integer', async () => { const app = new Elysia().get('/', ({ query: { limit } }) => limit, { query: t.Object({