diff --git a/packages/hoppscotch-app/helpers/functional/array.ts b/packages/hoppscotch-app/helpers/functional/array.ts new file mode 100644 index 000000000..64d12c4f2 --- /dev/null +++ b/packages/hoppscotch-app/helpers/functional/array.ts @@ -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 = + (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 = + (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 = + (mapFunc: (value: T, index: number, arr: T[]) => U[]) => + (arr: T[]) => + arr.flatMap(mapFunc) diff --git a/packages/hoppscotch-app/helpers/functional/formData.ts b/packages/hoppscotch-app/helpers/functional/formData.ts new file mode 100644 index 000000000..2ea416735 --- /dev/null +++ b/packages/hoppscotch-app/helpers/functional/formData.ts @@ -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 +} diff --git a/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts b/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts index 85630f12f..7fd39f3bf 100644 --- a/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts +++ b/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts @@ -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) => - { - 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) }