378 lines
12 KiB
TypeScript
378 lines
12 KiB
TypeScript
import { getI18n } from "~/modules/i18n"
|
|
import {
|
|
InspectionService,
|
|
Inspector,
|
|
InspectorLocation,
|
|
InspectorResult,
|
|
} from ".."
|
|
import { Service } from "dioc"
|
|
import { Ref, markRaw } from "vue"
|
|
import IconPlusCircle from "~icons/lucide/plus-circle"
|
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
|
import {
|
|
AggregateEnvironment,
|
|
aggregateEnvsWithSecrets$,
|
|
getCurrentEnvironment,
|
|
getSelectedEnvironmentType,
|
|
} from "~/newstore/environments"
|
|
import { invokeAction } from "~/helpers/actions"
|
|
import { computed } from "vue"
|
|
import { useStreamStatic } from "~/composables/stream"
|
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
|
import { RESTTabService } from "~/services/tab/rest"
|
|
|
|
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
|
|
|
const isENVInString = (str: string) => {
|
|
return HOPP_ENVIRONMENT_REGEX.test(str)
|
|
}
|
|
|
|
/**
|
|
* This inspector is responsible for inspecting the environment variables of a input.
|
|
* It checks if the environment variables are defined in the environment.
|
|
* It also provides an action to add the environment variable.
|
|
*
|
|
* NOTE: Initializing this service registers it as a inspector with the Inspection Service.
|
|
*/
|
|
export class EnvironmentInspectorService extends Service implements Inspector {
|
|
public static readonly ID = "ENVIRONMENT_INSPECTOR_SERVICE"
|
|
|
|
private t = getI18n()
|
|
|
|
public readonly inspectorID = "environment"
|
|
|
|
private readonly inspection = this.bind(InspectionService)
|
|
private readonly secretEnvs = this.bind(SecretEnvironmentService)
|
|
private readonly restTabs = this.bind(RESTTabService)
|
|
|
|
private aggregateEnvsWithSecrets = useStreamStatic(
|
|
aggregateEnvsWithSecrets$,
|
|
[],
|
|
() => {
|
|
/* noop */
|
|
}
|
|
)[0]
|
|
|
|
constructor() {
|
|
super()
|
|
|
|
this.inspection.registerInspector(this)
|
|
}
|
|
|
|
/**
|
|
* Validates the environment variables in the target array
|
|
* @param target The target array to validate
|
|
* @param locations The location where results are to be displayed
|
|
* @returns The results array containing the results of the validation
|
|
*/
|
|
private validateEnvironmentVariables = (
|
|
target: any[],
|
|
locations: InspectorLocation
|
|
) => {
|
|
const newErrors: InspectorResult[] = []
|
|
|
|
const currentTab = this.restTabs.currentActiveTab.value
|
|
|
|
const environmentVariables = [
|
|
...currentTab.document.request.requestVariables,
|
|
...this.aggregateEnvsWithSecrets.value,
|
|
]
|
|
|
|
const envKeys = environmentVariables.map((e) => e.key)
|
|
|
|
target.forEach((element, index) => {
|
|
if (isENVInString(element)) {
|
|
const extractedEnv = element.match(HOPP_ENVIRONMENT_REGEX)
|
|
|
|
if (extractedEnv) {
|
|
extractedEnv.forEach((exEnv: string) => {
|
|
const formattedExEnv = exEnv.slice(2, -2)
|
|
const itemLocation: InspectorLocation = {
|
|
type: locations.type,
|
|
position:
|
|
locations.type === "url" ||
|
|
locations.type === "body" ||
|
|
locations.type === "response"
|
|
? "key"
|
|
: locations.position,
|
|
index: index,
|
|
key: element,
|
|
}
|
|
if (!envKeys.includes(formattedExEnv)) {
|
|
newErrors.push({
|
|
id: `environment-not-found-${newErrors.length}`,
|
|
text: {
|
|
type: "text",
|
|
text: this.t("inspections.environment.not_found", {
|
|
environment: exEnv,
|
|
}),
|
|
},
|
|
icon: markRaw(IconPlusCircle),
|
|
action: {
|
|
text: this.t("inspections.environment.add_environment"),
|
|
apply: () => {
|
|
invokeAction("modals.environment.add", {
|
|
envName: formattedExEnv,
|
|
variableName: "",
|
|
})
|
|
},
|
|
},
|
|
severity: 3,
|
|
isApplicable: true,
|
|
locations: itemLocation,
|
|
doc: {
|
|
text: this.t("action.learn_more"),
|
|
link: "https://docs.hoppscotch.io/documentation/features/inspections",
|
|
},
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
return newErrors
|
|
}
|
|
|
|
/**
|
|
* Transforms the environment list to a list with unique keys with value
|
|
* @param envs The environment list to be transformed
|
|
* @returns The transformed environment list with keys with value
|
|
*/
|
|
private filterNonEmptyEnvironmentVariables = (
|
|
envs: AggregateEnvironment[]
|
|
): AggregateEnvironment[] => {
|
|
const envsMap = new Map<string, AggregateEnvironment>()
|
|
|
|
envs.forEach((env) => {
|
|
if (envsMap.has(env.key)) {
|
|
const existingEnv = envsMap.get(env.key)
|
|
|
|
if (existingEnv?.value === "" && env.value !== "") {
|
|
envsMap.set(env.key, env)
|
|
}
|
|
} else {
|
|
envsMap.set(env.key, env)
|
|
}
|
|
})
|
|
|
|
return Array.from(envsMap.values())
|
|
}
|
|
|
|
/**
|
|
* Checks if the environment variables in the target array are empty
|
|
* @param target The target array to validate
|
|
* @param locations The location where results are to be displayed
|
|
* @returns The results array containing the results of the validation
|
|
*/
|
|
private validateEmptyEnvironmentVariables = (
|
|
target: any[],
|
|
locations: InspectorLocation
|
|
) => {
|
|
const newErrors: InspectorResult[] = []
|
|
|
|
target.forEach((element, index) => {
|
|
if (isENVInString(element)) {
|
|
const extractedEnv = element.match(HOPP_ENVIRONMENT_REGEX)
|
|
|
|
if (extractedEnv) {
|
|
extractedEnv.forEach((exEnv: string) => {
|
|
const formattedExEnv = exEnv.slice(2, -2)
|
|
const currentSelectedEnvironment = getCurrentEnvironment()
|
|
|
|
const currentTab = this.restTabs.currentActiveTab.value
|
|
|
|
const environmentVariables =
|
|
this.filterNonEmptyEnvironmentVariables([
|
|
...currentTab.document.request.requestVariables.map((env) => ({
|
|
...env,
|
|
secret: false,
|
|
sourceEnv: "RequestVariable",
|
|
})),
|
|
...this.aggregateEnvsWithSecrets.value,
|
|
])
|
|
|
|
environmentVariables.forEach((env) => {
|
|
const hasSecretEnv = this.secretEnvs.hasSecretValue(
|
|
env.sourceEnv !== "Global"
|
|
? currentSelectedEnvironment.id
|
|
: "Global",
|
|
env.key
|
|
)
|
|
|
|
if (env.key === formattedExEnv) {
|
|
if (env.secret ? !hasSecretEnv : env.value === "") {
|
|
const itemLocation: InspectorLocation = {
|
|
type: locations.type,
|
|
position:
|
|
locations.type === "url" ||
|
|
locations.type === "body" ||
|
|
locations.type === "response"
|
|
? "key"
|
|
: locations.position,
|
|
index: index,
|
|
key: element,
|
|
}
|
|
|
|
const currentEnvironmentType = getSelectedEnvironmentType()
|
|
|
|
let invokeActionType:
|
|
| "modals.my.environment.edit"
|
|
| "modals.team.environment.edit"
|
|
| "modals.global.environment.update" =
|
|
"modals.my.environment.edit"
|
|
|
|
if (env.sourceEnv === "Global") {
|
|
invokeActionType = "modals.global.environment.update"
|
|
} else if (currentEnvironmentType === "MY_ENV") {
|
|
invokeActionType = "modals.my.environment.edit"
|
|
} else if (currentEnvironmentType === "TEAM_ENV") {
|
|
invokeActionType = "modals.team.environment.edit"
|
|
} else {
|
|
invokeActionType = "modals.my.environment.edit"
|
|
}
|
|
|
|
newErrors.push({
|
|
id: `environment-empty-${newErrors.length}`,
|
|
text: {
|
|
type: "text",
|
|
text: this.t("inspections.environment.empty_value", {
|
|
variable: exEnv,
|
|
}),
|
|
},
|
|
icon: markRaw(IconPlusCircle),
|
|
action: {
|
|
text: this.t(
|
|
"inspections.environment.add_environment_value"
|
|
),
|
|
apply: () => {
|
|
if (env.sourceEnv === "RequestVariable") {
|
|
currentTab.document.optionTabPreference =
|
|
"requestVariables"
|
|
} else {
|
|
invokeAction(invokeActionType, {
|
|
envName:
|
|
env.sourceEnv === "Global"
|
|
? "Global"
|
|
: currentSelectedEnvironment.name,
|
|
variableName: formattedExEnv,
|
|
isSecret: env.secret,
|
|
})
|
|
}
|
|
},
|
|
},
|
|
severity: 2,
|
|
isApplicable: true,
|
|
locations: itemLocation,
|
|
doc: {
|
|
text: this.t("action.learn_more"),
|
|
link: "https://docs.hoppscotch.io/documentation/features/inspections",
|
|
},
|
|
})
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
return newErrors
|
|
}
|
|
|
|
getInspections(req: Readonly<Ref<HoppRESTRequest>>) {
|
|
return computed(() => {
|
|
const results: InspectorResult[] = []
|
|
|
|
const headers = req.value.headers
|
|
|
|
const params = req.value.params
|
|
|
|
/**
|
|
* Validate the environment variables in the URL
|
|
*/
|
|
const url = req.value.endpoint
|
|
|
|
results.push(
|
|
...this.validateEnvironmentVariables([url], {
|
|
type: "url",
|
|
})
|
|
)
|
|
results.push(
|
|
...this.validateEmptyEnvironmentVariables([url], {
|
|
type: "url",
|
|
})
|
|
)
|
|
|
|
/**
|
|
* Validate the environment variables in the headers
|
|
*/
|
|
const headerKeys = Object.values(headers).map((header) => header.key)
|
|
|
|
results.push(
|
|
...this.validateEnvironmentVariables(headerKeys, {
|
|
type: "header",
|
|
position: "key",
|
|
})
|
|
)
|
|
results.push(
|
|
...this.validateEmptyEnvironmentVariables(headerKeys, {
|
|
type: "header",
|
|
position: "key",
|
|
})
|
|
)
|
|
|
|
const headerValues = Object.values(headers).map((header) => header.value)
|
|
|
|
results.push(
|
|
...this.validateEnvironmentVariables(headerValues, {
|
|
type: "header",
|
|
position: "value",
|
|
})
|
|
)
|
|
results.push(
|
|
...this.validateEmptyEnvironmentVariables(headerValues, {
|
|
type: "header",
|
|
position: "value",
|
|
})
|
|
)
|
|
|
|
/**
|
|
* Validate the environment variables in the parameters
|
|
*/
|
|
const paramsKeys = Object.values(params).map((param) => param.key)
|
|
|
|
results.push(
|
|
...this.validateEnvironmentVariables(paramsKeys, {
|
|
type: "parameter",
|
|
position: "key",
|
|
})
|
|
)
|
|
results.push(
|
|
...this.validateEmptyEnvironmentVariables(paramsKeys, {
|
|
type: "parameter",
|
|
position: "key",
|
|
})
|
|
)
|
|
|
|
const paramsValues = Object.values(params).map((param) => param.value)
|
|
|
|
results.push(
|
|
...this.validateEnvironmentVariables(paramsValues, {
|
|
type: "parameter",
|
|
position: "value",
|
|
})
|
|
)
|
|
|
|
results.push(
|
|
...this.validateEmptyEnvironmentVariables(paramsValues, {
|
|
type: "parameter",
|
|
position: "value",
|
|
})
|
|
)
|
|
|
|
return results
|
|
})
|
|
}
|
|
}
|