feat(cli): add support for request variables (#4275)

feat: add support for request variables in the CLI
This commit is contained in:
James George
2024-08-26 06:51:29 -07:00
committed by GitHub
parent 10e8f4ef19
commit 33b0a54af1
8 changed files with 449 additions and 45 deletions

View File

@@ -1,8 +1,8 @@
import {
Environment,
HoppCollection,
EnvironmentVariable,
HoppRESTHeader,
HoppRESTParam,
HoppRESTRequestVariables,
parseTemplateStringE,
} from "@hoppscotch/data";
import axios, { AxiosError } from "axios";
@@ -58,12 +58,12 @@ export const getColorStatusCode = (
* Replaces all template-string with their effective ENV values to generate effective
* request headers/parameters meta-data.
* @param metaData Headers/parameters on which ENVs will be applied.
* @param environment Provides ENV variables for parsing template-string.
* @param resolvedVariables Provides ENV variables for parsing template-string.
* @returns Active, non-empty-key, parsed headers/parameters pairs.
*/
export const getEffectiveFinalMetaData = (
metaData: HoppRESTHeader[] | HoppRESTParam[],
environment: Environment
resolvedVariables: EnvironmentVariable[]
) =>
pipe(
metaData,
@@ -72,11 +72,13 @@ export const getEffectiveFinalMetaData = (
* Selecting only non-empty and active pairs.
*/
A.filter(({ key, active }) => !S.isEmpty(key) && active),
A.map(({ key, value }) => ({
active: true,
key: parseTemplateStringE(key, environment.variables),
value: parseTemplateStringE(value, environment.variables),
})),
A.map(({ key, value }) => {
return {
active: true,
key: parseTemplateStringE(key, resolvedVariables),
value: parseTemplateStringE(value, resolvedVariables),
};
}),
E.fromPredicate(
/**
* Check if every key-value is right either. Else return HoppCLIError with
@@ -253,3 +255,30 @@ export const getResourceContents = async (
return contents;
};
/**
* Processes incoming request variables and environment variables and returns a list
* where active request variables are picked and prioritised over the supplied environment variables.
* Falls back to environment variables for an empty request variable.
*
* @param {HoppRESTRequestVariables} requestVariables - Incoming request variables.
* @param {EnvironmentVariable[]} environmentVariables - Incoming environment variables.
* @returns {EnvironmentVariable[]} The resolved list of variables that conforms to the shape of environment variables.
*/
export const getResolvedVariables = (
requestVariables: HoppRESTRequestVariables,
environmentVariables: EnvironmentVariable[]
): EnvironmentVariable[] => {
const activeRequestVariables = requestVariables
.filter(({ active, value }) => active && value)
.map(({ key, value }) => ({ key, value, secret: false }));
const requestVariableKeys = activeRequestVariables.map(({ key }) => key);
// Request variables have higher priority, hence filtering out environment variables with the same keys
const filteredEnvironmentVariables = environmentVariables.filter(
({ key }) => !requestVariableKeys.includes(key)
);
return [...activeRequestVariables, ...filteredEnvironmentVariables];
};

View File

@@ -1,5 +1,6 @@
import {
Environment,
EnvironmentVariable,
HoppRESTRequest,
parseBodyEnvVariablesE,
parseRawKeyValueEntriesE,
@@ -22,7 +23,7 @@ import { HoppEnvs } from "../types/request";
import { PreRequestMetrics } from "../types/response";
import { isHoppCLIError } from "./checks";
import { arrayFlatMap, arraySort, tupleToRecord } from "./functions/array";
import { getEffectiveFinalMetaData } from "./getters";
import { getEffectiveFinalMetaData, getResolvedVariables } from "./getters";
import { toFormData } from "./mutators";
/**
@@ -80,10 +81,15 @@ export function getEffectiveRESTRequest(
> {
const envVariables = environment.variables;
const resolvedVariables = getResolvedVariables(
request.requestVariables,
envVariables
);
// Parsing final headers with applied ENVs.
const _effectiveFinalHeaders = getEffectiveFinalMetaData(
request.headers,
environment
resolvedVariables
);
if (E.isLeft(_effectiveFinalHeaders)) {
return _effectiveFinalHeaders;
@@ -93,7 +99,7 @@ export function getEffectiveRESTRequest(
// Parsing final parameters with applied ENVs.
const _effectiveFinalParams = getEffectiveFinalMetaData(
request.params,
environment
resolvedVariables
);
if (E.isLeft(_effectiveFinalParams)) {
return _effectiveFinalParams;
@@ -104,8 +110,14 @@ export function getEffectiveRESTRequest(
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);
const username = parseTemplateString(
request.auth.username,
resolvedVariables
);
const password = parseTemplateString(
request.auth.password,
resolvedVariables
);
effectiveFinalHeaders.push({
active: true,
@@ -116,7 +128,7 @@ export function getEffectiveRESTRequest(
effectiveFinalHeaders.push({
active: true,
key: "Authorization",
value: `Bearer ${parseTemplateString(request.auth.token, envVariables)}`,
value: `Bearer ${parseTemplateString(request.auth.token, resolvedVariables)}`,
});
} else if (request.auth.authType === "oauth-2") {
const { addTo } = request.auth;
@@ -125,7 +137,7 @@ export function getEffectiveRESTRequest(
effectiveFinalHeaders.push({
active: true,
key: "Authorization",
value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, envVariables)}`,
value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, resolvedVariables)}`,
});
} else if (addTo === "QUERY_PARAMS") {
effectiveFinalParams.push({
@@ -133,7 +145,7 @@ export function getEffectiveRESTRequest(
key: "access_token",
value: parseTemplateString(
request.auth.grantTypeInfo.token,
envVariables
resolvedVariables
),
});
}
@@ -142,21 +154,24 @@ export function getEffectiveRESTRequest(
if (addTo === "HEADERS") {
effectiveFinalHeaders.push({
active: true,
key: parseTemplateString(key, envVariables),
value: parseTemplateString(value, envVariables),
key: parseTemplateString(key, resolvedVariables),
value: parseTemplateString(value, resolvedVariables),
});
} else if (addTo === "QUERY_PARAMS") {
effectiveFinalParams.push({
active: true,
key: parseTemplateString(key, envVariables),
value: parseTemplateString(value, envVariables),
key: parseTemplateString(key, resolvedVariables),
value: parseTemplateString(value, resolvedVariables),
});
}
}
}
// Parsing final-body with applied ENVs.
const _effectiveFinalBody = getFinalBodyFromRequest(request, envVariables);
const _effectiveFinalBody = getFinalBodyFromRequest(
request,
resolvedVariables
);
if (E.isLeft(_effectiveFinalBody)) {
return _effectiveFinalBody;
}
@@ -175,10 +190,10 @@ export function getEffectiveRESTRequest(
});
}
// Parsing final-endpoint with applied ENVs.
// Parsing final-endpoint with applied ENVs (environment + request variables).
const _effectiveFinalURL = parseTemplateStringE(
request.endpoint,
envVariables
resolvedVariables
);
if (E.isLeft(_effectiveFinalURL)) {
return E.left(
@@ -195,7 +210,7 @@ export function getEffectiveRESTRequest(
if (envVariables.some(({ secret }) => secret)) {
const _effectiveFinalDisplayURL = parseTemplateStringE(
request.endpoint,
envVariables,
resolvedVariables,
true
);
@@ -213,7 +228,7 @@ export function getEffectiveRESTRequest(
effectiveFinalParams,
effectiveFinalBody,
},
updatedEnvs: { global: [], selected: envVariables },
updatedEnvs: { global: [], selected: resolvedVariables },
});
}
@@ -221,14 +236,14 @@ export function getEffectiveRESTRequest(
* 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),
* @param resolvedVariables Provides set of key-value pairs (request + 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"]
resolvedVariables: EnvironmentVariable[]
): E.Either<HoppCLIError, string | null | FormData> {
if (request.body.contentType === null) {
return E.right(null);
@@ -252,8 +267,8 @@ function getFinalBodyFromRequest(
* which will be resolved in further steps.
*/
A.map(({ key, value }) => [
parseTemplateStringE(key, envVariables),
parseTemplateStringE(value, envVariables),
parseTemplateStringE(key, resolvedVariables),
parseTemplateStringE(value, resolvedVariables),
]),
/**
@@ -289,13 +304,13 @@ function getFinalBodyFromRequest(
arrayFlatMap((x) =>
x.isFile
? x.value.map((v) => ({
key: parseTemplateString(x.key, envVariables),
key: parseTemplateString(x.key, resolvedVariables),
value: v as string | Blob,
}))
: [
{
key: parseTemplateString(x.key, envVariables),
value: parseTemplateString(x.value, envVariables),
key: parseTemplateString(x.key, resolvedVariables),
value: parseTemplateString(x.value, resolvedVariables),
},
]
),
@@ -305,7 +320,7 @@ function getFinalBodyFromRequest(
}
return pipe(
parseBodyEnvVariablesE(request.body.body, envVariables),
parseBodyEnvVariablesE(request.body.body, resolvedVariables),
E.mapLeft((e) =>
error({
code: "PARSING_ERROR",

View File

@@ -4,7 +4,7 @@ import {
HoppRESTRequest,
RESTReqSchemaVersion,
} from "@hoppscotch/data";
import axios, { AxiosResponse, Method } from "axios";
import axios, { Method } from "axios";
import * as A from "fp-ts/Array";
import * as E from "fp-ts/Either";
import * as T from "fp-ts/Task";