From 5713e19c239a9b890c24674357ee2da2be7b0759 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 3 Sep 2024 21:02:22 +0530 Subject: [PATCH] Collection Variables Support (#2963) * Update package.json * Update package.json * Update package.json * Update package.json * draft: collection varaibles * reverted jest command * precedence update * feat: updates * feat: updates * feat: updates * feat: pre-vars values as strings * feat: updates --------- Co-authored-by: lohit Co-authored-by: lohit --- package-lock.json | 53 +++--- .../CollectionSettings/Vars/StyledWrapper.js | 9 + .../Vars/VarsTable/StyledWrapper.js | 56 ++++++ .../Vars/VarsTable/index.js | 162 ++++++++++++++++++ .../CollectionSettings/Vars/index.js | 32 ++++ .../components/CollectionSettings/index.js | 7 + .../FolderSettings/Headers/index.js | 1 + .../FolderSettings/Vars/VarsTable/index.js | 1 + .../Assertions/AssertionRow/index.js | 1 + .../RequestPane/Vars/VarsTable/index.js | 1 + .../ReduxStore/slices/collections/index.js | 68 ++++++++ .../bruno-app/src/utils/collections/index.js | 38 ++-- .../src/runner/run-single-request.js | 16 +- .../bruno-electron/src/ipc/network/index.js | 20 +-- .../src/ipc/network/interpolate-vars.js | 14 +- .../src/ipc/network/prepare-request.js | 73 ++++---- packages/bruno-js/src/bru.js | 18 +- packages/bruno-js/src/interpolate-string.js | 4 +- .../bruno-js/src/runtime/assert-runtime.js | 16 +- .../bruno-js/src/runtime/script-runtime.js | 8 +- packages/bruno-js/src/runtime/test-runtime.js | 4 +- packages/bruno-js/src/runtime/vars-runtime.js | 43 +---- .../bruno-js/src/sandbox/quickjs/shims/bru.js | 12 ++ .../bruno-tests/collection/collection.bru | 6 + .../js/data types - request vars.bru | 41 ----- .../string interpolation/folder.bru | 8 + .../string interpolation/pre-vars.bru | 48 ++++++ 27 files changed, 553 insertions(+), 207 deletions(-) create mode 100644 packages/bruno-app/src/components/CollectionSettings/Vars/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Vars/index.js create mode 100644 packages/bruno-tests/collection/string interpolation/folder.bru create mode 100644 packages/bruno-tests/collection/string interpolation/pre-vars.bru diff --git a/package-lock.json b/package-lock.json index 01f9bf787..3f0f8ae9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ }, "node_modules/@ampproject/remapping": { "version": "2.3.0", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -670,6 +671,7 @@ }, "node_modules/@babel/compat-data": { "version": "7.25.2", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -677,6 +679,7 @@ }, "node_modules/@babel/core": { "version": "7.25.2", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -740,6 +743,7 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.25.2", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.25.2", @@ -754,6 +758,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { "version": "5.1.1", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -761,6 +766,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", + "dev": true, "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { @@ -839,6 +845,7 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.25.2", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.24.7", @@ -905,6 +912,7 @@ }, "node_modules/@babel/helper-simple-access": { "version": "7.24.7", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.24.7", @@ -942,6 +950,7 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.24.8", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -962,6 +971,7 @@ }, "node_modules/@babel/helpers": { "version": "7.25.0", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.25.0", @@ -3644,37 +3654,6 @@ "node": ">=12" } }, - "node_modules/@n8n/vm2": { - "version": "3.9.25", - "resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.25.tgz", - "integrity": "sha512-qoGLFzyHBW7HKpwXkl05QKsIh3GkDw6lOiTOWYlUDnOIQ1b7EgM+O5EMjrMGy7r+kz52+Q7o6GLxBIcxVI8rEg==", - "license": "MIT", - "peer": true, - "dependencies": { - "acorn": "^8.7.0", - "acorn-walk": "^8.2.0" - }, - "bin": { - "vm2": "bin/vm2" - }, - "engines": { - "node": ">=18.10", - "pnpm": ">=9.6" - } - }, - "node_modules/@n8n/vm2/node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/@next/env": { "version": "12.3.3", "license": "MIT" @@ -4751,6 +4730,7 @@ }, "node_modules/@types/linkify-it": { "version": "5.0.0", + "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -4759,6 +4739,7 @@ }, "node_modules/@types/markdown-it": { "version": "12.2.3", + "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "*", @@ -4767,6 +4748,7 @@ }, "node_modules/@types/mdurl": { "version": "2.0.0", + "dev": true, "license": "MIT" }, "node_modules/@types/minimatch": { @@ -6240,6 +6222,7 @@ }, "node_modules/browserslist": { "version": "4.23.3", + "dev": true, "funding": [ { "type": "opencollective", @@ -7238,6 +7221,7 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -8477,6 +8461,7 @@ }, "node_modules/electron-to-chromium": { "version": "1.5.11", + "dev": true, "license": "ISC" }, "node_modules/electron-util": { @@ -9438,6 +9423,7 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -13186,6 +13172,7 @@ }, "node_modules/node-releases": { "version": "2.0.18", + "dev": true, "license": "MIT" }, "node_modules/node-vault": { @@ -16201,6 +16188,7 @@ }, "node_modules/semver": { "version": "6.3.1", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -17663,6 +17651,7 @@ }, "node_modules/update-browserslist-db": { "version": "1.1.0", + "dev": true, "funding": [ { "type": "opencollective", @@ -18633,7 +18622,7 @@ }, "packages/bruno-electron": { "name": "bruno", - "version": "v1.26.1", + "version": "v1.27.0", "dependencies": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", diff --git a/packages/bruno-app/src/components/CollectionSettings/Vars/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Vars/StyledWrapper.js new file mode 100644 index 000000000..44b01b464 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Vars/StyledWrapper.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + div.title { + color: var(--color-tab-inactive); + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/StyledWrapper.js new file mode 100644 index 000000000..efacc8288 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/StyledWrapper.js @@ -0,0 +1,56 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + table { + width: 100%; + border-collapse: collapse; + font-weight: 600; + table-layout: fixed; + + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + } + + thead { + color: ${(props) => props.theme.table.thead.color}; + font-size: 0.8125rem; + user-select: none; + } + td { + padding: 6px 10px; + + &:nth-child(1) { + width: 30%; + } + + &:nth-child(3) { + width: 70px; + } + } + } + + .btn-add-var { + font-size: 0.8125rem; + } + + input[type='text'] { + width: 100%; + border: solid 1px transparent; + outline: none !important; + background-color: inherit; + + &:focus { + outline: none !important; + border: solid 1px transparent; + } + } + + input[type='checkbox'] { + cursor: pointer; + position: relative; + top: 1px; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js b/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js new file mode 100644 index 000000000..950076b60 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js @@ -0,0 +1,162 @@ +import React from 'react'; +import cloneDeep from 'lodash/cloneDeep'; +import { IconTrash } from '@tabler/icons'; +import { useDispatch } from 'react-redux'; +import { useTheme } from 'providers/Theme'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import SingleLineEditor from 'components/SingleLineEditor'; +import InfoTip from 'components/InfoTip'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import { variableNameRegex } from 'utils/common/regex'; +import { + addCollectionVar, + deleteCollectionVar, + updateCollectionVar +} from 'providers/ReduxStore/slices/collections/index'; + +const VarsTable = ({ collection, vars, varType }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const addVar = () => { + dispatch( + addCollectionVar({ + collectionUid: collection.uid, + type: varType + }) + ); + }; + + const onSave = () => dispatch(saveCollectionRoot(collection.uid)); + const handleVarChange = (e, v, type) => { + const _var = cloneDeep(v); + switch (type) { + case 'name': { + const value = e.target.value; + + if (variableNameRegex.test(value) === false) { + toast.error( + 'Variable contains invalid characters! Variables must only contain alpha-numeric characters, "-", "_", "."' + ); + return; + } + + _var.name = value; + break; + } + case 'value': { + _var.value = e.target.value; + break; + } + case 'enabled': { + _var.enabled = e.target.checked; + break; + } + } + dispatch( + updateCollectionVar({ + type: varType, + var: _var, + collectionUid: collection.uid + }) + ); + }; + + const handleRemoveVar = (_var) => { + dispatch( + deleteCollectionVar({ + type: varType, + varUid: _var.uid, + collectionUid: collection.uid + }) + ); + }; + + return ( + + + + + + {varType === 'request' ? ( + + ) : ( + + )} + + + + + {vars && vars.length + ? vars.map((_var) => { + return ( + + + + + + ); + }) + : null} + +
Name +
+ Value + +
+
+
+ Expr + +
+
+ handleVarChange(e, _var, 'name')} + /> + + + handleVarChange( + { + target: { + value: newValue + } + }, + _var, + 'value' + ) + } + collection={collection} + /> + +
+ handleVarChange(e, _var, 'enabled')} + /> + +
+
+ +
+ ); +}; +export default VarsTable; diff --git a/packages/bruno-app/src/components/CollectionSettings/Vars/index.js b/packages/bruno-app/src/components/CollectionSettings/Vars/index.js new file mode 100644 index 000000000..fae3ed613 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Vars/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import get from 'lodash/get'; +import VarsTable from './VarsTable'; +import StyledWrapper from './StyledWrapper'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import { useDispatch } from 'react-redux'; + +const Vars = ({ collection }) => { + const dispatch = useDispatch(); + const requestVars = get(collection, 'root.request.vars.req', []); + const responseVars = get(collection, 'root.request.vars.res', []); + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + return ( + +
+
Pre Request
+ +
+
+
Post Response
+ +
+
+ +
+
+ ); +}; + +export default Vars; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index ebf86f724..d7698e26c 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -16,6 +16,7 @@ import Docs from './Docs'; import Presets from './Presets'; import Info from './Info'; import StyledWrapper from './StyledWrapper'; +import Vars from './Vars/index'; const CollectionSettings = ({ collection }) => { const dispatch = useDispatch(); @@ -77,6 +78,9 @@ const CollectionSettings = ({ collection }) => { case 'headers': { return ; } + case 'vars': { + return ; + } case 'auth': { return ; } @@ -123,6 +127,9 @@ const CollectionSettings = ({ collection }) => {
setTab('headers')}> Headers
+
setTab('vars')}> + Vars +
setTab('auth')}> Auth
diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/index.js b/packages/bruno-app/src/components/FolderSettings/Headers/index.js index 550a835c2..0f6e05f1f 100644 --- a/packages/bruno-app/src/components/FolderSettings/Headers/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Headers/index.js @@ -116,6 +116,7 @@ const Headers = ({ collection, folder }) => { ) } collection={collection} + item={folder} /> diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js index c3414170e..d0a77de44 100644 --- a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js @@ -130,6 +130,7 @@ const VarsTable = ({ folder, collection, vars, varType }) => { ) } collection={collection} + item={folder} /> diff --git a/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js b/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js index 375fa0ec4..38250f8ea 100644 --- a/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js +++ b/packages/bruno-app/src/components/RequestPane/Assertions/AssertionRow/index.js @@ -191,6 +191,7 @@ const AssertionRow = ({ } onRun={handleRun} collection={collection} + item={item} /> ) : ( diff --git a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js index 1ed01da24..84f040c6e 100644 --- a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js +++ b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js @@ -132,6 +132,7 @@ const VarsTable = ({ item, collection, vars, varType }) => { } onRun={handleRun} collection={collection} + item={item} /> diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index ca66ac2ea..63e49360c 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1302,6 +1302,71 @@ export const collectionsSlice = createSlice({ set(collection, 'root.request.headers', headers); } }, + addCollectionVar: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const type = action.payload.type; + if (collection) { + if (type === 'request') { + const vars = get(collection, 'root.request.vars.req', []); + vars.push({ + uid: uuid(), + name: '', + value: '', + enabled: true + }); + set(collection, 'root.request.vars.req', vars); + } else if (type === 'response') { + const vars = get(collection, 'root.request.vars.res', []); + vars.push({ + uid: uuid(), + name: '', + value: '', + enabled: true + }); + set(collection, 'root.request.vars.res', vars); + } + } + }, + updateCollectionVar: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const type = action.payload.type; + if (type === 'request') { + let vars = get(collection, 'root.request.vars.req', []); + const _var = find(vars, (h) => h.uid === action.payload.var.uid); + if (_var) { + _var.name = action.payload.var.name; + _var.value = action.payload.var.value; + _var.description = action.payload.var.description; + _var.enabled = action.payload.var.enabled; + } + set(collection, 'root.request.vars.req', vars); + } else if (type === 'response') { + let vars = get(collection, 'root.request.vars.res', []); + const _var = find(vars, (h) => h.uid === action.payload.var.uid); + if (_var) { + _var.name = action.payload.var.name; + _var.value = action.payload.var.value; + _var.description = action.payload.var.description; + _var.enabled = action.payload.var.enabled; + } + set(collection, 'root.request.vars.res', vars); + } + }, + deleteCollectionVar: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const type = action.payload.type; + if (collection) { + if (type === 'request') { + let vars = get(collection, 'root.request.vars.req', []); + vars = filter(vars, (h) => h.uid !== action.payload.varUid); + set(collection, 'root.request.vars.req', vars); + } else if (type === 'response') { + let vars = get(collection, 'root.request.vars.res', []); + vars = filter(vars, (h) => h.uid !== action.payload.varUid); + set(collection, 'root.request.vars.res', vars); + } + } + }, collectionAddFileEvent: (state, action) => { const file = action.payload.file; const isCollectionRoot = file.meta.collectionRoot ? true : false; @@ -1694,6 +1759,9 @@ export const { addCollectionHeader, updateCollectionHeader, deleteCollectionHeader, + addCollectionVar, + updateCollectionVar, + deleteCollectionVar, updateCollectionAuthMode, updateCollectionAuth, updateCollectionRequestScript, diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 7dc9f2e57..d872a6685 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -787,24 +787,25 @@ export const getTotalRequestCountInCollection = (collection) => { }; export const getAllVariables = (collection, item) => { - const environmentVariables = getEnvironmentVariables(collection); - let requestVariables = {}; - if (item?.request) { - const requestTreePath = getTreePathFromCollectionToItem(collection, item); - requestVariables = mergeFolderLevelVars(item?.request, requestTreePath); - } + const envVariables = getEnvironmentVariables(collection); + const requestTreePath = getTreePathFromCollectionToItem(collection, item); + let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath); const pathParams = getPathParams(item); + const { processEnvVariables = {}, runtimeVariables = {} } = collection; + return { - ...environmentVariables, + ...collectionVariables, + ...envVariables, + ...folderVariables, ...requestVariables, - ...collection.runtimeVariables, + ...runtimeVariables, pathParams: { ...pathParams }, process: { env: { - ...collection.processEnvVariables + ...processEnvVariables } } }; @@ -831,14 +832,22 @@ const getTreePathFromCollectionToItem = (collection, _item) => { return path; }; -const mergeFolderLevelVars = (request, requestTreePath = []) => { +const mergeVars = (collection, requestTreePath = []) => { + let collectionVariables = {}; + let folderVariables = {}; let requestVariables = {}; + let collectionRequestVars = get(collection, 'root.request.vars.req', []); + collectionRequestVars.forEach((_var) => { + if (_var.enabled) { + collectionVariables[_var.name] = _var.value; + } + }); for (let i of requestTreePath) { if (i.type === 'folder') { let vars = get(i, 'root.request.vars.req', []); vars.forEach((_var) => { if (_var.enabled) { - requestVariables[_var.name] = _var.value; + folderVariables[_var.name] = _var.value; } }); } else { @@ -850,6 +859,9 @@ const mergeFolderLevelVars = (request, requestTreePath = []) => { }); } } - - return requestVariables; + return { + collectionVariables, + folderVariables, + requestVariables + }; }; diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index b77c7f61e..b260f6be9 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -60,20 +60,6 @@ const runSingleRequest = async function ( request.data = form; } - // run pre-request vars - const preRequestVars = get(bruJson, 'request.vars.req'); - if (preRequestVars?.length) { - const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime }); - varsRuntime.runPreRequestVars( - preRequestVars, - request, - envVariables, - runtimeVariables, - collectionPath, - processEnvVars - ); - } - // run pre request script const requestScriptFile = compact([ get(collectionRoot, 'request.script.req'), @@ -276,7 +262,7 @@ const runSingleRequest = async function ( console.log( chalk.green(stripExtension(filename)) + - chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`) + chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`) ); // run post-response vars diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index bbdcdb55b..4e2c0a451 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -290,10 +290,10 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) => // Filter out ZWNBSP character // https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d data = data.replace(/^\uFEFF/, ''); - if(!disableParsingResponseJson) { + if (!disableParsingResponseJson) { data = JSON.parse(data); } - } catch {} + } catch { } return { data, dataBuffer }; }; @@ -319,20 +319,6 @@ const registerNetworkIpc = (mainWindow) => { processEnvVars, scriptingConfig ) => { - // run pre-request vars - const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars?.length) { - const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime }); - varsRuntime.runPreRequestVars( - preRequestVars, - request, - envVars, - runtimeVariables, - collectionPath, - processEnvVars - ); - } - // run pre-request script let scriptResult; const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL); @@ -1160,7 +1146,7 @@ const registerNetworkIpc = (mainWindow) => { try { const disposition = contentDispositionParser.parse(contentDisposition); return disposition && disposition.parameters['filename']; - } catch (error) {} + } catch (error) { } }; const getFileNameFromUrlPath = () => { diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 899b3d0f2..b6aeaa078 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -12,15 +12,17 @@ const getContentType = (headers = {}) => { return contentType; }; -const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEnvVars = {}) => { +const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => { + const collectionVariables = request?.collectionVariables || {}; + const folderVariables = request?.folderVariables || {}; const requestVariables = request?.requestVariables || {}; // we clone envVars because we don't want to modify the original object - envVars = cloneDeep(envVars); + envVariables = cloneDeep(envVariables); // envVars can inturn have values as {{process.env.VAR_NAME}} // so we need to interpolate envVars first with processEnvVars - forOwn(envVars, (value, key) => { - envVars[key] = interpolate(value, { + forOwn(envVariables, (value, key) => { + envVariables[key] = interpolate(value, { process: { env: { ...processEnvVars @@ -36,7 +38,9 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn // runtimeVariables take precedence over envVars const combinedVars = { - ...envVars, + ...collectionVariables, + ...envVariables, + ...folderVariables, ...requestVariables, ...runtimeVariables, process: { diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 74cb85ac6..38f8f5d3c 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -44,73 +44,75 @@ const mergeFolderLevelHeaders = (request, requestTreePath) => { request.headers = Array.from(requestHeadersMap, ([name, value]) => ({ name, value, enabled: true })); }; -const mergeFolderLevelVars = (request, requestTreePath) => { - let folderReqVars = new Map(); +const mergeVars = (collection, request, requestTreePath) => { + let reqVars = new Map(); + let collectionRequestVars = get(collection, 'root.request.vars.req', []); + let collectionVariables = {}; + collectionRequestVars.forEach((_var) => { + if (_var.enabled) { + reqVars.set(_var.name, _var.value); + collectionVariables[_var.name] = _var.value; + } + }); + let folderVariables = {}; + let requestVariables = {}; for (let i of requestTreePath) { if (i.type === 'folder') { let vars = get(i, 'root.request.vars.req', []); vars.forEach((_var) => { if (_var.enabled) { - folderReqVars.set(_var.name, _var.value); + reqVars.set(_var.name, _var.value); + folderVariables[_var.name] = _var.value; } }); - } else if (i.uid === request.uid) { + } else { const vars = i?.draft ? get(i, 'draft.request.vars.req', []) : get(i, 'request.vars.req', []); vars.forEach((_var) => { if (_var.enabled) { - folderReqVars.set(_var.name, _var.value); + reqVars.set(_var.name, _var.value); + requestVariables[_var.name] = _var.value; } }); } } - let mergedFolderReqVars = Array.from(folderReqVars, ([name, value]) => ({ name, value, enabled: true })); - let requestReqVars = request?.vars?.req || []; - let requestReqVarsMap = new Map(); - for (let _var of requestReqVars) { - if (_var.enabled) { - requestReqVarsMap.set(_var.name, _var.value); - } - } - mergedFolderReqVars.forEach((_var) => { - requestReqVarsMap.set(_var.name, _var.value); - }); - request.vars.req = Array.from(requestReqVarsMap, ([name, value]) => ({ + + request.collectionVariables = collectionVariables; + request.folderVariables = folderVariables; + request.requestVariables = requestVariables; + + request.vars.req = Array.from(reqVars, ([name, value]) => ({ name, value, enabled: true, type: 'request' })); - let folderResVars = new Map(); + let resVars = new Map(); + let collectionResponseVars = get(collection, 'root.request.vars.res', []); + collectionResponseVars.forEach((_var) => { + if (_var.enabled) { + resVars.set(_var.name, _var.value); + } + }); for (let i of requestTreePath) { if (i.type === 'folder') { let vars = get(i, 'root.request.vars.res', []); vars.forEach((_var) => { if (_var.enabled) { - folderResVars.set(_var.name, _var.value); + resVars.set(_var.name, _var.value); } }); - } else if (i.uid === request.uid) { + } else { const vars = i?.draft ? get(i, 'draft.request.vars.res', []) : get(i, 'request.vars.res', []); vars.forEach((_var) => { if (_var.enabled) { - folderResVars.set(_var.name, _var.value); + resVars.set(_var.name, _var.value); } }); } } - let mergedFolderResVars = Array.from(folderResVars, ([name, value]) => ({ name, value, enabled: true })); - let requestResVars = request?.vars?.res || []; - let requestResVarsMap = new Map(); - for (let _var of requestResVars) { - if (_var.enabled) { - requestResVarsMap.set(_var.name, _var.value); - } - } - mergedFolderResVars.forEach((_var) => { - requestResVarsMap.set(_var.name, _var.value); - }); - request.vars.res = Array.from(requestResVarsMap, ([name, value]) => ({ + + request.vars.res = Array.from(resVars, ([name, value]) => ({ name, value, enabled: true, @@ -314,7 +316,7 @@ const prepareRequest = (item, collection) => { if (requestTreePath && requestTreePath.length > 0) { mergeFolderLevelHeaders(request, requestTreePath); mergeFolderLevelScripts(request, requestTreePath, scriptFlow); - mergeFolderLevelVars(request, requestTreePath); + mergeVars(collection, request, requestTreePath); } each(request.headers, (h) => { @@ -401,6 +403,9 @@ const prepareRequest = (item, collection) => { } axiosRequest.vars = request.vars; + axiosRequest.collectionVariables = request.collectionVariables; + axiosRequest.folderVariables = request.folderVariables; + axiosRequest.requestVariables = request.requestVariables; axiosRequest.assertions = request.assertions; return axiosRequest; diff --git a/packages/bruno-js/src/bru.js b/packages/bruno-js/src/bru.js index 8dfc69486..7f24cea14 100644 --- a/packages/bruno-js/src/bru.js +++ b/packages/bruno-js/src/bru.js @@ -4,10 +4,12 @@ const { interpolate } = require('@usebruno/common'); const variableNameRegex = /^[\w-.]*$/; class Bru { - constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables) { + constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables) { this.envVariables = envVariables || {}; this.runtimeVariables = runtimeVariables || {}; this.processEnvVars = cloneDeep(processEnvVars || {}); + this.collectionVariables = collectionVariables || {}; + this.folderVariables = folderVariables || {}; this.requestVariables = requestVariables || {}; this.collectionPath = collectionPath; } @@ -18,7 +20,9 @@ class Bru { } const combinedVars = { + ...this.collectionVariables, ...this.envVariables, + ...this.folderVariables, ...this.requestVariables, ...this.runtimeVariables, process: { @@ -71,7 +75,7 @@ class Bru { if (variableNameRegex.test(key) === false) { throw new Error( `Variable name: "${key}" contains invalid characters!` + - ' Names must only contain alpha-numeric characters, "-", "_", "."' + ' Names must only contain alpha-numeric characters, "-", "_", "."' ); } @@ -82,7 +86,7 @@ class Bru { if (variableNameRegex.test(key) === false) { throw new Error( `Variable name: "${key}" contains invalid characters!` + - ' Names must only contain alpha-numeric characters, "-", "_", "."' + ' Names must only contain alpha-numeric characters, "-", "_", "."' ); } @@ -93,6 +97,14 @@ class Bru { delete this.runtimeVariables[key]; } + getCollectionVar(key) { + return this._interpolate(this.collectionVariables[key]); + } + + getFolderVar(key) { + return this._interpolate(this.folderVariables[key]); + } + getRequestVar(key) { return this._interpolate(this.requestVariables[key]); } diff --git a/packages/bruno-js/src/interpolate-string.js b/packages/bruno-js/src/interpolate-string.js index 637b03832..2692641c2 100644 --- a/packages/bruno-js/src/interpolate-string.js +++ b/packages/bruno-js/src/interpolate-string.js @@ -2,14 +2,16 @@ const { interpolate } = require('@usebruno/common'); const interpolateString = ( str, - { envVariables = {}, runtimeVariables = {}, processEnvVars = {}, requestVariables = {} } + { envVariables = {}, runtimeVariables = {}, processEnvVars = {}, collectionVariables = {}, folderVariables = {}, requestVariables = {} } ) => { if (!str || !str.length || typeof str !== 'string') { return str; } const combinedVars = { + ...collectionVariables, ...envVariables, + ...folderVariables, ...requestVariables, ...runtimeVariables, process: { diff --git a/packages/bruno-js/src/runtime/assert-runtime.js b/packages/bruno-js/src/runtime/assert-runtime.js index 5b904dee4..aafacfe8a 100644 --- a/packages/bruno-js/src/runtime/assert-runtime.js +++ b/packages/bruno-js/src/runtime/assert-runtime.js @@ -192,6 +192,8 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => { } const interpolationContext = { + collectionVariables: context.bru.collectionVariables, + folderVariables: context.bru.folderVariables, requestVariables: context.bru.requestVariables, runtimeVariables: context.bru.runtimeVariables, envVariables: context.bru.envVariables, @@ -238,13 +240,23 @@ class AssertRuntime { } runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) { + const collectionVariables = request?.collectionVariables || {}; + const folderVariables = request?.folderVariables || {}; const requestVariables = request?.requestVariables || {}; const enabledAssertions = _.filter(assertions, (a) => a.enabled); if (!enabledAssertions.length) { return []; } - const bru = new Bru(envVariables, runtimeVariables, processEnvVars, undefined, requestVariables); + const bru = new Bru( + envVariables, + runtimeVariables, + processEnvVars, + undefined, + collectionVariables, + folderVariables, + requestVariables + ); const req = new BrunoRequest(request); const res = createResponseParser(response); @@ -255,7 +267,9 @@ class AssertRuntime { }; const context = { + ...collectionVariables, ...envVariables, + ...folderVariables, ...requestVariables, ...runtimeVariables, ...processEnvVars, diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index 9a9338c10..9dc47a29d 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -47,8 +47,10 @@ class ScriptRuntime { processEnvVars, scriptingConfig ) { + const collectionVariables = request?.collectionVariables || {}; + const folderVariables = request?.folderVariables || {}; const requestVariables = request?.requestVariables || {}; - const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables); + const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables); const req = new BrunoRequest(request); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []); @@ -162,8 +164,10 @@ class ScriptRuntime { processEnvVars, scriptingConfig ) { + const collectionVariables = request?.collectionVariables || {}; + const folderVariables = request?.folderVariables || {}; const requestVariables = request?.requestVariables || {}; - const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables); + const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables); const req = new BrunoRequest(request); const res = new BrunoResponse(response); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 7fa9941ed..53fab05eb 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -48,8 +48,10 @@ class TestRuntime { processEnvVars, scriptingConfig ) { + const collectionVariables = request?.collectionVariables || {}; + const folderVariables = request?.folderVariables || {}; const requestVariables = request?.requestVariables || {}; - const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables); + const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables); const req = new BrunoRequest(request); const res = new BrunoResponse(response); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); diff --git a/packages/bruno-js/src/runtime/vars-runtime.js b/packages/bruno-js/src/runtime/vars-runtime.js index 4f4bd3719..1ed806000 100644 --- a/packages/bruno-js/src/runtime/vars-runtime.js +++ b/packages/bruno-js/src/runtime/vars-runtime.js @@ -1,22 +1,10 @@ const _ = require('lodash'); const Bru = require('../bru'); const BrunoRequest = require('../bruno-request'); -const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils'); +const { evaluateJsExpression, createResponseParser } = require('../utils'); const { executeQuickJsVm } = require('../sandbox/quickjs'); -const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => { - if (runtime === 'quickjs') { - return executeQuickJsVm({ - script: literal, - context, - scriptType: 'template-literal' - }); - } - - return evaluateJsTemplateLiteral(literal, context); -}; - const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => { if (runtime === 'quickjs') { return executeQuickJsVm({ @@ -35,35 +23,6 @@ class VarsRuntime { this.mode = props?.mode || 'developer'; } - runPreRequestVars(vars, request, envVariables, runtimeVariables, collectionPath, processEnvVars) { - if (!request?.requestVariables) { - request.requestVariables = {}; - } - const enabledVars = _.filter(vars, (v) => v.enabled); - if (!enabledVars.length) { - return; - } - - const bru = new Bru(envVariables, runtimeVariables, processEnvVars); - const req = new BrunoRequest(request); - - const bruContext = { - bru, - req - }; - - const context = { - ...envVariables, - ...runtimeVariables, - ...bruContext - }; - - _.each(enabledVars, (v) => { - const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime); - request?.requestVariables && (request.requestVariables[v.name] = value); - }); - } - runPostResponseVars(vars, request, response, envVariables, runtimeVariables, collectionPath, processEnvVars) { const requestVariables = request?.requestVariables || {}; const enabledVars = _.filter(vars, (v) => v.enabled); diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js index ced639893..f045b134b 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js @@ -69,6 +69,18 @@ const addBruShimToContext = (vm, bru) => { vm.setProp(bruObject, 'getRequestVar', getRequestVar); getRequestVar.dispose(); + let getFolderVar = vm.newFunction('getFolderVar', function (key) { + return marshallToVm(bru.getFolderVar(vm.dump(key)), vm); + }); + vm.setProp(bruObject, 'getFolderVar', getFolderVar); + getFolderVar.dispose(); + + let getCollectionVar = vm.newFunction('getCollectionVar', function (key) { + return marshallToVm(bru.getCollectionVar(vm.dump(key)), vm); + }); + vm.setProp(bruObject, 'getCollectionVar', getCollectionVar); + getCollectionVar.dispose(); + const sleep = vm.newFunction('sleep', (timer) => { const t = vm.getString(timer); const promise = vm.newPromise(); diff --git a/packages/bruno-tests/collection/collection.bru b/packages/bruno-tests/collection/collection.bru index ab9776995..d4b353eb8 100644 --- a/packages/bruno-tests/collection/collection.bru +++ b/packages/bruno-tests/collection/collection.bru @@ -1,5 +1,6 @@ headers { check: again + token: {{collection_pre_var_token}} } auth { @@ -10,6 +11,11 @@ auth:bearer { token: {{bearer_auth_token}} } +vars:pre-request { + collection_pre_var: collection_pre_var_value + collection_pre_var_token: {{request_pre_var_token}} +} + docs { # bruno-testbench 🐶 diff --git a/packages/bruno-tests/collection/scripting/js/data types - request vars.bru b/packages/bruno-tests/collection/scripting/js/data types - request vars.bru index d8a8af9f2..a0f7c91a8 100644 --- a/packages/bruno-tests/collection/scripting/js/data types - request vars.bru +++ b/packages/bruno-tests/collection/scripting/js/data types - request vars.bru @@ -25,16 +25,6 @@ body:json { } } -vars:pre-request { - boolean: false - undefined: undefined - null: null - string: foo - number_1: 1 - number_2: 0 - number_3: -1 -} - assert { req.body.boolean: isBoolean false req.body.number_1: isNumber 1 @@ -51,35 +41,4 @@ assert { req.body.number_3: eq -1 req.body.number_2: isNumber req.body.number_3: isNumber - boolean: eq false - undefined: eq undefined - null: eq null - string: eq foo - number_1: eq 1 - number_2: eq 0 - number_3: eq -1 -} - -tests { - test("boolean pre var", function() { - expect(bru.getRequestVar('boolean')).to.eql(false); - }); - - test("number pre var", function() { - expect(bru.getRequestVar('number_1')).to.eql(1); - expect(bru.getRequestVar('number_2')).to.eql(0); - expect(bru.getRequestVar('number_3')).to.eql(-1); - }); - - test("null pre var", function() { - expect(bru.getRequestVar('null')).to.eql(null); - }); - - test("undefined pre var", function() { - expect(bru.getRequestVar('undefined')).to.eql(undefined); - }); - - test("string pre var", function() { - expect(bru.getRequestVar('string')).to.eql('foo'); - }); } diff --git a/packages/bruno-tests/collection/string interpolation/folder.bru b/packages/bruno-tests/collection/string interpolation/folder.bru new file mode 100644 index 000000000..8aef24cb2 --- /dev/null +++ b/packages/bruno-tests/collection/string interpolation/folder.bru @@ -0,0 +1,8 @@ +meta { + name: string interpolation +} + +vars:pre-request { + folder_pre_var: folder_pre_var_value + folder_pre_var_2: {{env.var1}} +} diff --git a/packages/bruno-tests/collection/string interpolation/pre-vars.bru b/packages/bruno-tests/collection/string interpolation/pre-vars.bru new file mode 100644 index 000000000..9a6d8d8f4 --- /dev/null +++ b/packages/bruno-tests/collection/string interpolation/pre-vars.bru @@ -0,0 +1,48 @@ +meta { + name: pre-vars + type: http + seq: 5 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +headers { + request_pre_var: {{request_pre_var}} +} + +vars:pre-request { + request_pre_var: {{folder_pre_var}} + request_pre_var_token: request_pre_var_token_value + request_pre_var_1: request_pre_var_1_value + request_pre_var_2: {{request_pre_var_1}} +} + +assert { + collection_pre_var: eq collection_pre_var_value + folder_pre_var: eq folder_pre_var_value +} + +tests { + test("collection pre var bru function", function() { + expect(bru.getCollectionVar('collection_pre_var')).to.eql('collection_pre_var_value'); + }); + + + test("folder pre var bru function", function() { + expect(bru.getFolderVar('folder_pre_var')).to.eql('folder_pre_var_value'); + }); + + + test("request pre var bru function", function() { + expect(bru.getRequestVar('request_pre_var')).to.eql('folder_pre_var_value'); + }); + + test("request pre var self-interpoaltion", function() { + expect(bru.getRequestVar('request_pre_var_2')).to.eql('request_pre_var_1_value'); + }); + +}