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 { combineLatest, Observable } from "rxjs"
|
||||||
import { map } from "rxjs/operators"
|
import { map } from "rxjs/operators"
|
||||||
import {
|
import {
|
||||||
@@ -6,6 +8,8 @@ import {
|
|||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { parseTemplateString, parseBodyEnvVariables } from "../templating"
|
import { parseTemplateString, parseBodyEnvVariables } from "../templating"
|
||||||
|
import { arrayFlatMap, arraySort } from "../functional/array"
|
||||||
|
import { toFormData } from "../functional/formData"
|
||||||
import { Environment, getGlobalVariables } from "~/newstore/environments"
|
import { Environment, getGlobalVariables } from "~/newstore/environments"
|
||||||
|
|
||||||
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||||
@@ -59,27 +63,34 @@ function getFinalBodyFromRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.body.contentType === "multipart/form-data") {
|
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
|
// Sort files down
|
||||||
.filter((x) => x.key !== "" && x.active) // Remove empty keys
|
arraySort((a, b) => {
|
||||||
.map(
|
if (a.isFile) return 1
|
||||||
(x) =>
|
if (b.isFile) return -1
|
||||||
<FormDataKeyValue>{
|
return 0
|
||||||
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))
|
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
} else return parseBodyEnvVariables(request.body.body, env.variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user