fix: multipart form data sorting (#2067)
* fix: multipart form data sorting place all the file types in multipart form data body in the last to avoid errors due to map key being placed after file type data * refactor: fp-ify formdata file sort implementation Co-authored-by: Liyas Thomas <liyascthomas@gmail.com> Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
41
packages/hoppscotch-app/helpers/functional/array.ts
Normal file
41
packages/hoppscotch-app/helpers/functional/array.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import clone from "lodash/clone"
|
||||
/**
|
||||
* Sorts the array based on the sort func.
|
||||
* NOTE: Creates a new array, if you don't need ref
|
||||
* to original array, use `arrayUnsafeSort` for better perf
|
||||
* @param sortFunc Sort function to sort against
|
||||
*/
|
||||
export const arraySort =
|
||||
<T>(sortFunc: (a: T, b: T) => number) =>
|
||||
(arr: T[]) => {
|
||||
const newArr = clone(arr)
|
||||
|
||||
newArr.sort(sortFunc)
|
||||
|
||||
return newArr
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an array based on the sort func.
|
||||
* Unsafe because this sort mutates the passed array
|
||||
* and returns it. So use it if you do not want the
|
||||
* original array for better performance
|
||||
* @param sortFunc sort function to sort against (same as Array.sort)
|
||||
*/
|
||||
export const arrayUnsafeSort =
|
||||
<T>(sortFunc: (a: T, b: T) => number) =>
|
||||
(arr: T[]) => {
|
||||
arr.sort(sortFunc)
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to `Array.prototype.flatMap`
|
||||
* @param mapFunc The map function
|
||||
* @returns
|
||||
*/
|
||||
export const arrayFlatMap =
|
||||
<T, U>(mapFunc: (value: T, index: number, arr: T[]) => U[]) =>
|
||||
(arr: T[]) =>
|
||||
arr.flatMap(mapFunc)
|
||||
12
packages/hoppscotch-app/helpers/functional/formData.ts
Normal file
12
packages/hoppscotch-app/helpers/functional/formData.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
type FormDataEntry = {
|
||||
key: string
|
||||
value: string | Blob
|
||||
}
|
||||
|
||||
export const toFormData = (values: FormDataEntry[]) => {
|
||||
const formData = new FormData()
|
||||
|
||||
values.forEach(({ key, value }) => formData.append(key, value))
|
||||
|
||||
return formData
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { combineLatest, Observable } from "rxjs"
|
||||
import { map } from "rxjs/operators"
|
||||
import {
|
||||
@@ -6,6 +8,8 @@ import {
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { parseTemplateString, parseBodyEnvVariables } from "../templating"
|
||||
import { arrayFlatMap, arraySort } from "../functional/array"
|
||||
import { toFormData } from "../functional/formData"
|
||||
import { Environment, getGlobalVariables } from "~/newstore/environments"
|
||||
|
||||
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||
@@ -59,27 +63,34 @@ function getFinalBodyFromRequest(
|
||||
}
|
||||
|
||||
if (request.body.contentType === "multipart/form-data") {
|
||||
const formData = new FormData()
|
||||
return pipe(
|
||||
request.body.body,
|
||||
A.filter((x) => x.key !== "" && x.active), // Remove empty keys
|
||||
|
||||
request.body.body
|
||||
.filter((x) => x.key !== "" && x.active) // Remove empty keys
|
||||
.map(
|
||||
(x) =>
|
||||
<FormDataKeyValue>{
|
||||
active: x.active,
|
||||
isFile: x.isFile,
|
||||
key: parseTemplateString(x.key, env.variables),
|
||||
value: x.isFile
|
||||
? x.value
|
||||
: parseTemplateString(x.value, env.variables),
|
||||
}
|
||||
)
|
||||
.forEach((entry) => {
|
||||
if (!entry.isFile) formData.append(entry.key, entry.value)
|
||||
else entry.value.forEach((blob) => formData.append(entry.key, blob))
|
||||
})
|
||||
// Sort files down
|
||||
arraySort((a, b) => {
|
||||
if (a.isFile) return 1
|
||||
if (b.isFile) return -1
|
||||
return 0
|
||||
}),
|
||||
|
||||
return formData
|
||||
// FormData allows only a single blob in an entry,
|
||||
// we split array blobs into separate entries (FormData will then join them together during exec)
|
||||
arrayFlatMap((x) =>
|
||||
x.isFile
|
||||
? x.value.map((v) => ({
|
||||
key: parseTemplateString(x.key, env.variables),
|
||||
value: v as string | Blob,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: parseTemplateString(x.key, env.variables),
|
||||
value: parseTemplateString(x.value, env.variables),
|
||||
},
|
||||
]
|
||||
),
|
||||
toFormData
|
||||
)
|
||||
} else return parseBodyEnvVariables(request.body.body, env.variables)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user