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:
kyteinsky
2022-01-22 21:00:39 +00:00
committed by GitHub
parent 3bb65f2115
commit 6205b5f163
3 changed files with 83 additions and 19 deletions

View 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)

View 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
}

View File

@@ -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)
}