added files to start parsing functionality of path variables
This commit is contained in:
@@ -35,10 +35,13 @@ import { EditorState, Extension } from "@codemirror/state"
|
|||||||
import clone from "lodash/clone"
|
import clone from "lodash/clone"
|
||||||
import { tooltips } from "@codemirror/tooltip"
|
import { tooltips } from "@codemirror/tooltip"
|
||||||
import { history, historyKeymap } from "@codemirror/history"
|
import { history, historyKeymap } from "@codemirror/history"
|
||||||
|
import { HoppRESTVar } from "@hoppscotch/data"
|
||||||
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
|
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
|
||||||
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
|
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
|
||||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||||
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
|
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
|
||||||
|
import { HoppReactiveVarPlugin } from "~/helpers/editor/extensions/HoppVariable"
|
||||||
|
import { restVars$ } from "~/newstore/RESTSession"
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -46,6 +49,7 @@ const props = withDefaults(
|
|||||||
placeholder: string
|
placeholder: string
|
||||||
styles: string
|
styles: string
|
||||||
envs: { key: string; value: string; source: string }[] | null
|
envs: { key: string; value: string; source: string }[] | null
|
||||||
|
vars: { key: string; value: string }[] | null
|
||||||
focus: boolean
|
focus: boolean
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
}>(),
|
}>(),
|
||||||
@@ -54,6 +58,7 @@ const props = withDefaults(
|
|||||||
placeholder: "",
|
placeholder: "",
|
||||||
styles: "",
|
styles: "",
|
||||||
envs: null,
|
envs: null,
|
||||||
|
vars: null,
|
||||||
focus: false,
|
focus: false,
|
||||||
readonly: false,
|
readonly: false,
|
||||||
}
|
}
|
||||||
@@ -109,6 +114,7 @@ let pastedValue: string | null = null
|
|||||||
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, []) as Ref<
|
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, []) as Ref<
|
||||||
AggregateEnvironment[]
|
AggregateEnvironment[]
|
||||||
>
|
>
|
||||||
|
const aggregateVars = useReadonlyStream(restVars$, []) as Ref<HoppRESTVar[]>
|
||||||
|
|
||||||
const envVars = computed(() =>
|
const envVars = computed(() =>
|
||||||
props.envs
|
props.envs
|
||||||
@@ -120,7 +126,17 @@ const envVars = computed(() =>
|
|||||||
: aggregateEnvs.value
|
: aggregateEnvs.value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const varVars = computed(() =>
|
||||||
|
props.vars
|
||||||
|
? props.vars.map((x) => ({
|
||||||
|
key: x.key,
|
||||||
|
value: x.value,
|
||||||
|
}))
|
||||||
|
: aggregateVars.value
|
||||||
|
)
|
||||||
|
|
||||||
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
|
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
|
||||||
|
const varTooltipPlugin = new HoppReactiveVarPlugin(varVars, view)
|
||||||
|
|
||||||
const initView = (el: any) => {
|
const initView = (el: any) => {
|
||||||
const extensions: Extension = [
|
const extensions: Extension = [
|
||||||
@@ -146,6 +162,7 @@ const initView = (el: any) => {
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
}),
|
}),
|
||||||
envTooltipPlugin,
|
envTooltipPlugin,
|
||||||
|
varTooltipPlugin,
|
||||||
placeholderExt(props.placeholder),
|
placeholderExt(props.placeholder),
|
||||||
EditorView.domEventHandlers({
|
EditorView.domEventHandlers({
|
||||||
paste(ev) {
|
paste(ev) {
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import { watch, Ref } from "@nuxtjs/composition-api"
|
||||||
|
import { Compartment } from "@codemirror/state"
|
||||||
|
import { hoverTooltip } from "@codemirror/tooltip"
|
||||||
|
import {
|
||||||
|
Decoration,
|
||||||
|
EditorView,
|
||||||
|
MatchDecorator,
|
||||||
|
ViewPlugin,
|
||||||
|
} from "@codemirror/view"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { HoppRESTVar, parseTemplateStringE } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
const HOPP_ENVIRONMENT_REGEX = /({{\w+}})/g
|
||||||
|
|
||||||
|
const HOPP_ENV_HIGHLIGHT =
|
||||||
|
"cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight"
|
||||||
|
const HOPP_ENV_HIGHLIGHT_FOUND =
|
||||||
|
"bg-accentDark text-accentContrast hover:bg-accent"
|
||||||
|
const HOPP_ENV_HIGHLIGHT_NOT_FOUND =
|
||||||
|
"bg-red-500 text-accentContrast hover:bg-red-600"
|
||||||
|
|
||||||
|
const cursorTooltipField = (aggregateEnvs: HoppRESTVar[]) =>
|
||||||
|
hoverTooltip(
|
||||||
|
(view, pos, side) => {
|
||||||
|
const { from, to, text } = view.state.doc.lineAt(pos)
|
||||||
|
|
||||||
|
// TODO: When Codemirror 6 allows this to work (not make the
|
||||||
|
// popups appear half of the time) use this implementation
|
||||||
|
// const wordSelection = view.state.wordAt(pos)
|
||||||
|
// if (!wordSelection) return null
|
||||||
|
// const word = view.state.doc.sliceString(
|
||||||
|
// wordSelection.from - 2,
|
||||||
|
// wordSelection.to + 2
|
||||||
|
// )
|
||||||
|
// if (!HOPP_ENVIRONMENT_REGEX.test(word)) return null
|
||||||
|
|
||||||
|
// Tracking the start and the end of the words
|
||||||
|
let start = pos
|
||||||
|
let end = pos
|
||||||
|
|
||||||
|
while (start > from && /\w/.test(text[start - from - 1])) start--
|
||||||
|
while (end < to && /\w/.test(text[end - from])) end++
|
||||||
|
|
||||||
|
if (
|
||||||
|
(start === pos && side < 0) ||
|
||||||
|
(end === pos && side > 0) ||
|
||||||
|
!HOPP_ENVIRONMENT_REGEX.test(
|
||||||
|
text.slice(start - from - 2, end - from + 2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
|
||||||
|
const envValue =
|
||||||
|
aggregateEnvs.find(
|
||||||
|
(env) => env.key === text.slice(start - from, end - from)
|
||||||
|
// env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2)
|
||||||
|
)?.value ?? "not found"
|
||||||
|
|
||||||
|
const result = parseTemplateStringE(envValue, aggregateEnvs)
|
||||||
|
|
||||||
|
const finalEnv = E.isLeft(result) ? "error" : result.right
|
||||||
|
|
||||||
|
return {
|
||||||
|
pos: start,
|
||||||
|
end: to,
|
||||||
|
above: true,
|
||||||
|
arrow: true,
|
||||||
|
create() {
|
||||||
|
const dom = document.createElement("span")
|
||||||
|
const xmp = document.createElement("xmp")
|
||||||
|
xmp.textContent = finalEnv
|
||||||
|
dom.appendChild(xmp)
|
||||||
|
dom.className = "tooltip-theme"
|
||||||
|
return { dom }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// HACK: This is a hack to fix hover tooltip not coming half of the time
|
||||||
|
// https://github.com/codemirror/tooltip/blob/765c463fc1d5afcc3ec93cee47d72606bed27e1d/src/tooltip.ts#L622
|
||||||
|
// Still doesn't fix the not showing up some of the time issue, but this is atleast more consistent
|
||||||
|
{ hoverTime: 1 } as any
|
||||||
|
)
|
||||||
|
|
||||||
|
function checkEnv(env: string, aggregateEnvs: HoppRESTVar[]) {
|
||||||
|
const className = aggregateEnvs.find(
|
||||||
|
(k: { key: string }) => k.key === env.slice(2, -2)
|
||||||
|
)
|
||||||
|
? HOPP_ENV_HIGHLIGHT_FOUND
|
||||||
|
: HOPP_ENV_HIGHLIGHT_NOT_FOUND
|
||||||
|
|
||||||
|
return Decoration.mark({
|
||||||
|
class: `${HOPP_ENV_HIGHLIGHT} ${className}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMatchDecorator = (aggregateEnvs: HoppRESTVar[]) =>
|
||||||
|
new MatchDecorator({
|
||||||
|
regexp: HOPP_ENVIRONMENT_REGEX,
|
||||||
|
decoration: (m) => checkEnv(m[0], aggregateEnvs),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const environmentHighlightStyle = (aggregateEnvs: HoppRESTVar[]) => {
|
||||||
|
const decorator = getMatchDecorator(aggregateEnvs)
|
||||||
|
|
||||||
|
return ViewPlugin.define(
|
||||||
|
(view) => ({
|
||||||
|
decorations: decorator.createDeco(view),
|
||||||
|
update(u) {
|
||||||
|
this.decorations = decorator.updateDeco(u, this.decorations)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
decorations: (v) => v.decorations,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// export class HoppEnvironmentPlugin {
|
||||||
|
// private compartment = new Compartment()
|
||||||
|
//
|
||||||
|
// private envs: AggregateEnvironment[] = []
|
||||||
|
//
|
||||||
|
// constructor(
|
||||||
|
// subscribeToStream: StreamSubscriberFunc,
|
||||||
|
// private editorView: Ref<EditorView | undefined>
|
||||||
|
// ) {
|
||||||
|
// this.envs = getAggregateEnvs()
|
||||||
|
//
|
||||||
|
// subscribeToStream(aggregateEnvs$, (envs) => {
|
||||||
|
// this.envs = envs
|
||||||
|
//
|
||||||
|
// this.editorView.value?.dispatch({
|
||||||
|
// effects: this.compartment.reconfigure([
|
||||||
|
// cursorTooltipField(this.envs),
|
||||||
|
// environmentHighlightStyle(this.envs),
|
||||||
|
// ]),
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// get extension() {
|
||||||
|
// return this.compartment.of([
|
||||||
|
// cursorTooltipField(this.envs),
|
||||||
|
// environmentHighlightStyle(this.envs),
|
||||||
|
// ])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export class HoppReactiveVarPlugin {
|
||||||
|
private compartment = new Compartment()
|
||||||
|
|
||||||
|
private envs: HoppRESTVar[] = []
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
envsRef: Ref<HoppRESTVar[]>,
|
||||||
|
private editorView: Ref<EditorView | undefined>
|
||||||
|
) {
|
||||||
|
watch(
|
||||||
|
envsRef,
|
||||||
|
(envs) => {
|
||||||
|
this.envs = envs
|
||||||
|
|
||||||
|
this.editorView.value?.dispatch({
|
||||||
|
effects: this.compartment.reconfigure([
|
||||||
|
cursorTooltipField(this.envs),
|
||||||
|
environmentHighlightStyle(this.envs),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get extension() {
|
||||||
|
return this.compartment.of([
|
||||||
|
cursorTooltipField(this.envs),
|
||||||
|
environmentHighlightStyle(this.envs),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
HoppRESTHeader,
|
HoppRESTHeader,
|
||||||
HoppRESTParam,
|
HoppRESTParam,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
|
import { parseTemplateStringV } from "@hoppscotch/data/src/pathVariables"
|
||||||
import { arrayFlatMap, arraySort } from "../functional/array"
|
import { arrayFlatMap, arraySort } from "../functional/array"
|
||||||
import { toFormData } from "../functional/formData"
|
import { toFormData } from "../functional/formData"
|
||||||
import { tupleToRecord } from "../functional/record"
|
import { tupleToRecord } from "../functional/record"
|
||||||
@@ -29,6 +30,7 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
effectiveFinalHeaders: { key: string; value: string }[]
|
effectiveFinalHeaders: { key: string; value: string }[]
|
||||||
effectiveFinalParams: { key: string; value: string }[]
|
effectiveFinalParams: { key: string; value: string }[]
|
||||||
effectiveFinalBody: FormData | string | null
|
effectiveFinalBody: FormData | string | null
|
||||||
|
effectiveFinalVars: { key: string; value: string }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -298,15 +300,21 @@ export function getEffectiveRESTRequest(
|
|||||||
value: parseTemplateString(x.value, envVariables),
|
value: parseTemplateString(x.value, envVariables),
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
const effectiveFinalVars = request.vars
|
||||||
|
|
||||||
const effectiveFinalBody = getFinalBodyFromRequest(request, envVariables)
|
const effectiveFinalBody = getFinalBodyFromRequest(request, envVariables)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...request,
|
...request,
|
||||||
effectiveFinalURL: parseTemplateString(request.endpoint, envVariables),
|
effectiveFinalURL: parseTemplateStringV(
|
||||||
|
request.endpoint,
|
||||||
|
envVariables,
|
||||||
|
request.vars
|
||||||
|
),
|
||||||
effectiveFinalHeaders,
|
effectiveFinalHeaders,
|
||||||
effectiveFinalParams,
|
effectiveFinalParams,
|
||||||
effectiveFinalBody,
|
effectiveFinalBody,
|
||||||
|
effectiveFinalVars,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,12 +30,7 @@ export const getDefaultRESTRequest = (): HoppRESTRequest => ({
|
|||||||
endpoint: "https://echo.hoppscotch.io",
|
endpoint: "https://echo.hoppscotch.io",
|
||||||
name: "Untitled request",
|
name: "Untitled request",
|
||||||
params: [],
|
params: [],
|
||||||
vars: [
|
vars: [],
|
||||||
{
|
|
||||||
key: "amount",
|
|
||||||
value: "23",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
headers: [],
|
headers: [],
|
||||||
method: "GET",
|
method: "GET",
|
||||||
auth: {
|
auth: {
|
||||||
|
|||||||
@@ -9,7 +9,14 @@ export type Environment = {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Variables = {
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
}[]
|
||||||
|
|
||||||
|
|
||||||
const REGEX_ENV_VAR = /<<([^>]*)>>/g // "<<myVariable>>"
|
const REGEX_ENV_VAR = /<<([^>]*)>>/g // "<<myVariable>>"
|
||||||
|
const REGEX_PATHVAR = /{{([^>]*)}}/g // "{{myVariable}}"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much times can we expand environment variables
|
* How much times can we expand environment variables
|
||||||
@@ -59,9 +66,9 @@ export const parseBodyEnvVariables = (
|
|||||||
|
|
||||||
export function parseTemplateStringE(
|
export function parseTemplateStringE(
|
||||||
str: string,
|
str: string,
|
||||||
variables: Environment["variables"]
|
variables: Environment["variables"],
|
||||||
) {
|
) {
|
||||||
if (!variables || !str) {
|
if (!variables || !str ) {
|
||||||
return E.right(str)
|
return E.right(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
76
packages/hoppscotch-data/src/pathVariables.ts
Normal file
76
packages/hoppscotch-data/src/pathVariables.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import {parseTemplateStringE} from "./environment";
|
||||||
|
|
||||||
|
export type Environment = {
|
||||||
|
name: string
|
||||||
|
variables: {
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Variables = {
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
}[]
|
||||||
|
|
||||||
|
const REGEX_ENV_VAR = /<<([^>]*)>>/g // "<<myVariable>>"
|
||||||
|
const REGEX_PATHVAR = /{{([^>]*)}}/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 function parseTemplateStringEV(
|
||||||
|
str: string,
|
||||||
|
variables: Environment["variables"],
|
||||||
|
pathVariables: Variables
|
||||||
|
) {
|
||||||
|
if (!variables || !str || !pathVariables) {
|
||||||
|
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) => variables.find((x) => x.key === p1)?.value || ""
|
||||||
|
)
|
||||||
|
depth++
|
||||||
|
}
|
||||||
|
|
||||||
|
while (result.match(REGEX_PATHVAR) != null && depth <= ENV_MAX_EXPAND_LIMIT) {
|
||||||
|
result = decodeURI(encodeURI(result)).replace(
|
||||||
|
REGEX_PATHVAR,
|
||||||
|
(_, p1) => pathVariables.find((x) => x.key === p1)?.value || ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return depth > ENV_MAX_EXPAND_LIMIT
|
||||||
|
? E.left(ENV_EXPAND_LOOP)
|
||||||
|
: E.right(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `parseTemplateStringE` instead
|
||||||
|
*/
|
||||||
|
export const parseTemplateStringV = (
|
||||||
|
str: string,
|
||||||
|
variables: Environment["variables"],
|
||||||
|
pathVariables: Variables
|
||||||
|
) =>
|
||||||
|
pipe(
|
||||||
|
parseTemplateStringEV(str, variables, pathVariables),
|
||||||
|
E.getOrElse(() => str)
|
||||||
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user