/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Result } from './router.ts'
import type {
Input,
InputToDataByTarget,
ParamKeys,
ParamKeyToRecord,
RemoveQuestion,
UndefinedIfHavingQuestion,
ValidationTargets,
RouterRoute,
} from './types.ts'
import { parseBody } from './utils/body.ts'
import type { BodyData, ParseBodyOptions } from './utils/body.ts'
import type { Cookie } from './utils/cookie.ts'
import { parse } from './utils/cookie.ts'
import type { UnionToIntersection } from './utils/types.ts'
import { getQueryParam, getQueryParams, decodeURIComponent_ } from './utils/url.ts'
type Body = {
json: any
text: string
arrayBuffer: ArrayBuffer
blob: Blob
formData: FormData
}
type BodyCache = Partial
export class HonoRequest {
raw: Request
#validatedData: { [K in keyof ValidationTargets]?: {} } // Short name of validatedData
#matchResult: Result<[unknown, RouterRoute]>
routeIndex: number = 0
path: string
bodyCache: BodyCache = {}
constructor(
request: Request,
path: string = '/',
matchResult: Result<[unknown, RouterRoute]> = [[]]
) {
this.raw = request
this.path = path
this.#matchResult = matchResult
this.#validatedData = {}
}
param(
key: RemoveQuestion>
): UndefinedIfHavingQuestion>
param(): UnionToIntersection>>
param(key?: string): unknown {
if (key) {
const param = (
this.#matchResult[1]
? this.#matchResult[1][this.#matchResult[0][this.routeIndex][1][key] as any]
: this.#matchResult[0][this.routeIndex][1][key]
) as string | undefined
return param ? (/\%/.test(param) ? decodeURIComponent_(param) : param) : undefined
} else {
const decoded: Record = {}
const keys = Object.keys(this.#matchResult[0][this.routeIndex][1])
for (let i = 0, len = keys.length; i < len; i++) {
const key = keys[i]
const value = (
this.#matchResult[1]
? this.#matchResult[1][this.#matchResult[0][this.routeIndex][1][key] as any]
: this.#matchResult[0][this.routeIndex][1][key]
) as string | undefined
if (value && typeof value === 'string') {
decoded[key] = /\%/.test(value) ? decodeURIComponent_(value) : value
}
}
return decoded
}
}
query(key: string): string | undefined
query(): Record
query(key?: string) {
return getQueryParam(this.url, key)
}
queries(key: string): string[] | undefined
queries(): Record
queries(key?: string) {
return getQueryParams(this.url, key)
}
header(name: string): string | undefined
header(): Record
header(name?: string) {
if (name) return this.raw.headers.get(name.toLowerCase()) ?? undefined
const headerData: Record = {}
this.raw.headers.forEach((value, key) => {
headerData[key] = value
})
return headerData
}
/** @deprecated
* Use Cookie Middleware instead of `c.req.cookie()`. The `c.req.cookie()` will be removed in v4.
*
* @example
*
* import { getCookie } from 'hono/cookie'
* // ...
* app.get('/', (c) => c.text(getCookie(c, 'cookie-name')))
*/
cookie(key: string): string | undefined
/** @deprecated
* Use Cookie Middleware instead of `c.req.cookie()`. The `c.req.cookie()` will be removed in v4.
*
* @example
*
* import { getCookie } from 'hono/cookie'
* // ...
* app.get('/', (c) => c.json(getCookie(c)))
*/
cookie(): Cookie
cookie(key?: string) {
const cookie = this.raw.headers.get('Cookie')
if (!cookie) return
const obj = parse(cookie)
if (key) {
const value = obj[key]
return value
} else {
return obj
}
}
async parseBody(options?: ParseBodyOptions): Promise {
if (this.bodyCache.parsedBody) return this.bodyCache.parsedBody as T
const parsedBody = await parseBody(this, options)
this.bodyCache.parsedBody = parsedBody
return parsedBody
}
private cachedBody = (key: keyof Body) => {
const { bodyCache, raw } = this
const cachedBody = bodyCache[key]
if (cachedBody) return cachedBody
/**
* If an arrayBuffer cache is exist,
* use it for creating a text, json, and others.
*/
if (bodyCache.arrayBuffer) {
return (async () => {
return await new Response(bodyCache.arrayBuffer)[key]()
})()
}
return (bodyCache[key] = raw[key]())
}
json(): Promise {
return this.cachedBody('json')
}
text(): Promise {
return this.cachedBody('text')
}
arrayBuffer(): Promise {
return this.cachedBody('arrayBuffer')
}
blob(): Promise {
return this.cachedBody('blob')
}
formData(): Promise {
return this.cachedBody('formData')
}
addValidatedData(target: keyof ValidationTargets, data: {}) {
this.#validatedData[target] = data
}
valid(target: T): InputToDataByTarget
valid(target: keyof ValidationTargets) {
return this.#validatedData[target] as unknown
}
get url() {
return this.raw.url
}
get method() {
return this.raw.method
}
get matchedRoutes(): RouterRoute[] {
return this.#matchResult[0].map(([[, route]]) => route)
}
get routePath(): string {
return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path
}
/** @deprecated
* Use `c.req.raw.headers` instead of `c.req.headers`. The `c.req.headers` will be removed in v4.
* Or you can get the header values with using `c.req.header`.
* @example
*
* app.get('/', (c) => {
* const userAgent = c.req.header('User-Agent')
* //...
* })
*/
get headers() {
return this.raw.headers
}
/** @deprecated
* Use `c.req.raw.body` instead of `c.req.body`. The `c.req.body` will be removed in v4.
*/
get body() {
return this.raw.body
}
/** @deprecated
* Use `c.req.raw.bodyUsed` instead of `c.req.bodyUsed`. The `c.req.bodyUsed` will be removed in v4.
*/
get bodyUsed() {
return this.raw.bodyUsed
}
/** @deprecated
* Use `c.req.raw.integrity` instead of `c.req.integrity`. The `c.req.integrity` will be removed in v4.
*/
get integrity() {
return this.raw.integrity
}
/** @deprecated
* Use `c.req.raw.keepalive` instead of `c.req.keepalive`. The `c.req.keepalive` will be removed in v4.
*/
get keepalive() {
return this.raw.keepalive
}
/** @deprecated
* Use `c.req.raw.referrer` instead of `c.req.referrer`. The `c.req.referrer` will be removed in v4.
*/
get referrer() {
return this.raw.referrer
}
/** @deprecated
* Use `c.req.raw.signal` instead of `c.req.signal`. The `c.req.signal` will be removed in v4.
*/
get signal() {
return this.raw.signal
}
}