fix(valibot-validator): Add supports async validation (#515)

pull/516/head
mizunohito 2024-05-13 06:56:42 +09:00 committed by GitHub
parent 2ba0639f95
commit f59edfe89c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 169 additions and 7 deletions

View File

@ -0,0 +1,5 @@
---
'@hono/valibot-validator': patch
---
Add supports async validation

View File

@ -1,9 +1,9 @@
import type { Context, MiddlewareHandler, Env, ValidationTargets, Input as HonoInput } from 'hono'
import { validator } from 'hono/validator'
import type { BaseSchema, Input, Output, SafeParseResult } from 'valibot'
import { safeParse } from 'valibot'
import type { BaseSchema, BaseSchemaAsync, Input, Output, SafeParseResult } from 'valibot'
import { safeParseAsync } from 'valibot'
type Hook<T extends BaseSchema, E extends Env, P extends string> = (
type Hook<T extends BaseSchema | BaseSchemaAsync, E extends Env, P extends string> = (
result: SafeParseResult<T>,
c: Context<E, P>
) => Response | Promise<Response> | void | Promise<Response | void>
@ -11,7 +11,7 @@ type Hook<T extends BaseSchema, E extends Env, P extends string> = (
type HasUndefined<T> = undefined extends T ? true : false
export const vValidator = <
T extends BaseSchema,
T extends BaseSchema | BaseSchemaAsync,
Target extends keyof ValidationTargets,
E extends Env,
P extends string,
@ -42,8 +42,8 @@ export const vValidator = <
hook?: Hook<T, E, P>
): MiddlewareHandler<E, P, V> =>
// @ts-expect-error not typed well
validator(target, (value, c) => {
const result = safeParse(schema, value)
validator(target, async (value, c) => {
const result = await safeParseAsync(schema, value)
if (hook) {
const hookResult = hook(result, c)

View File

@ -1,6 +1,6 @@
import { Hono } from 'hono'
import type { Equal, Expect } from 'hono/utils/types'
import { number, object, string, optional } from 'valibot'
import { number, object, string, optional, numberAsync, objectAsync, stringAsync, optionalAsync } from 'valibot'
import { vValidator } from '../src'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -162,3 +162,160 @@ describe('With Hook', () => {
expect(res.status).toBe(400)
})
})
describe('Async', () => {
const app = new Hono()
const schemaAsync = objectAsync({
name: stringAsync(),
age: numberAsync(),
})
const querySchemaAsync = optionalAsync(
objectAsync({
search: optionalAsync(stringAsync()),
page: optionalAsync(numberAsync()),
})
)
const route = app.post(
'/author',
vValidator('json', schemaAsync),
vValidator('query', querySchemaAsync),
(c) => {
const data = c.req.valid('json')
const query = c.req.valid('query')
return c.json({
success: true,
message: `${data.name} is ${data.age}, search is ${query?.search}`,
})
}
)
type Actual = ExtractSchema<typeof route>
type Expected = {
'/author': {
$post: {
input: {
json: {
name: string
age: number
}
} & {
query?:
| {
search?: string | string[] | undefined
page?: string | string[] | undefined
}
| undefined
}
output: {
success: boolean
message: string
}
}
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type verify = Expect<Equal<Expected, Actual>>
it('Should return 200 response', async () => {
const req = new Request('http://localhost/author?search=hello', {
body: JSON.stringify({
name: 'Superman',
age: 20,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
const res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
success: true,
message: 'Superman is 20, search is hello',
})
})
it('Should return 400 response', async () => {
const req = new Request('http://localhost/author', {
body: JSON.stringify({
name: 'Superman',
age: '20',
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
const res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(400)
const data = (await res.json()) as { success: boolean }
expect(data['success']).toBe(false)
})
})
describe('With Hook Async', () => {
const app = new Hono()
const schemaAsync = objectAsync({
id: numberAsync(),
title: stringAsync(),
})
app.post(
'/post',
vValidator('json', schemaAsync, (result, c) => {
if (!result.success) {
return c.text('Invalid!', 400)
}
const data = result.output
return c.text(`${data.id} is valid!`)
}),
(c) => {
const data = c.req.valid('json')
return c.json({
success: true,
message: `${data.id} is ${data.title}`,
})
}
)
it('Should return 200 response', async () => {
const req = new Request('http://localhost/post', {
body: JSON.stringify({
id: 123,
title: 'Hello',
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
const res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.text()).toBe('123 is valid!')
})
it('Should return 400 response', async () => {
const req = new Request('http://localhost/post', {
body: JSON.stringify({
id: '123',
title: 'Hello',
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
const res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(400)
})
})