Skip to content

Commit

Permalink
🎉: feat: add UnionEnum with JSON schema enum usage (#512)
Browse files Browse the repository at this point in the history
  • Loading branch information
kravetsone committed Sep 1, 2024
1 parent bfdd80a commit d832742
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 5 deletions.
53 changes: 48 additions & 5 deletions src/type-system.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ArrayOptions,
DateOptions,
Kind,
NumberOptions,
TArray,
TDate,
Expand Down Expand Up @@ -304,6 +305,25 @@ if (!FormatRegistry.Has('ArrayString'))
}
})

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

type NonEmptyArray<T> = [T, ...T[]]

export type TEnumValue = number | string | null;

export interface TUnionEnum<T extends NonEmptyArray<TEnumValue> = [TEnumValue]> extends TSchema {
[Kind]: 'UnionEnum'
static: T[number]
enum: T
}

export const ElysiaType = {
Numeric: (property?: NumberOptions) => {
const schema = Type.Number(property)
Expand Down Expand Up @@ -569,6 +589,31 @@ export const ElysiaType = {
}

return v
},
// based on https://github.com/elysiajs/elysia/issues/512#issuecomment-1980134955
UnionEnum: <const T extends NonEmptyArray<TEnumValue>>(
values: T,
options: SchemaOptions = {}
) => {
const type = values.every((value) => typeof value === 'string')
? { type: 'string' }
: values.every((value) => typeof value === 'number')
? { type: 'number' }
: values.every((value) => value === 'null')
? { type: 'null' }
: {}

if (values.some((x) => typeof x === 'object' && x !== null))
throw new Error('This type does not support objects or arrays')

return {
// why it need default??
default: values[0],
...options,
[Kind]: 'UnionEnum',
...type,
enum: values
} as any as TUnionEnum<T>
}
} as const

Expand All @@ -579,17 +624,13 @@ declare module '@sinclair/typebox' {
BooleanString: typeof ElysiaType.BooleanString
ObjectString: typeof ElysiaType.ObjectString
ArrayString: typeof ElysiaType.ArrayString
// @ts-ignore
Numeric: typeof ElysiaType.Numeric
// @ts-ignore
File: typeof ElysiaType.File
// @ts-ignore
Files: typeof ElysiaType.Files
// @ts-ignore
Nullable: typeof ElysiaType.Nullable
// @ts-ignore
MaybeEmpty: typeof ElysiaType.MaybeEmpty
Cookie: typeof ElysiaType.Cookie
UnionEnum: typeof ElysiaType.UnionEnum
}

interface SchemaOptions {
Expand Down Expand Up @@ -653,6 +694,8 @@ t.MaybeEmpty = ElysiaType.MaybeEmpty as any
t.Cookie = ElysiaType.Cookie
t.Date = ElysiaType.Date

t.UnionEnum = ElysiaType.UnionEnum

export { t }

export {
Expand Down
43 changes: 43 additions & 0 deletions test/type-system/union-enum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Elysia, { t } from '../../src'
import { describe, expect, it } from 'bun:test'
import { Value } from '@sinclair/typebox/value'
import { post } from '../utils'

describe('TypeSystem - UnionEnum', () => {
it('Create', () => {
expect(Value.Create(t.UnionEnum(['some', 'data']))).toEqual('some')
})

it('Check', () => {
const schema = t.UnionEnum(['some', 'data', null])

expect(Value.Check(schema, 'some')).toBe(true)
expect(Value.Check(schema, 'data')).toBe(true)
expect(Value.Check(schema, null)).toBe(true)

expect(Value.Check(schema, { deep: 2 })).toBe(false)
expect(Value.Check(schema, 'yay')).toBe(false)
expect(Value.Check(schema, 42)).toBe(false)
expect(Value.Check(schema, {})).toBe(false)
expect(Value.Check(schema, undefined)).toBe(false)
})
it('Integrate', async () => {
const app = new Elysia().post('/', ({ body }) => body, {
body: t.Object({
value: t.UnionEnum(['some', 1, null])
})
})

const res1 = await app.handle(post('/', { value: 1 }))
expect(res1.status).toBe(200)

const res2 = await app.handle(post('/', { value: null }))
expect(res2.status).toBe(200)

const res3 = await app.handle(post('/', { value: 'some' }))
expect(res3.status).toBe(200)

const res4 = await app.handle(post('/', { value: 'data' }))
expect(res4.status).toBe(422)
})
})

0 comments on commit d832742

Please sign in to comment.