mirror of
https://github.com/honojs/hono.git
synced 2024-12-01 11:51:01 +01:00
feat(req): support c.req.valid('query')
(#832)
This commit is contained in:
parent
2de2ef3279
commit
2afa06a5ba
@ -1,6 +1,5 @@
|
||||
import type { Context } from '../../context.ts'
|
||||
import type { Env, ValidationTypes, MiddlewareHandler } from '../../types.ts'
|
||||
import { mergeObjects } from '../../utils/object.ts'
|
||||
|
||||
type ValidationTypeKeysWithBody = 'form' | 'json'
|
||||
type ValidationTypeByMethod<M> = M extends 'get' | 'head' // GET and HEAD request must not have a body content.
|
||||
@ -55,10 +54,8 @@ export const validator = <
|
||||
return res
|
||||
}
|
||||
|
||||
const target = c.req.valid()
|
||||
const newObject = mergeObjects(target, res)
|
||||
c.req.addValidatedData(type, res as {})
|
||||
|
||||
c.req.valid(newObject)
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import type { InputToData } from './types.ts'
|
||||
import type { InputToData, InputToTypeData, ValidationTypes } from './types.ts'
|
||||
import { parseBody } from './utils/body.ts'
|
||||
import type { BodyData } from './utils/body.ts'
|
||||
import type { Cookie } from './utils/cookie.ts'
|
||||
import { parse } from './utils/cookie.ts'
|
||||
import { mergeObjects } from './utils/object.ts'
|
||||
import type { UnionToIntersection } from './utils/types.ts'
|
||||
import { getQueryStringFromURL, getQueryParam, getQueryParams } from './utils/url.ts'
|
||||
|
||||
@ -36,7 +37,7 @@ export class HonoRequest<Path extends string = '/', Input = {}> {
|
||||
private bodyData: BodyData | undefined
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private jsonData: Promise<any> | undefined
|
||||
private data: InputToData<Input>
|
||||
private validatedData: { [K in keyof ValidationTypes]?: {} }
|
||||
private queryIndex: number
|
||||
|
||||
constructor(
|
||||
@ -46,8 +47,8 @@ export class HonoRequest<Path extends string = '/', Input = {}> {
|
||||
) {
|
||||
this.raw = request
|
||||
this.paramData = paramData
|
||||
this.data = {} as InputToData<Input>
|
||||
this.queryIndex = queryIndex
|
||||
this.validatedData = {}
|
||||
}
|
||||
|
||||
param(key: RemoveQuestion<ParamKeys<Path>>): UndefinedIfHavingQuestion<ParamKeys<Path>>
|
||||
@ -157,14 +158,25 @@ export class HonoRequest<Path extends string = '/', Input = {}> {
|
||||
return this.raw.formData()
|
||||
}
|
||||
|
||||
valid(data?: unknown): InputToData<Input> {
|
||||
if (!this.data) {
|
||||
this.data = {} as InputToData<Input>
|
||||
addValidatedData(type: keyof ValidationTypes, data: {}) {
|
||||
const storedData = this.validatedData[type] || {}
|
||||
const merged = mergeObjects(storedData, data)
|
||||
this.validatedData[type] = merged
|
||||
}
|
||||
|
||||
valid(): InputToData<Input>
|
||||
valid<T extends keyof ValidationTypes>(type: T): InputToTypeData<T, Input>
|
||||
valid<T extends keyof ValidationTypes>(type?: T) {
|
||||
if (type) {
|
||||
const data = this.validatedData[type]
|
||||
return data
|
||||
} else {
|
||||
let data: Record<string, unknown> = {}
|
||||
for (const v of Object.values(this.validatedData)) {
|
||||
data = mergeObjects(data, v)
|
||||
}
|
||||
return data
|
||||
}
|
||||
if (data) {
|
||||
this.data = data as InputToData<Input>
|
||||
}
|
||||
return this.data
|
||||
}
|
||||
|
||||
get url() {
|
||||
|
@ -269,8 +269,16 @@ type ToAppTypeInner<P extends string, M extends string, I, O> = RemoveBlank<I> e
|
||||
export type InputToData<T> = ExtractData<T> extends never
|
||||
? any
|
||||
: UnionToIntersection<ExtractData<T>>
|
||||
|
||||
type ExtractData<T> = T extends { type: keyof ValidationTypes }
|
||||
? T extends { type: keyof ValidationTypes; data?: infer R }
|
||||
? R
|
||||
: any
|
||||
: T
|
||||
|
||||
export type InputToTypeData<K extends keyof ValidationTypes, T> = T extends {
|
||||
type: K
|
||||
data: infer R
|
||||
}
|
||||
? R
|
||||
: never
|
||||
|
@ -305,6 +305,8 @@ describe('Validator middleware with Zod multiple validators', () => {
|
||||
(c) => {
|
||||
const id = c.get('id')
|
||||
type verify = Expect<Equal<number, typeof id>>
|
||||
const formValidatedData = c.req.valid('form')
|
||||
type verify2 = Expect<Equal<{ title: string }, typeof formValidatedData>>
|
||||
const res = c.req.valid()
|
||||
return c.jsonT({
|
||||
page: res.page,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import type { Context } from '../../context'
|
||||
import type { Env, ValidationTypes, MiddlewareHandler } from '../../types'
|
||||
import { mergeObjects } from '../../utils/object'
|
||||
|
||||
type ValidationTypeKeysWithBody = 'form' | 'json'
|
||||
type ValidationTypeByMethod<M> = M extends 'get' | 'head' // GET and HEAD request must not have a body content.
|
||||
@ -55,10 +54,8 @@ export const validator = <
|
||||
return res
|
||||
}
|
||||
|
||||
const target = c.req.valid()
|
||||
const newObject = mergeObjects(target, res)
|
||||
c.req.addValidatedData(type, res as {})
|
||||
|
||||
c.req.valid(newObject)
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
57
src/request.test.ts
Normal file
57
src/request.test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { HonoRequest } from './request'
|
||||
|
||||
const rawRequest = new Request('http://localhost')
|
||||
|
||||
describe('req.addValidatedData() and req.data()', () => {
|
||||
const payload = {
|
||||
title: 'hello',
|
||||
author: {
|
||||
name: 'young man',
|
||||
age: 20,
|
||||
},
|
||||
}
|
||||
|
||||
test('add data - json', () => {
|
||||
const req = new HonoRequest(rawRequest)
|
||||
req.addValidatedData('json', payload)
|
||||
const data = req.valid('json')
|
||||
expect(data).toEqual(payload)
|
||||
})
|
||||
|
||||
test('append data - json', () => {
|
||||
const req = new HonoRequest(rawRequest)
|
||||
req.addValidatedData('json', payload)
|
||||
req.addValidatedData('json', {
|
||||
tag: ['sport', 'music'],
|
||||
author: {
|
||||
tall: 170,
|
||||
},
|
||||
})
|
||||
const data = req.valid('json')
|
||||
expect(data).toEqual({
|
||||
title: 'hello',
|
||||
author: {
|
||||
name: 'young man',
|
||||
age: 20,
|
||||
tall: 170,
|
||||
},
|
||||
tag: ['sport', 'music'],
|
||||
})
|
||||
})
|
||||
|
||||
test('req.data() with no argument', () => {
|
||||
const req = new HonoRequest(rawRequest)
|
||||
req.addValidatedData('query', { page: '123', q: 'foo' })
|
||||
req.addValidatedData('json', payload)
|
||||
const data = req.valid()
|
||||
expect(data).toEqual({
|
||||
page: '123',
|
||||
q: 'foo',
|
||||
title: 'hello',
|
||||
author: {
|
||||
name: 'young man',
|
||||
age: 20,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
@ -1,8 +1,9 @@
|
||||
import type { InputToData } from './types'
|
||||
import type { InputToData, InputToTypeData, ValidationTypes } from './types'
|
||||
import { parseBody } from './utils/body'
|
||||
import type { BodyData } from './utils/body'
|
||||
import type { Cookie } from './utils/cookie'
|
||||
import { parse } from './utils/cookie'
|
||||
import { mergeObjects } from './utils/object'
|
||||
import type { UnionToIntersection } from './utils/types'
|
||||
import { getQueryStringFromURL, getQueryParam, getQueryParams } from './utils/url'
|
||||
|
||||
@ -36,7 +37,7 @@ export class HonoRequest<Path extends string = '/', Input = {}> {
|
||||
private bodyData: BodyData | undefined
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private jsonData: Promise<any> | undefined
|
||||
private data: InputToData<Input>
|
||||
private validatedData: { [K in keyof ValidationTypes]?: {} }
|
||||
private queryIndex: number
|
||||
|
||||
constructor(
|
||||
@ -46,8 +47,8 @@ export class HonoRequest<Path extends string = '/', Input = {}> {
|
||||
) {
|
||||
this.raw = request
|
||||
this.paramData = paramData
|
||||
this.data = {} as InputToData<Input>
|
||||
this.queryIndex = queryIndex
|
||||
this.validatedData = {}
|
||||
}
|
||||
|
||||
param(key: RemoveQuestion<ParamKeys<Path>>): UndefinedIfHavingQuestion<ParamKeys<Path>>
|
||||
@ -157,14 +158,25 @@ export class HonoRequest<Path extends string = '/', Input = {}> {
|
||||
return this.raw.formData()
|
||||
}
|
||||
|
||||
valid(data?: unknown): InputToData<Input> {
|
||||
if (!this.data) {
|
||||
this.data = {} as InputToData<Input>
|
||||
addValidatedData(type: keyof ValidationTypes, data: {}) {
|
||||
const storedData = this.validatedData[type] || {}
|
||||
const merged = mergeObjects(storedData, data)
|
||||
this.validatedData[type] = merged
|
||||
}
|
||||
|
||||
valid(): InputToData<Input>
|
||||
valid<T extends keyof ValidationTypes>(type: T): InputToTypeData<T, Input>
|
||||
valid<T extends keyof ValidationTypes>(type?: T) {
|
||||
if (type) {
|
||||
const data = this.validatedData[type]
|
||||
return data
|
||||
} else {
|
||||
let data: Record<string, unknown> = {}
|
||||
for (const v of Object.values(this.validatedData)) {
|
||||
data = mergeObjects(data, v)
|
||||
}
|
||||
return data
|
||||
}
|
||||
if (data) {
|
||||
this.data = data as InputToData<Input>
|
||||
}
|
||||
return this.data
|
||||
}
|
||||
|
||||
get url() {
|
||||
|
@ -2,7 +2,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Hono } from './hono'
|
||||
import { poweredBy } from './middleware/powered-by'
|
||||
import type { Env, CustomHandler as Handler, InputToData, ToAppType } from './types'
|
||||
import type {
|
||||
Env,
|
||||
CustomHandler as Handler,
|
||||
InputToData,
|
||||
InputToTypeData,
|
||||
ToAppType,
|
||||
} from './types'
|
||||
import type { Expect, Equal } from './utils/types'
|
||||
|
||||
describe('Test types of CustomHandler', () => {
|
||||
@ -135,6 +141,18 @@ describe('Types used in the validator', () => {
|
||||
}
|
||||
type verify = Expect<Equal<Expected, Actual>>
|
||||
})
|
||||
|
||||
test('InputToTypeData', () => {
|
||||
type P =
|
||||
| {}
|
||||
| { type: 'query'; data: { page: number } }
|
||||
| { type: 'form'; data: { title: string } }
|
||||
type Actual = InputToTypeData<'query', P>
|
||||
type Expected = {
|
||||
page: number
|
||||
}
|
||||
type verify = Expect<Equal<Expected, Actual>>
|
||||
})
|
||||
})
|
||||
|
||||
describe('`jsonT()`', () => {
|
||||
|
@ -269,8 +269,16 @@ type ToAppTypeInner<P extends string, M extends string, I, O> = RemoveBlank<I> e
|
||||
export type InputToData<T> = ExtractData<T> extends never
|
||||
? any
|
||||
: UnionToIntersection<ExtractData<T>>
|
||||
|
||||
type ExtractData<T> = T extends { type: keyof ValidationTypes }
|
||||
? T extends { type: keyof ValidationTypes; data?: infer R }
|
||||
? R
|
||||
: any
|
||||
: T
|
||||
|
||||
export type InputToTypeData<K extends keyof ValidationTypes, T> = T extends {
|
||||
type: K
|
||||
data: infer R
|
||||
}
|
||||
? R
|
||||
: never
|
||||
|
Loading…
Reference in New Issue
Block a user