Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ export default class Elysia<

get models(): {
[K in keyof Definitions['typebox']]: ModelValidator<
Definitions['typebox'][K]
Extract<Definitions['typebox'][K], TSchema>
>
} & {
modules:
Expand Down
9 changes: 5 additions & 4 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
TObject,
TransformKind,
TSchema,
type TAnySchema
type TAnySchema,
type Static
} from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
import { TypeCompiler } from '@sinclair/typebox/compiler'
Expand Down Expand Up @@ -39,10 +40,10 @@ export interface ElysiaTypeCheck<T extends TSchema>
provider: 'typebox' | 'standard'
schema: T
config: Object
Clean?(v: unknown): T
parse(v: unknown): T
Clean?(v: unknown): Static<T>
parse(v: unknown): Static<T>
safeParse(v: unknown):
| { success: true; data: T; error: null }
| { success: true; data: Static<T>; error: null }
| {
success: false
data: null
Expand Down
11 changes: 5 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { Elysia, AnyElysia, InvertedStatusMap } from './index'
import type { Elysia, AnyElysia, InvertedStatusMap, Static } from './index'
import type { ElysiaFile } from './universal/file'
import type { Serve } from './universal/server'

Expand Down Expand Up @@ -2353,13 +2353,12 @@ export interface ModelValidatorError extends ValueError {
summary: string
}

// @ts-ignore trust me bro
export interface ModelValidator<T> extends TypeCheck<T> {
parse(a: T): T
export interface ModelValidator<T extends TSchema> extends TypeCheck<T> {
parse(a: T): Static<T>
safeParse(a: T):
| { success: true; data: T; error: null }
| { success: true; data: Static<T>; error: null }
| {
success: true
success: false
data: null
error: string
errors: ModelValidatorError[]
Expand Down
167 changes: 167 additions & 0 deletions test/validator/schema-validator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { t } from '../../src'
import { getSchemaValidator } from '../../src/schema'

import { describe, expect, it } from 'bun:test'

describe('Schema Validator', () => {
describe('parse', () => {
it('should parse valid object', () => {
const validator = getSchemaValidator(
t.Object({
name: t.String()
})
)

const result = validator.parse({ name: 'test' })

expect(result).toEqual({ name: 'test' })
})

it('should pass object with additional properties when allowed', () => {
const validator = getSchemaValidator(
t.Object({
a: t.String()
}),
{ additionalProperties: true }
)

const result = validator.parse({
a: 'hello',
b: 'world'
})

expect(result.a).toBe('hello')
})

it('should reject additional properties by default', () => {
const validator = getSchemaValidator(
t.Object({
a: t.String()
})
)

expect(() =>
validator.parse({
a: 'hello',
b: 'world'
})
).toThrow()
})

it('should throw on invalid data', () => {
const validator = getSchemaValidator(
t.Object({
name: t.String()
})
)

expect(() => validator.parse({ name: 123 })).toThrow()
})
})

describe('safeParse', () => {
it('should return success with data on valid input', () => {
const validator = getSchemaValidator(
t.Object({
a: t.String()
})
)

const result = validator.safeParse({ a: 'test' })

expect(result.success).toBe(true)
if (result.success) {
expect(result.data).toEqual({ a: 'test' })
expect(result.error).toBeNull()
}
})

it('should return failure with error on invalid input', () => {
const validator = getSchemaValidator(
t.Object({
a: t.String()
})
)

const result = validator.safeParse({ a: 123 })

expect(result.success).toBe(false)
if (!result.success) {
expect(result.data).toBeNull()
expect(result.error).toBeDefined()
expect(result.errors).toBeDefined()
}
})

it('should work with union types', () => {
// Sample union schema
const eventPayload = t.Union([
t.Object({
type: t.Literal('message'),
content: t.String()
}),
t.Object({
type: t.Literal('error'),
code: t.Number()
})
])
type eventPayload = typeof eventPayload.static

const validator = getSchemaValidator(eventPayload)

// Test message type
const messageResult = validator.safeParse({
type: 'message',
content: 'hello'
})

expect(messageResult.success).toBe(true)
if (messageResult.success) {
expect(messageResult.data).toEqual({
type: 'message',
content: 'hello'
})
}

// Test error type
const errorResult = validator.safeParse({
type: 'error',
code: 500
})

expect(errorResult.success).toBe(true)
if (errorResult.success) {
expect(errorResult.data).toEqual({
type: 'error',
code: 500
})
}

// Test invalid type
const invalidResult = validator.safeParse({
type: 'unknown'
})

expect(invalidResult.success).toBe(false)
})

it('should have correct type inference for data', () => {
const validator = getSchemaValidator(
t.Object({
id: t.Number(),
name: t.String()
})
)

const result = validator.safeParse({ id: 1, name: 'test' })

if (result.success) {
const id: number = result.data.id
const name: string = result.data.name

expect(id).toBe(1)
expect(name).toBe('test')
}
})
})
})