/* 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 { 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 } 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 } }