Files
hoppscotch/packages/hoppscotch-data/src/environment/index.ts

179 lines
3.9 KiB
TypeScript

import * as E from "fp-ts/Either"
import { pipe } from "fp-ts/function"
import { InferredEntity, createVersionedEntity } from "verzod"
import { z } from "zod"
import V0_VERSION from "./v/0"
import V1_VERSION from "./v/1"
const versionedObject = z.object({
v: z.number(),
})
export const Environment = createVersionedEntity({
latestVersion: 1,
versionMap: {
0: V0_VERSION,
1: V1_VERSION,
},
getVersion(data) {
const versionCheck = versionedObject.safeParse(data)
if (versionCheck.success) return versionCheck.data.v
// For V0 we have to check the schema
const result = V0_VERSION.schema.safeParse(data)
return result.success ? 0 : null
},
})
export type Environment = InferredEntity<typeof Environment>
export type EnvironmentVariable = InferredEntity<
typeof Environment
>["variables"][number]
const REGEX_ENV_VAR = /<<([^>]*)>>/g // "<<myVariable>>"
/**
* How much times can we expand environment variables
*/
const ENV_MAX_EXPAND_LIMIT = 10
/**
* Error state when there is a suspected loop while
* recursively expanding variables
*/
const ENV_EXPAND_LOOP = "ENV_EXPAND_LOOP" as const
export const EnvironmentSchemaVersion = 1
export function parseBodyEnvVariablesE(
body: string,
env: Environment["variables"]
) {
let result = body
let depth = 0
while (result.match(REGEX_ENV_VAR) != null && depth <= ENV_MAX_EXPAND_LIMIT) {
result = result.replace(REGEX_ENV_VAR, (key) => {
const found = env.find(
(envVar) => envVar.key === key.replace(/[<>]/g, "")
)
if (found && "value" in found) {
return found.value
}
return key
})
depth++
}
return depth > ENV_MAX_EXPAND_LIMIT
? E.left(ENV_EXPAND_LOOP)
: E.right(result)
}
/**
* @deprecated Use `parseBodyEnvVariablesE` instead.
*/
export const parseBodyEnvVariables = (
body: string,
env: Environment["variables"]
) =>
pipe(
parseBodyEnvVariablesE(body, env),
E.getOrElse(() => body)
)
export function parseTemplateStringE(
str: string,
variables:
| Environment["variables"]
| { secret: true; value: string; key: string }[],
maskValue = false
) {
if (!variables || !str) {
return E.right(str)
}
let result = str
let depth = 0
while (result.match(REGEX_ENV_VAR) != null && depth <= ENV_MAX_EXPAND_LIMIT) {
result = decodeURI(encodeURI(result)).replace(REGEX_ENV_VAR, (_, p1) => {
const variable = variables.find((x) => x && x.key === p1)
if (variable && "value" in variable) {
// Mask the value if it is a secret and explicitly specified
if (variable.secret && maskValue) {
return "*".repeat(
(variable as { secret: true; value: string; key: string }).value
.length
)
}
return variable.value
}
return ""
})
depth++
}
return depth > ENV_MAX_EXPAND_LIMIT
? E.left(ENV_EXPAND_LOOP)
: E.right(result)
}
export type NonSecretEnvironmentVariable = Extract<
EnvironmentVariable,
{ secret: false }
>
export type NonSecretEnvironment = Omit<Environment, "variables"> & {
variables: NonSecretEnvironmentVariable[]
}
/**
* @deprecated Use `parseTemplateStringE` instead
*/
export const parseTemplateString = (
str: string,
variables:
| Environment["variables"]
| { secret: true; value: string; key: string }[],
maskValue = false
) =>
pipe(
parseTemplateStringE(str, variables, maskValue),
E.getOrElse(() => str)
)
export const translateToNewEnvironmentVariables = (
x: any
): Environment["variables"][number] => {
return {
key: x.key,
value: x.value,
secret: false,
}
}
export const translateToNewEnvironment = (x: any): Environment => {
if (x.v && x.v === EnvironmentSchemaVersion) return x
// Legacy
const id = x.id ?? ""
const name = x.name ?? "Untitled"
const variables = (x.variables ?? []).map(translateToNewEnvironmentVariables)
return {
v: EnvironmentSchemaVersion,
id,
name,
variables,
}
}