0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-21 10:08:58 +01:00

refactor: body parser (#2045)

This commit is contained in:
Aris Kemper 2024-01-20 22:17:48 +01:00 committed by GitHub
parent 039cac90ea
commit c02b83bccd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 142 additions and 79 deletions

View File

@ -8,5 +8,6 @@
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -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 <T extends BodyData = BodyData>(
request: HonoRequest | Request,
options: ParseBodyOptions = {
all: false,
}
options: ParseBodyOptions = { all: false }
): Promise<T> => {
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<T>(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<T extends BodyData = BodyData>(
request: HonoRequest | Request,
options: ParseBodyOptions
): Promise<T> {
const formData = await (request as Request).formData()
if (formData) {
return convertFormDataToBodyData<T>(formData, options)
}
return {} as T
}
function convertFormDataToBodyData<T extends BodyData = BodyData>(
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]
}

View File

@ -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 <T extends BodyData = BodyData>(
request: HonoRequest | Request,
options: ParseBodyOptions = {
all: false,
}
options: ParseBodyOptions = { all: false }
): Promise<T> => {
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<T>(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<T extends BodyData = BodyData>(
request: HonoRequest | Request,
options: ParseBodyOptions
): Promise<T> {
const formData = await (request as Request).formData()
if (formData) {
return convertFormDataToBodyData<T>(formData, options)
}
return {} as T
}
function convertFormDataToBodyData<T extends BodyData = BodyData>(
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]
}