From c02b83bccd45e98b2feaa188326258a5b262e223 Mon Sep 17 00:00:00 2001 From: Aris Kemper Date: Sat, 20 Jan 2024 22:17:48 +0100 Subject: [PATCH] refactor: body parser (#2045) --- .vscode/settings.json | 3 +- deno_dist/utils/body.ts | 109 ++++++++++++++++++++++++++-------------- src/utils/body.ts | 109 ++++++++++++++++++++++++++-------------- 3 files changed, 142 insertions(+), 79 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 74fdab42..261fd4b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ ], "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" - } + }, + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/deno_dist/utils/body.ts b/deno_dist/utils/body.ts index 336430c7..f354222f 100644 --- a/deno_dist/utils/body.ts +++ b/deno_dist/utils/body.ts @@ -22,49 +22,80 @@ export type ParseBodyOptions = { all?: boolean } -const isArrayField = (value: unknown): value is (string | File)[] => { - return Array.isArray(value) -} - export const parseBody = async ( request: HonoRequest | Request, - options: ParseBodyOptions = { - all: false, - } + options: ParseBodyOptions = { all: false } ): Promise => { - let body: BodyData = {} const contentType = request.headers.get('Content-Type') - if ( - contentType && - (contentType.startsWith('multipart/form-data') || - contentType.startsWith('application/x-www-form-urlencoded')) - ) { - const formData = await request.formData() - if (formData) { - const form: BodyData = {} - formData.forEach((value, key) => { - const shouldParseAllValues = options.all || key.slice(-2) === '[]' - - if (!shouldParseAllValues) { - form[key] = value // override if same key - return - } - - if (form[key] && isArrayField(form[key])) { - ;(form[key] as (string | File)[]).push(value) // append if same key - return - } - - if (form[key]) { - form[key] = [form[key] as string | File, value] // convert to array if multiple values - return - } - - form[key] = value - }) - body = form - } + if (isFormDataContent(contentType)) { + return parseFormData(request, options) } - return body as T + + return {} as T +} + +function isFormDataContent(contentType: string | null): boolean { + if (contentType === null) { + return false + } + + return ( + contentType.startsWith('multipart/form-data') || + contentType.startsWith('application/x-www-form-urlencoded') + ) +} + +async function parseFormData( + request: HonoRequest | Request, + options: ParseBodyOptions +): Promise { + const formData = await (request as Request).formData() + + if (formData) { + return convertFormDataToBodyData(formData, options) + } + + return {} as T +} + +function convertFormDataToBodyData( + formData: FormData, + options: ParseBodyOptions +): T { + const form: BodyData = {} + + formData.forEach((value, key) => { + const shouldParseAllValues = options.all || key.endsWith('[]') + + if (!shouldParseAllValues) { + form[key] = value + } else { + handleParsingAllValues(form, key, value) + } + }) + + return form as T +} + +const handleParsingAllValues = (form: BodyData, key: string, value: FormDataEntryValue): void => { + if (form[key] && isArrayField(form[key])) { + appendToExistingArray(form[key] as (string | File)[], value) + } else if (form[key]) { + convertToNewArray(form, key, value) + } else { + form[key] = value + } +} + +function isArrayField(field: unknown): field is (string | File)[] { + return Array.isArray(field) +} + +const appendToExistingArray = (arr: (string | File)[], value: FormDataEntryValue): void => { + arr.push(value) +} + +const convertToNewArray = (form: BodyData, key: string, value: FormDataEntryValue): void => { + form[key] = [form[key] as string | File, value] } diff --git a/src/utils/body.ts b/src/utils/body.ts index 5de7870e..40199934 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -22,49 +22,80 @@ export type ParseBodyOptions = { all?: boolean } -const isArrayField = (value: unknown): value is (string | File)[] => { - return Array.isArray(value) -} - export const parseBody = async ( request: HonoRequest | Request, - options: ParseBodyOptions = { - all: false, - } + options: ParseBodyOptions = { all: false } ): Promise => { - let body: BodyData = {} const contentType = request.headers.get('Content-Type') - if ( - contentType && - (contentType.startsWith('multipart/form-data') || - contentType.startsWith('application/x-www-form-urlencoded')) - ) { - const formData = await request.formData() - if (formData) { - const form: BodyData = {} - formData.forEach((value, key) => { - const shouldParseAllValues = options.all || key.slice(-2) === '[]' - - if (!shouldParseAllValues) { - form[key] = value // override if same key - return - } - - if (form[key] && isArrayField(form[key])) { - ;(form[key] as (string | File)[]).push(value) // append if same key - return - } - - if (form[key]) { - form[key] = [form[key] as string | File, value] // convert to array if multiple values - return - } - - form[key] = value - }) - body = form - } + if (isFormDataContent(contentType)) { + return parseFormData(request, options) } - return body as T + + return {} as T +} + +function isFormDataContent(contentType: string | null): boolean { + if (contentType === null) { + return false + } + + return ( + contentType.startsWith('multipart/form-data') || + contentType.startsWith('application/x-www-form-urlencoded') + ) +} + +async function parseFormData( + request: HonoRequest | Request, + options: ParseBodyOptions +): Promise { + const formData = await (request as Request).formData() + + if (formData) { + return convertFormDataToBodyData(formData, options) + } + + return {} as T +} + +function convertFormDataToBodyData( + formData: FormData, + options: ParseBodyOptions +): T { + const form: BodyData = {} + + formData.forEach((value, key) => { + const shouldParseAllValues = options.all || key.endsWith('[]') + + if (!shouldParseAllValues) { + form[key] = value + } else { + handleParsingAllValues(form, key, value) + } + }) + + return form as T +} + +const handleParsingAllValues = (form: BodyData, key: string, value: FormDataEntryValue): void => { + if (form[key] && isArrayField(form[key])) { + appendToExistingArray(form[key] as (string | File)[], value) + } else if (form[key]) { + convertToNewArray(form, key, value) + } else { + form[key] = value + } +} + +function isArrayField(field: unknown): field is (string | File)[] { + return Array.isArray(field) +} + +const appendToExistingArray = (arr: (string | File)[], value: FormDataEntryValue): void => { + arr.push(value) +} + +const convertToNewArray = (form: BodyData, key: string, value: FormDataEntryValue): void => { + form[key] = [form[key] as string | File, value] }