123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- import {
- Environment,
- HoppRESTRequest,
- parseBodyEnvVariablesE,
- parseRawKeyValueEntriesE,
- parseTemplateString,
- parseTemplateStringE,
- } from "@hoppscotch/data";
- import { runPreRequestScript } from "@hoppscotch/js-sandbox";
- import { flow, pipe } from "fp-ts/function";
- import * as TE from "fp-ts/TaskEither";
- import * as E from "fp-ts/Either";
- import * as RA from "fp-ts/ReadonlyArray";
- import * as A from "fp-ts/Array";
- import * as O from "fp-ts/Option";
- import * as S from "fp-ts/string";
- import qs from "qs";
- import { EffectiveHoppRESTRequest } from "../interfaces/request";
- import { error, HoppCLIError } from "../types/errors";
- import { HoppEnvs } from "../types/request";
- import { isHoppCLIError } from "./checks";
- import { tupleToRecord, arraySort, arrayFlatMap } from "./functions/array";
- import { toFormData } from "./mutators";
- import { getEffectiveFinalMetaData } from "./getters";
- import { PreRequestMetrics } from "../types/response";
- /**
- * Runs pre-request-script runner over given request which extracts set ENVs and
- * applies them on current request to generate updated request.
- * @param request HoppRESTRequest to be converted to EffectiveHoppRESTRequest.
- * @param envs Environment variables related to request.
- * @returns EffectiveHoppRESTRequest that includes parsed ENV variables with in
- * request OR HoppCLIError with error code and related information.
- */
- export const preRequestScriptRunner = (
- request: HoppRESTRequest,
- envs: HoppEnvs
- ): TE.TaskEither<HoppCLIError, EffectiveHoppRESTRequest> =>
- pipe(
- TE.of(request),
- TE.chain(({ preRequestScript }) =>
- runPreRequestScript(preRequestScript, envs)
- ),
- TE.map(
- ({ selected, global }) =>
- <Environment>{ name: "Env", variables: [...selected, ...global] }
- ),
- TE.chainEitherKW((env) => getEffectiveRESTRequest(request, env)),
- TE.mapLeft((reason) =>
- isHoppCLIError(reason)
- ? reason
- : error({
- code: "PRE_REQUEST_SCRIPT_ERROR",
- data: reason,
- })
- )
- );
- /**
- * Outputs an executable request format with environment variables applied
- *
- * @param request The request to source from
- * @param environment The environment to apply
- *
- * @returns An object with extra fields defining a complete request
- */
- export function getEffectiveRESTRequest(
- request: HoppRESTRequest,
- environment: Environment
- ): E.Either<HoppCLIError, EffectiveHoppRESTRequest> {
- const envVariables = environment.variables;
- // Parsing final headers with applied ENVs.
- const _effectiveFinalHeaders = getEffectiveFinalMetaData(
- request.headers,
- environment
- );
- if (E.isLeft(_effectiveFinalHeaders)) {
- return _effectiveFinalHeaders;
- }
- const effectiveFinalHeaders = _effectiveFinalHeaders.right;
- // Parsing final parameters with applied ENVs.
- const _effectiveFinalParams = getEffectiveFinalMetaData(
- request.params,
- environment
- );
- if (E.isLeft(_effectiveFinalParams)) {
- return _effectiveFinalParams;
- }
- const effectiveFinalParams = _effectiveFinalParams.right;
- // Authentication
- if (request.auth.authActive) {
- // TODO: Support a better b64 implementation than btoa ?
- if (request.auth.authType === "basic") {
- const username = parseTemplateString(request.auth.username, envVariables);
- const password = parseTemplateString(request.auth.password, envVariables);
- effectiveFinalHeaders.push({
- active: true,
- key: "Authorization",
- value: `Basic ${btoa(`${username}:${password}`)}`,
- });
- } else if (
- request.auth.authType === "bearer" ||
- request.auth.authType === "oauth-2"
- ) {
- effectiveFinalHeaders.push({
- active: true,
- key: "Authorization",
- value: `Bearer ${parseTemplateString(
- request.auth.token,
- envVariables
- )}`,
- });
- } else if (request.auth.authType === "api-key") {
- const { key, value, addTo } = request.auth;
- if (addTo === "Headers") {
- effectiveFinalHeaders.push({
- active: true,
- key: parseTemplateString(key, envVariables),
- value: parseTemplateString(value, envVariables),
- });
- } else if (addTo === "Query params") {
- effectiveFinalParams.push({
- active: true,
- key: parseTemplateString(key, envVariables),
- value: parseTemplateString(value, envVariables),
- });
- }
- }
- }
- // Parsing final-body with applied ENVs.
- const _effectiveFinalBody = getFinalBodyFromRequest(request, envVariables);
- if (E.isLeft(_effectiveFinalBody)) {
- return _effectiveFinalBody;
- }
- const effectiveFinalBody = _effectiveFinalBody.right;
- if (request.body.contentType)
- effectiveFinalHeaders.push({
- active: true,
- key: "content-type",
- value: request.body.contentType,
- });
- // Parsing final-endpoint with applied ENVs.
- const _effectiveFinalURL = parseTemplateStringE(
- request.endpoint,
- envVariables
- );
- if (E.isLeft(_effectiveFinalURL)) {
- return E.left(
- error({
- code: "PARSING_ERROR",
- data: `${request.endpoint} (${_effectiveFinalURL.left})`,
- })
- );
- }
- const effectiveFinalURL = _effectiveFinalURL.right;
- return E.right({
- ...request,
- effectiveFinalURL,
- effectiveFinalHeaders,
- effectiveFinalParams,
- effectiveFinalBody,
- });
- }
- /**
- * Replaces template variables in request's body from the given set of ENVs,
- * to generate final request body without any template variables.
- * @param request Provides request's body, on which ENVs has to be applied.
- * @param envVariables Provides set of key-value pairs (environment variables),
- * used to parse-out template variables.
- * @returns Final request body without any template variables as value.
- * Or, HoppCLIError in case of error while parsing.
- */
- function getFinalBodyFromRequest(
- request: HoppRESTRequest,
- envVariables: Environment["variables"]
- ): E.Either<HoppCLIError, string | null | FormData> {
- if (request.body.contentType === null) {
- return E.right(null);
- }
- if (request.body.contentType === "application/x-www-form-urlencoded") {
- return pipe(
- request.body.body,
- parseRawKeyValueEntriesE,
- E.map(
- flow(
- RA.toArray,
- /**
- * Filtering out empty keys and non-active pairs.
- */
- A.filter(({ active, key }) => active && !S.isEmpty(key)),
- /**
- * Mapping each key-value to template-string-parser with either on array,
- * which will be resolved in further steps.
- */
- A.map(({ key, value }) => [
- parseTemplateStringE(key, envVariables),
- parseTemplateStringE(value, envVariables),
- ]),
- /**
- * Filtering and mapping only right-eithers for each key-value as [string, string].
- */
- A.filterMap(([key, value]) =>
- E.isRight(key) && E.isRight(value)
- ? O.some([key.right, value.right] as [string, string])
- : O.none
- ),
- tupleToRecord,
- qs.stringify
- )
- ),
- E.mapLeft((e) => error({ code: "PARSING_ERROR", data: e.message }))
- );
- }
- if (request.body.contentType === "multipart/form-data") {
- return pipe(
- request.body.body,
- A.filter((x) => x.key !== "" && x.active), // Remove empty keys
- // Sort files down
- arraySort((a, b) => {
- if (a.isFile) return 1;
- if (b.isFile) return -1;
- return 0;
- }),
- // 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, envVariables),
- value: v as string | Blob,
- }))
- : [
- {
- key: parseTemplateString(x.key, envVariables),
- value: parseTemplateString(x.value, envVariables),
- },
- ]
- ),
- toFormData,
- E.right
- );
- }
- return pipe(
- parseBodyEnvVariablesE(request.body.body, envVariables),
- E.mapLeft((e) =>
- error({
- code: "PARSING_ERROR",
- data: `${request.body.body} (${e})`,
- })
- )
- );
- }
- /**
- * Get pre-request-metrics (stats + duration) object based on existence of
- * PRE_REQUEST_ERROR code in given hopp-error list.
- * @param errors List of errors to check for PRE_REQUEST_ERROR code.
- * @param duration Time taken (in seconds) to execute the pre-request-script.
- * @returns Object containing details of pre-request-script's execution stats
- * i.e., failed/passed data and duration.
- */
- export const getPreRequestMetrics = (
- errors: HoppCLIError[],
- duration: number
- ): PreRequestMetrics =>
- pipe(
- errors,
- A.some(({ code }) => code === "PRE_REQUEST_SCRIPT_ERROR"),
- (hasPreReqErrors) =>
- hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
- (scripts) => <PreRequestMetrics>{ scripts, duration }
- );
|