refactor: inherit default curl parser values (#2169)
This commit is contained in:
169
packages/hoppscotch-app/helpers/curl/sub_helpers/body.ts
Normal file
169
packages/hoppscotch-app/helpers/curl/sub_helpers/body.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import parser from "yargs-parser"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
||||
import * as S from "fp-ts/string"
|
||||
import {
|
||||
HoppRESTReqBody,
|
||||
HoppRESTReqBodyFormData,
|
||||
ValidContentTypes,
|
||||
knownContentTypes,
|
||||
} from "@hoppscotch/data"
|
||||
import { detectContentType, parseBody } from "./contentParser"
|
||||
import { tupleToRecord } from "~/helpers/functional/record"
|
||||
import {
|
||||
objHasProperty,
|
||||
objHasArrayProperty,
|
||||
} from "~/helpers/functional/object"
|
||||
|
||||
type BodyReturnType =
|
||||
| { type: "FORMDATA"; body: Record<string, string> }
|
||||
| {
|
||||
type: "NON_FORMDATA"
|
||||
body: Exclude<HoppRESTReqBody, HoppRESTReqBodyFormData>
|
||||
}
|
||||
|
||||
/** Parses body based on the content type
|
||||
* @param rData Raw data
|
||||
* @param cType Sanitized content type
|
||||
* @returns Option of parsed body of type string | Record<string, string>
|
||||
*/
|
||||
const getBodyFromContentType =
|
||||
(rData: string, cType: HoppRESTReqBody["contentType"]) => (rct: string) =>
|
||||
pipe(
|
||||
cType,
|
||||
O.fromPredicate((ctype) => ctype === "multipart/form-data"),
|
||||
O.chain(() =>
|
||||
pipe(
|
||||
// pass rawContentType for boundary ascertion
|
||||
parseBody(rData, cType, rct),
|
||||
O.filter((parsedBody) => typeof parsedBody !== "string")
|
||||
)
|
||||
),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
parseBody(rData, cType),
|
||||
O.filter(
|
||||
(parsedBody) =>
|
||||
typeof parsedBody === "string" && parsedBody.length > 0
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const getContentTypeFromRawContentType = (rawContentType: string) =>
|
||||
pipe(
|
||||
rawContentType,
|
||||
O.fromPredicate((rct) => rct.length > 0),
|
||||
// get everything before semi-colon
|
||||
O.map(flow(S.toLowerCase, S.split(";"), RNEA.head)),
|
||||
// if rawContentType is valid, cast it to contentType type
|
||||
O.filter((ct) => Object.keys(knownContentTypes).includes(ct)),
|
||||
O.map((ct) => ct as HoppRESTReqBody["contentType"])
|
||||
)
|
||||
|
||||
const getContentTypeFromRawData = (rawData: string) =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate((rd) => rd.length > 0),
|
||||
O.map(detectContentType)
|
||||
)
|
||||
|
||||
export const getBody = (
|
||||
rawData: string,
|
||||
rawContentType: string,
|
||||
contentType: HoppRESTReqBody["contentType"]
|
||||
): O.Option<BodyReturnType> => {
|
||||
return pipe(
|
||||
O.Do,
|
||||
|
||||
O.bind("cType", () =>
|
||||
pipe(
|
||||
// get provided content-type
|
||||
contentType,
|
||||
O.fromNullable,
|
||||
// or figure it out
|
||||
O.alt(() => getContentTypeFromRawContentType(rawContentType)),
|
||||
O.alt(() => getContentTypeFromRawData(rawData))
|
||||
)
|
||||
),
|
||||
|
||||
O.bind("rData", () =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate(() => rawData.length > 0)
|
||||
)
|
||||
),
|
||||
|
||||
O.bind("ctBody", ({ cType, rData }) =>
|
||||
pipe(rawContentType, getBodyFromContentType(rData, cType))
|
||||
),
|
||||
|
||||
O.map(({ cType, ctBody }) =>
|
||||
typeof ctBody === "string"
|
||||
? {
|
||||
type: "NON_FORMDATA",
|
||||
body: {
|
||||
body: ctBody,
|
||||
contentType: cType as Exclude<
|
||||
ValidContentTypes,
|
||||
"multipart/form-data"
|
||||
>,
|
||||
},
|
||||
}
|
||||
: { type: "FORMDATA", body: ctBody }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and structures multipart/form-data from -F argument of curl command
|
||||
* @param parsedArguments Parsed Arguments object
|
||||
* @returns Option of Record<string, string> type containing key-value pairs of multipart/form-data
|
||||
*/
|
||||
export function getFArgumentMultipartData(
|
||||
parsedArguments: parser.Arguments
|
||||
): O.Option<Record<string, string>> {
|
||||
// --form or -F multipart data
|
||||
|
||||
return pipe(
|
||||
parsedArguments,
|
||||
// make it an array if not already
|
||||
O.fromPredicate(objHasProperty("F", "string")),
|
||||
O.map((args) => [args.F]),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasArrayProperty("F", "string")),
|
||||
O.map((args) => args.F)
|
||||
)
|
||||
),
|
||||
O.chain(
|
||||
flow(
|
||||
A.map(S.split("=")),
|
||||
// can only have a key and no value
|
||||
O.fromPredicate((fArgs) => fArgs.length > 0),
|
||||
O.map(
|
||||
flow(
|
||||
A.map(([k, v]) =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
// form-string option allows for "@" and "<" prefixes
|
||||
// without them being considered as files
|
||||
O.fromPredicate(objHasProperty("form-string", "boolean")),
|
||||
O.match(
|
||||
// leave the value field empty for files
|
||||
() => [k, v[0] === "@" || v[0] === "<" ? "" : v],
|
||||
(_) => [k, v]
|
||||
)
|
||||
)
|
||||
),
|
||||
A.map(([k, v]) => [k, v] as [string, string]),
|
||||
tupleToRecord
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user