From 2e71e605ea728752f654a1715632d8b556cf9319 Mon Sep 17 00:00:00 2001 From: game5413 <37659721+game5413@users.noreply.github.com> Date: Mon, 13 May 2024 17:14:27 +0700 Subject: [PATCH] feat: path parameters (#484) * add path parameters on bruno-app * add path parameters on bruno-cli * fix bruno-schema testing * fix generate request code not replace path parameter value --------- Co-authored-by: game5413 Co-authored-by: Anoop M D --- docs/contributing/contributing_cn.md | 5 +- docs/contributing/contributing_fr.md | 5 +- docs/contributing/contributing_pl.md | 4 +- docs/contributing/contributing_tr.md | 4 +- docs/contributing/contributing_zhtw.md | 4 - docs/readme/readme_cn.md | 8 +- docs/readme/readme_fr.md | 7 +- docs/readme/readme_ru.md | 1 - .../RequestPane/HttpRequestPane/index.js | 13 +- .../RequestPane/QueryParams/StyledWrapper.js | 3 + .../RequestPane/QueryParams/index.js | 235 ++++++++++++------ .../CollectionItem/GenerateCodeItem/index.js | 54 +++- .../ReduxStore/slices/collections/index.js | 76 ++++-- .../bruno-app/src/utils/collections/export.js | 1 + .../bruno-app/src/utils/collections/index.js | 25 ++ .../bruno-app/src/utils/importers/common.js | 1 + packages/bruno-app/src/utils/url/index.js | 36 +++ .../bruno-app/src/utils/url/index.spec.js | 47 +++- .../bruno-cli/src/runner/interpolate-vars.js | 25 +- .../bruno-cli/src/runner/prepare-request.js | 3 +- packages/bruno-cli/src/utils/bru.js | 2 + packages/bruno-electron/src/app/watcher.js | 4 + packages/bruno-electron/src/bru/index.js | 4 + .../src/ipc/network/interpolate-vars.js | 25 +- .../src/ipc/network/prepare-request.js | 1 + packages/bruno-lang/v2/src/bruToJson.js | 9 +- packages/bruno-lang/v2/src/jsonToBru.js | 10 +- .../bruno-schema/src/collections/index.js | 1 + .../src/collections/index.spec.js | 2 + .../src/collections/requestSchema.spec.js | 2 + 30 files changed, 486 insertions(+), 131 deletions(-) diff --git a/docs/contributing/contributing_cn.md b/docs/contributing/contributing_cn.md index 15f4588a4..76ed5722d 100644 --- a/docs/contributing/contributing_cn.md +++ b/docs/contributing/contributing_cn.md @@ -23,7 +23,6 @@ Bruno 基于 NextJs 和 React 构建。我们使用 Electron 来封装桌面版 您需要 [Node v18.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我们在这个项目中也使用 npm 工作区(_npm workspaces_)。 - ## 开发 Bruno 是作为一个 _client lourd(重客户端)_ 应用程序开发的。您需要在一个终端中启动 nextjs 来加载应用程序,然后在另一个终端中启动 Electron 应用程序。 @@ -68,7 +67,6 @@ done find . -type f -name "package-lock.json" -delete ``` - ### 测试 ```bash @@ -79,7 +77,6 @@ npm test --workspace=packages/bruno-schema npm test --workspace=packages/bruno-lang ``` - ### 提交 Pull Request - 请保持 PR 精简并专注于单一目标 @@ -87,4 +84,4 @@ npm test --workspace=packages/bruno-lang - feature/[feature name]:该分支应包含特定功能 - 例如:feature/dark-mode - bugfix/[bug name]:该分支应仅包含特定 bug 的修复 - - 例如:bugfix/bug-1 \ No newline at end of file + - 例如:bugfix/bug-1 diff --git a/docs/contributing/contributing_fr.md b/docs/contributing/contributing_fr.md index 680bdbbcf..46062bb0e 100644 --- a/docs/contributing/contributing_fr.md +++ b/docs/contributing/contributing_fr.md @@ -27,7 +27,6 @@ Vous aurez besoin de [Node v18.x ou la dernière version LTS](https://nodejs.org Bruno est développé comme une application _client lourd_. Vous devrez charger l'application en démarrant nextjs dans un premier terminal, puis démarre l'application Electron dans un second. - ### Dépendances - NodeJS v18 @@ -68,7 +67,6 @@ done find . -type f -name "package-lock.json" -delete ``` - ### Tests ```bash @@ -79,7 +77,6 @@ npm test --workspace=packages/bruno-schema npm test --workspace=packages/bruno-lang ``` - ### Ouvrir une Pull Request - Merci de conserver les PR minimes et focalisées sur un seul objectif @@ -87,4 +84,4 @@ npm test --workspace=packages/bruno-lang - feature/[feature name]: Cette branche doit contenir une fonctionnalité spécifique - Exemple : feature/dark-mode - bugfix/[bug name]: Cette branche doit contenir seulement une solution pour un bug spécifique - - Exemple : bugfix/bug-1 \ No newline at end of file + - Exemple : bugfix/bug-1 diff --git a/docs/contributing/contributing_pl.md b/docs/contributing/contributing_pl.md index 17795ddbb..a930adf28 100644 --- a/docs/contributing/contributing_pl.md +++ b/docs/contributing/contributing_pl.md @@ -33,7 +33,7 @@ Bruno jest rozwijane jako aplikacja desktopowa. Musisz załadować aplikację, u ### Lokalny Rozwój -```bash +````bash # użyj wersji nodejs 18 nvm use @@ -66,7 +66,7 @@ done # Usuń package-lock w podkatalogach find . -type f -name "package-lock.json" -delete -``` +```` ### Testowanie diff --git a/docs/contributing/contributing_tr.md b/docs/contributing/contributing_tr.md index cfe9f9aca..96b2bdeaf 100644 --- a/docs/contributing/contributing_tr.md +++ b/docs/contributing/contributing_tr.md @@ -1,4 +1,5 @@ -[English](../../contributing.md) | [Українська](docs/contributing/contributing_ua.md) | [Русский](docs/contributing/contributing_ru.md) | **Türkçe** | [Deutsch](docs/contributing/contributing_de.md) | [Français](docs/contributing/contributing_fr.md) | [Português (BR)](docs/contributing/contributing_pt_br.md) | [বাংলা](docs/contributing/contributing_bn.md) | [Español](docs/contributing/contributing_es.md) | [Română](docs/contributing/contributing_ro.md) | [Polski](docs/contributing/contributing_pl.md) | [简体中文](docs/contributing/contributing_cn.md) | [正體中文](docs/contributing/contributing_zhtw.md) +[English](../../contributing.md) | [Українська](docs/contributing/contributing_ua.md) | [Русский](docs/contributing/contributing_ru.md) | **Türkçe** | [Deutsch](docs/contributing/contributing_de.md) | [Français](docs/contributing/contributing_fr.md) | [Português (BR)](docs/contributing/contributing_pt_br.md) | [বাংলা](docs/contributing/contributing_bn.md) | [Español](docs/contributing/contributing_es.md) | [Română](docs/contributing/contributing_ro.md) | [Polski](docs/contributing/contributing_pl.md) +| [简体中文](docs/contributing/contributing_cn.md) | [正體中文](docs/contributing/contributing_zhtw.md) ## Bruno'yu birlikte daha iyi hale getirelim!!! @@ -57,7 +58,6 @@ npm run dev:electron `npm install`'ı çalıştırdığınızda `Unsupported platform` hatası ile karşılaşabilirsiniz. Bunu düzeltmek için `node_modules` ve `package-lock.json` dosyalarını silmeniz ve `npm install` dosyasını çalıştırmanız gerekecektir. Bu, uygulamayı çalıştırmak için gereken tüm gerekli paketleri yüklemelidir. - ```shell # Alt dizinlerdeki node_modules öğelerini silme find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do diff --git a/docs/contributing/contributing_zhtw.md b/docs/contributing/contributing_zhtw.md index 5b7fc5ec6..383a0f9f2 100644 --- a/docs/contributing/contributing_zhtw.md +++ b/docs/contributing/contributing_zhtw.md @@ -23,12 +23,10 @@ Bruno 使用 Next.js 和 React 構建。我們使用 Electron 來封裝及發佈 您需要使用 [Node v18.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我們在這個專案中使用 npm 工作區(_npm workspaces_)。 - ## 開發 Bruno 正以桌面應用程式的形式開發。您需要在一個終端機中執行 Next.js 來載入應用程式,然後在另一個終端機中執行 electron 應用程式。 - ### 開發依賴 - NodeJS v18 @@ -69,7 +67,6 @@ done find . -type f -name "package-lock.json" -delete ``` - ### 測試 ```bash @@ -80,7 +77,6 @@ npm test --workspace=packages/bruno-schema npm test --workspace=packages/bruno-lang ``` - ### 發送 Pull Request - 請保持 PR 精簡並專注於一個目標 diff --git a/docs/readme/readme_cn.md b/docs/readme/readme_cn.md index dec6498a1..265ab75b0 100644 --- a/docs/readme/readme_cn.md +++ b/docs/readme/readme_cn.md @@ -1,7 +1,7 @@
-### Bruno - 开源IDE,用于探索和测试API。 +### Bruno - 开源 IDE,用于探索和测试 API。 [![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno) [![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml) @@ -12,16 +12,14 @@ [English](../../readme.md) | [Українська](./readme_ua.md) | [Русский](./readme_ru.md) | [Türkçe](./readme_tr.md) | [Deutsch](./readme_de.md) | [Français](./readme_fr.md) | [Português (BR)](./readme_pt_br.md) | [한국어](./readme_kr.md) | [বাংলা](./readme_bn.md) | [Español](./readme_es.md) | [Italiano](./readme_it.md) | [Română](./readme_ro.md) | [Polski](./readme_pl.md) | **简体中文** | [正體中文](docs/readme/readme_zhtw.md) - Bruno 是一款全新且创新的 API 客户端,旨在颠覆 Postman 和其他类似工具。 Bruno 直接在您的电脑文件夹中存储您的 API 信息。我们使用纯文本标记语言 Bru 来保存有关 API 的信息。 -您可以使用 Git 或您选择的任何版本控制系统来对您的API信息进行版本控制和协作。 +您可以使用 Git 或您选择的任何版本控制系统来对您的 API 信息进行版本控制和协作。 Bruno 仅限离线使用。我们计划永不向 Bruno 添加云同步功能。我们重视您的数据隐私,并认为它应该留在您的设备上。阅读我们的长期愿景 [点击查看](https://github.com/usebruno/bruno/discussions/269) - 📢 观看我们在印度 FOSS 3.0 会议上的最新演讲 [点击查看](https://www.youtube.com/watch?v=7bSMFpbcPiY) ![bruno](../../assets/images/landing-2.png)

@@ -99,7 +97,7 @@ sudo apt install bruno 我很高兴您希望改进bruno。请查看 [贡献指南](../../contributing_cn.md)。 -即使您无法通过代码做出贡献,我们仍然欢迎您提出BUG和新的功能需求。 +即使您无法通过代码做出贡献,我们仍然欢迎您提出 BUG 和新的功能需求。 ### 作者 diff --git a/docs/readme/readme_fr.md b/docs/readme/readme_fr.md index 0c5a41bf2..52188b5d1 100644 --- a/docs/readme/readme_fr.md +++ b/docs/readme/readme_fr.md @@ -10,7 +10,6 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) - [English](/readme.md) | [Українська](docs/readme/readme_ua.md) | [Русский](docs/readme/readme_ru.md) | [Türkçe](docs/readme/readme_tr.md) | [Deutsch](docs/readme/readme_de.md) | **Français** | [Português (BR)](docs/readme/readme_pt_br.md) | [한국어](docs/readme/readme_kr.md) | [বাংলা](docs/readme/readme_bn.md) | [Español](docs/readme/readme_es.md) | [Italiano](docs/readme/readme_it.md) | [Română](docs/readme/readme_ro.md) | [Polski](docs/readme/readme_pl.md) | [简体中文](docs/readme/readme_cn.md) | [正體中文](docs/readme/readme_zhtw.md) Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _statu quo_ que représente Postman et les autres outils. @@ -21,9 +20,7 @@ Vous pouvez utiliser git ou tout autre gestionnaire de version pour travailler d Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269). - -📢 Regarder notre présentation récente lors de la conférence India FOSS 3.0 (en anglais) [ici](https://www.youtube.com/watch?v=7bSMFpbcPiY) - +📢 Regarder notre présentation récente lors de la conférence India FOSS 3.0 (en anglais) [ici](https://www.youtube.com/watch?v=7bSMFpbcPiY) ![bruno](/assets/images/landing-2.png)

@@ -31,7 +28,7 @@ Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas d'abonnement ou de syn Bruno est disponible au téléchargement [sur notre site web](https://www.usebruno.com/downloads), pour Mac, Windows et Linux. -Vous pouvez aussi installer Bruno via un gestionnaire de paquets, comme Homebrew, Chocolatey, Scoop, Snap et Apt. +Vous pouvez aussi installer Bruno via un gestionnaire de paquets, comme Homebrew, Chocolatey, Scoop, Snap et Apt. ```sh # Mac via Homebrew diff --git a/docs/readme/readme_ru.md b/docs/readme/readme_ru.md index fe1b4f441..ae4f52866 100644 --- a/docs/readme/readme_ru.md +++ b/docs/readme/readme_ru.md @@ -10,7 +10,6 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) - [English](/readme.md) | [Українська](/readme_ua.md) | **Русский** | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [বাংলা](docs/readme/readme_bn.md) | [简体中文](docs/readme/readme_cn.md) | [正體中文](docs/readme/readme_zhtw.md) Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами. diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js index 834751848..df90082c6 100644 --- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js @@ -1,5 +1,4 @@ import React from 'react'; -import find from 'lodash/find'; import classnames from 'classnames'; import { useSelector, useDispatch } from 'react-redux'; import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs'; @@ -14,7 +13,7 @@ import Assertions from 'components/RequestPane/Assertions'; import Script from 'components/RequestPane/Script'; import Tests from 'components/RequestPane/Tests'; import StyledWrapper from './StyledWrapper'; -import { get } from 'lodash'; +import { find, get } from 'lodash'; import Documentation from 'components/Documentation/index'; const HttpRequestPane = ({ item, collection, leftPaneWidth }) => { @@ -81,6 +80,8 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => { }); }; + const isMultipleContentTab = ['params', 'script', 'vars', 'auth', 'docs'].includes(focusedTab.requestPaneTab); + // get the length of active params, headers, asserts and vars const params = item.draft ? get(item, 'draft.request.params', []) : get(item, 'request.params', []); const headers = item.draft ? get(item, 'draft.request.headers', []) : get(item, 'request.headers', []); @@ -99,7 +100,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('params')}> - Query + Params {activeParamsLength > 0 && {activeParamsLength}}
selectTab('body')}> @@ -136,9 +137,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => { ) : null}
{getTabPanel(focusedTab.requestPaneTab)}
diff --git a/packages/bruno-app/src/components/RequestPane/QueryParams/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/QueryParams/StyledWrapper.js index d3dc58d5c..5c3e1d537 100644 --- a/packages/bruno-app/src/components/RequestPane/QueryParams/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/QueryParams/StyledWrapper.js @@ -1,6 +1,9 @@ import styled from 'styled-components'; const Wrapper = styled.div` + div.title { + color: var(--color-tab-inactive); + } table { width: 100%; border-collapse: collapse; diff --git a/packages/bruno-app/src/components/RequestPane/QueryParams/index.js b/packages/bruno-app/src/components/RequestPane/QueryParams/index.js index 54e3ee0b3..f48d91c63 100644 --- a/packages/bruno-app/src/components/RequestPane/QueryParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/QueryParams/index.js @@ -1,10 +1,16 @@ import React from 'react'; import get from 'lodash/get'; import cloneDeep from 'lodash/cloneDeep'; +import has from 'lodash/has'; import { IconTrash } from '@tabler/icons'; import { useDispatch } from 'react-redux'; import { useTheme } from 'providers/Theme'; -import { addQueryParam, updateQueryParam, deleteQueryParam } from 'providers/ReduxStore/slices/collections'; +import { + addQueryParam, + updateQueryParam, + deleteQueryParam, + updatePathParam +} from 'providers/ReduxStore/slices/collections'; import SingleLineEditor from 'components/SingleLineEditor'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; @@ -14,6 +20,7 @@ const QueryParams = ({ item, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const params = item.draft ? get(item, 'draft.request.params') : get(item, 'request.params'); + const paths = item.draft ? get(item, 'draft.request.paths') : get(item, 'request.paths'); const handleAddParam = () => { dispatch( @@ -26,24 +33,39 @@ const QueryParams = ({ item, collection }) => { const onSave = () => dispatch(saveRequest(item.uid, collection.uid)); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleParamChange = (e, _param, type) => { - const param = cloneDeep(_param); + + const handleValueChange = (data, type, value) => { + const _data = cloneDeep(data); + + if (!has(_data, type)) { + return; + } + + _data[type] = value; + + return _data; + }; + + const handleParamChange = (e, data, type) => { + let value; switch (type) { case 'name': { - param.name = e.target.value; + value = e.target.value; break; } case 'value': { - param.value = e.target.value; + value = e.target.value; break; } case 'enabled': { - param.enabled = e.target.checked; + value = e.target.checked; break; } } + const param = handleValueChange(data, type, value); + dispatch( updateQueryParam({ param, @@ -53,6 +75,20 @@ const QueryParams = ({ item, collection }) => { ); }; + const handlePathChange = (e, data) => { + let value = e.target.value; + + const path = handleValueChange(data, 'value', value); + + dispatch( + updatePathParam({ + path, + itemUid: item.uid, + collectionUid: collection.uid + }) + ); + }; + const handleRemoveParam = (param) => { dispatch( deleteQueryParam({ @@ -64,75 +100,128 @@ const QueryParams = ({ item, collection }) => { }; return ( - - - - - - - - - - - {params && params.length - ? params.map((param, index) => { - return ( - - - - + + + + ); + }) + : null} + +
NameValue
- handleParamChange(e, param, 'name')} - /> - - - handleParamChange( - { - target: { - value: newValue - } - }, - param, - 'value' - ) - } - onRun={handleRun} - collection={collection} - /> - -
+ +
+
Query
+ + + + + + + + + + {params && params.length + ? params.map((param, index) => { + return ( + + - - ); - }) - : null} - -
NameValue
handleParamChange(e, param, 'enabled')} + type="text" + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + spellCheck="false" + value={param.name} + className="mousetrap" + onChange={(e) => handleParamChange(e, param, 'name')} /> - - -
- +
+ + handleParamChange( + { + target: { + value: newValue + } + }, + param, + 'value' + ) + } + onRun={handleRun} + collection={collection} + /> + +
+ handleParamChange(e, param, 'enabled')} + /> + +
+
+ +
Path
+ + + + + + + + + {paths && paths.length + ? paths.map((path, index) => { + return ( + + + + + ); + }) + : null} + +
NameValue
+ + + + handlePathChange( + { + target: { + value: newValue + } + }, + path + ) + } + onRun={handleRun} + collection={collection} + /> +
+
); }; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js index ed1bc3f64..045df08d4 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js @@ -3,7 +3,7 @@ import { useState } from 'react'; import CodeView from './CodeView'; import StyledWrapper from './StyledWrapper'; import { isValidUrl } from 'utils/url/index'; -import get from 'lodash/get'; +import { get, find } from 'lodash'; import { findEnvironmentInCollection } from 'utils/collections'; // Todo: Fix this @@ -27,6 +27,53 @@ const interpolateUrl = ({ url, envVars, collectionVariables, processEnvVars }) = }); }; +const joinPathUrl = (url, paths) => { + let uri = url.slice(); + if (uri.indexOf('http') === -1 || uri.indexOf('https') === -1) { + let [base, query = ''] = uri.split('?'); + + let URL_SEPARATOR; + + uri = base.split('/').reduce((acc, path, index) => { + if (index !== 0) { + URL_SEPARATOR = '/'; + } + if (path.charAt(0) !== ':') { + acc += URL_SEPARATOR + path; + } else { + path = path.slice(1, path.length); + const data = find(paths, (v) => v.name === path); + if (data) { + acc += URL_SEPARATOR + data.value; + } + } + return acc; + }, ''); + + return uri + query; + } + uri = new URL(uri); + let uriPaths = url.pathname.split('/'); + uriPaths = uriPaths.reduce((acc, path) => { + if (path !== '') { + if (path[0] !== ':') { + acc += '/' + path; + } else { + let name = path.slice(1, path.length); + if (name) { + let existingPath = find(paths, (path) => path.name === name); + if (existingPath) { + acc += '/' + existingPath.value; + } + } + } + } + return acc; + }, ''); + + return uri.origin + uriPaths + uri.search; +}; + const languages = [ { name: 'HTTP', @@ -76,7 +123,10 @@ const languages = [ ]; const GenerateCodeItem = ({ collection, item, onClose }) => { - const url = get(item, 'draft.request.url') !== undefined ? get(item, 'draft.request.url') : get(item, 'request.url'); + const url = joinPathUrl( + get(item, 'draft.request.url') !== undefined ? get(item, 'draft.request.url') : get(item, 'request.url'), + get(item, 'draft.request.paths') !== undefined ? get(item, 'draft.request.paths') : get(item, 'request.paths') + ); const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); let envVars = {}; if (environment) { 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 15afa72f5..744a29842 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1,29 +1,29 @@ -import { createSlice } from '@reduxjs/toolkit'; -import cloneDeep from 'lodash/cloneDeep'; -import concat from 'lodash/concat'; -import each from 'lodash/each'; -import filter from 'lodash/filter'; +import { uuid } from 'utils/common'; import find from 'lodash/find'; -import forOwn from 'lodash/forOwn'; -import get from 'lodash/get'; import map from 'lodash/map'; +import forOwn from 'lodash/forOwn'; +import concat from 'lodash/concat'; +import filter from 'lodash/filter'; +import each from 'lodash/each'; +import cloneDeep from 'lodash/cloneDeep'; +import get from 'lodash/get'; import set from 'lodash/set'; +import { createSlice } from '@reduxjs/toolkit'; import { + findCollectionByUid, + findCollectionByPathname, + findItemInCollection, + findEnvironmentInCollection, + findItemInCollectionByPathname, addDepth, - areItemsTheSameExceptSeqUpdate, collapseCollection, deleteItemInCollection, deleteItemInCollectionByPathname, - findCollectionByPathname, - findCollectionByUid, - findEnvironmentInCollection, - findItemInCollection, - findItemInCollectionByPathname, - isItemARequest + isItemARequest, + areItemsTheSameExceptSeqUpdate } from 'utils/collections'; -import { uuid } from 'utils/common'; -import { PATH_SEPARATOR, getDirectoryName, getSubdirectoriesFromRoot } from 'utils/common/platform'; -import { parseQueryParams, splitOnFirst, stringifyQueryParams } from 'utils/url'; +import { parseQueryParams, stringifyQueryParams, splitOnFirst, parsePathParams } from 'utils/url'; +import { getSubdirectoriesFromRoot, getDirectoryName, PATH_SEPARATOR } from 'utils/common/platform'; const initialState = { collections: [], @@ -307,6 +307,7 @@ export const collectionsSlice = createSlice({ url: action.payload.requestUrl, method: action.payload.requestMethod, params, + paths: [], headers: [], body: { mode: null, @@ -351,8 +352,11 @@ export const collectionsSlice = createSlice({ const parts = splitOnFirst(item.draft.request.url, '?'); const urlParams = parseQueryParams(parts[1]); + const urlPaths = parsePathParams(parts[0]); const disabledParams = filter(item.draft.request.params, (p) => !p.enabled); let enabledParams = filter(item.draft.request.params, (p) => p.enabled); + let oldPaths = cloneDeep(item.draft.request.paths); + let newPaths = []; // try and connect as much as old params uid's as possible each(urlParams, (urlParam) => { @@ -366,10 +370,29 @@ export const collectionsSlice = createSlice({ } }); + // filter the newest path param and compare with previous data that already inserted + newPaths = filter(urlPaths, (urlPath) => { + const existingPath = find(oldPaths, (p) => p.name === urlPath.name); + if (existingPath) { + return false; + } + urlPath.uid = uuid(); + urlPath.enabled = true; + return true; + }); + + // remove path param that not used or deleted when typing url + oldPaths = filter(oldPaths, (urlPath) => { + return find(urlPaths, (p) => p.name === urlPath.name); + }); + // ultimately params get replaced with params in url + the disabled ones that existed prior // the query params are the source of truth, the url in the queryurl input gets constructed using these params // we however are also storing the full url (with params) in the url itself item.draft.request.params = concat(urlParams, disabledParams); + + // join both old and new path param to preserve consistency between url and data + item.draft.request.paths = concat(newPaths, oldPaths); } } }, @@ -495,6 +518,24 @@ export const collectionsSlice = createSlice({ } } }, + updatePathParam: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + + if (collection) { + const item = findItemInCollection(collection, action.payload.itemUid); + + if (item && isItemARequest(item)) { + if (!item.draft) { + item.draft = cloneDeep(item); + } + const path = find(item.draft.request.paths, (h) => h.uid === action.payload.path.uid); + if (path) { + path.name = action.payload.path.name; + path.value = action.payload.path.value; + } + } + } + }, addRequestHeader: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); @@ -1418,6 +1459,7 @@ export const { addQueryParam, updateQueryParam, deleteQueryParam, + updatePathParam, addRequestHeader, updateRequestHeader, deleteRequestHeader, diff --git a/packages/bruno-app/src/utils/collections/export.js b/packages/bruno-app/src/utils/collections/export.js index 17c979fe6..0efb0d66a 100644 --- a/packages/bruno-app/src/utils/collections/export.js +++ b/packages/bruno-app/src/utils/collections/export.js @@ -9,6 +9,7 @@ export const deleteUidsInItems = (items) => { if (['http-request', 'graphql-request'].includes(item.type)) { each(get(item, 'request.headers'), (header) => delete header.uid); each(get(item, 'request.params'), (param) => delete param.uid); + each(get(item, 'request.paths'), (path) => delete path.uid); each(get(item, 'request.vars.req'), (v) => delete v.uid); each(get(item, 'request.vars.res'), (v) => delete v.uid); each(get(item, 'request.vars.assertions'), (a) => delete a.uid); diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index f59d9e2d5..b47ff9513 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -240,6 +240,17 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} }); }; + const copyPathParams = (paths) => { + return map(paths, (path) => { + return { + uid: path.uid, + name: path.name, + value: path.value, + enabled: path.enabled + }; + }); + }; + const copyFormUrlEncodedParams = (params = []) => { return map(params, (param) => { return { @@ -285,6 +296,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} method: si.draft.request.method, headers: copyHeaders(si.draft.request.headers), params: copyQueryParams(si.draft.request.params), + paths: copyPathParams(si.draft.request.paths), body: { mode: si.draft.request.body.mode, json: si.draft.request.body.json, @@ -318,6 +330,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} method: si.request.method, headers: copyHeaders(si.request.headers), params: copyQueryParams(si.request.params), + paths: copyPathParams(si.request.paths), body: { mode: si.request.body.mode, json: si.request.body.json, @@ -385,6 +398,7 @@ export const transformRequestToSaveToFilesystem = (item) => { method: _item.request.method, url: _item.request.url, params: [], + paths: [], headers: [], auth: _item.request.auth, body: _item.request.body, @@ -406,6 +420,14 @@ export const transformRequestToSaveToFilesystem = (item) => { }); }); + each(_item.request.paths, (path) => { + itemToSave.request.paths.push({ + uid: path.uid, + name: path.name, + value: path.value + }); + }); + each(_item.request.headers, (header) => { itemToSave.request.headers.push({ uid: header.uid, @@ -546,6 +568,7 @@ export const refreshUidsInItem = (item) => { each(get(item, 'request.headers'), (header) => (header.uid = uuid())); each(get(item, 'request.params'), (param) => (param.uid = uuid())); + each(get(item, 'request.paths'), (path) => (path.uid = uuid())); each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid())); each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid())); @@ -556,11 +579,13 @@ export const deleteUidsInItem = (item) => { delete item.uid; const params = get(item, 'request.params', []); const headers = get(item, 'request.headers', []); + const paths = get(item, 'request.paths', []); const bodyFormUrlEncoded = get(item, 'request.body.formUrlEncoded', []); const bodyMultipartForm = get(item, 'request.body.multipartForm', []); params.forEach((param) => delete param.uid); headers.forEach((header) => delete header.uid); + paths.forEach((path) => delete path.uid); bodyFormUrlEncoded.forEach((param) => delete param.uid); bodyMultipartForm.forEach((param) => delete param.uid); diff --git a/packages/bruno-app/src/utils/importers/common.js b/packages/bruno-app/src/utils/importers/common.js index f1e17ac00..0819539d1 100644 --- a/packages/bruno-app/src/utils/importers/common.js +++ b/packages/bruno-app/src/utils/importers/common.js @@ -31,6 +31,7 @@ export const updateUidsInCollection = (_collection) => { each(get(item, 'request.headers'), (header) => (header.uid = uuid())); each(get(item, 'request.query'), (param) => (param.uid = uuid())); each(get(item, 'request.params'), (param) => (param.uid = uuid())); + each(get(item, 'request.paths'), (path) => (path.uid = uuid())); each(get(item, 'request.vars.req'), (v) => (v.uid = uuid())); each(get(item, 'request.vars.res'), (v) => (v.uid = uuid())); each(get(item, 'request.assertions'), (a) => (a.uid = uuid())); diff --git a/packages/bruno-app/src/utils/url/index.js b/packages/bruno-app/src/utils/url/index.js index 328b22cdc..34befdab7 100644 --- a/packages/bruno-app/src/utils/url/index.js +++ b/packages/bruno-app/src/utils/url/index.js @@ -2,6 +2,7 @@ import isEmpty from 'lodash/isEmpty'; import trim from 'lodash/trim'; import each from 'lodash/each'; import filter from 'lodash/filter'; +import find from 'lodash/find'; const hasLength = (str) => { if (!str || !str.length) { @@ -26,6 +27,41 @@ export const parseQueryParams = (query) => { return filter(params, (p) => hasLength(p.name)); }; +export const parsePathParams = (url) => { + let uri = url.slice(); + + if (!uri || !uri.length) { + return []; + } + + if (uri.indexOf('http://') === -1 || uri.indexOf('https://') === -1) { + uri = `http://${uri}`; + } + + if (!isValidUrl(uri)) { + throw 'Invalid URL format'; + } + + uri = new URL(uri); + + let paths = uri.pathname.split('/'); + + paths = paths.reduce((acc, path) => { + if (path !== '' && path[0] === ':') { + let name = path.slice(1, path.length); + if (name) { + let isExist = find(acc, (path) => path.name === name); + if (!isExist) { + acc.push({ name: path.slice(1, path.length), value: '' }); + } + } + } + return acc; + }, []); + + return paths; +}; + export const stringifyQueryParams = (params) => { if (!params || isEmpty(params)) { return ''; diff --git a/packages/bruno-app/src/utils/url/index.spec.js b/packages/bruno-app/src/utils/url/index.spec.js index 02112cdf2..1f43affaf 100644 --- a/packages/bruno-app/src/utils/url/index.spec.js +++ b/packages/bruno-app/src/utils/url/index.spec.js @@ -1,4 +1,4 @@ -import { parseQueryParams, splitOnFirst } from './index'; +import { parseQueryParams, splitOnFirst, parsePathParams } from './index'; describe('Url Utils - parseQueryParams', () => { it('should parse query - case 1', () => { @@ -51,6 +51,51 @@ describe('Url Utils - parseQueryParams', () => { }); }); +describe('Url Utils - parsePathParams', () => { + it('should parse path - case 1', () => { + const params = parsePathParams('www.example.com'); + expect(params).toEqual([]); + }); + + it('should parse path - case 2', () => { + const params = parsePathParams('http://www.example.com'); + expect(params).toEqual([]); + }); + + it('should parse path - case 3', () => { + const params = parsePathParams('https://www.example.com'); + expect(params).toEqual([]); + }); + + it('should parse path - case 4', () => { + const params = parsePathParams('https://www.example.com/users/:id'); + expect(params).toEqual([{ name: 'id', value: '' }]); + }); + + it('should parse path - case 5', () => { + const params = parsePathParams('https://www.example.com/users/:id/'); + expect(params).toEqual([{ name: 'id', value: '' }]); + }); + + it('should parse path - case 6', () => { + const params = parsePathParams('https://www.example.com/users/:id/:'); + expect(params).toEqual([{ name: 'id', value: '' }]); + }); + + it('should parse path - case 7', () => { + const params = parsePathParams('https://www.example.com/users/:id/posts/:id'); + expect(params).toEqual([{ name: 'id', value: '' }]); + }); + + it('should parse path - case 8', () => { + const params = parsePathParams('https://www.example.com/users/:id/posts/:postId'); + expect(params).toEqual([ + { name: 'id', value: '' }, + { name: 'postId', value: '' } + ]); + }); +}); + describe('Url Utils - splitOnFirst', () => { it('should split on first - case 1', () => { const params = splitOnFirst('a', '='); diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index 63ebdd4ca..eac5849db 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -1,5 +1,5 @@ const { interpolate } = require('@usebruno/common'); -const { each, forOwn, cloneDeep } = require('lodash'); +const { each, forOwn, cloneDeep, find } = require('lodash'); const getContentType = (headers = {}) => { let contentType = ''; @@ -86,6 +86,29 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces param.value = _interpolate(param.value); }); + if (request.paths.length) { + let url = new URL(request.url); + let urlPaths = url.pathname.split('/'); + urlPaths = urlPaths.reduce((acc, path) => { + if (path !== '') { + if (path[0] !== ':') { + acc += '/' + path; + } else { + let name = path.slice(1, path.length); + if (name) { + let existingPath = find(request.paths, (path) => path.name === name); + if (existingPath) { + acc += '/' + interpolate(existingPath.value); + } + } + } + } + return acc; + }, ''); + + request.url = url.origin + urlPaths + url.search; + } + if (request.proxy) { request.proxy.protocol = _interpolate(request.proxy.protocol); request.proxy.hostname = _interpolate(request.proxy.hostname); diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js index 8b1b249db..1e7e083cd 100644 --- a/packages/bruno-cli/src/runner/prepare-request.js +++ b/packages/bruno-cli/src/runner/prepare-request.js @@ -29,7 +29,8 @@ const prepareRequest = (request, collectionRoot) => { let axiosRequest = { method: request.method, url: request.url, - headers: headers + headers: headers, + paths: request.paths }; const collectionAuth = get(collectionRoot, 'request.auth'); diff --git a/packages/bruno-cli/src/utils/bru.js b/packages/bruno-cli/src/utils/bru.js index 34fb09c6b..620402480 100644 --- a/packages/bruno-cli/src/utils/bru.js +++ b/packages/bruno-cli/src/utils/bru.js @@ -14,6 +14,7 @@ const collectionBruToJson = (bru) => { const transformedJson = { request: { params: _.get(json, 'query', []), + paths: _.get(json, 'path', []), headers: _.get(json, 'headers', []), auth: _.get(json, 'auth', {}), script: _.get(json, 'script', {}), @@ -61,6 +62,7 @@ const bruToJson = (bru) => { url: _.get(json, 'http.url'), auth: _.get(json, 'auth', {}), params: _.get(json, 'query', []), + paths: _.get(json, 'path', []), headers: _.get(json, 'headers', []), body: _.get(json, 'body', {}), vars: _.get(json, 'vars', []), diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 441bba3b2..98a7cc2bf 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -48,6 +48,7 @@ const hydrateRequestWithUuid = (request, pathname) => { request.uid = getRequestUid(pathname); const params = _.get(request, 'request.params', []); + const paths = _.get(request, 'request.paths', []); const headers = _.get(request, 'request.headers', []); const requestVars = _.get(request, 'request.vars.req', []); const responseVars = _.get(request, 'request.vars.res', []); @@ -56,6 +57,7 @@ const hydrateRequestWithUuid = (request, pathname) => { const bodyMultipartForm = _.get(request, 'request.body.multipartForm', []); params.forEach((param) => (param.uid = uuid())); + paths.forEach((path) => (path.uid = uuid())); headers.forEach((header) => (header.uid = uuid())); requestVars.forEach((variable) => (variable.uid = uuid())); responseVars.forEach((variable) => (variable.uid = uuid())); @@ -68,11 +70,13 @@ const hydrateRequestWithUuid = (request, pathname) => { const hydrateBruCollectionFileWithUuid = (collectionRoot) => { const params = _.get(collectionRoot, 'request.params', []); + const paths = _.get(collectionRoot, 'request.paths', []); const headers = _.get(collectionRoot, 'request.headers', []); const requestVars = _.get(collectionRoot, 'request.vars.req', []); const responseVars = _.get(collectionRoot, 'request.vars.res', []); params.forEach((param) => (param.uid = uuid())); + paths.forEach((path) => (path.uid = uuid())); headers.forEach((header) => (header.uid = uuid())); requestVars.forEach((variable) => (variable.uid = uuid())); responseVars.forEach((variable) => (variable.uid = uuid())); diff --git a/packages/bruno-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js index de1080ac0..5d4dde410 100644 --- a/packages/bruno-electron/src/bru/index.js +++ b/packages/bruno-electron/src/bru/index.js @@ -15,6 +15,7 @@ const collectionBruToJson = (bru) => { const transformedJson = { request: { params: _.get(json, 'query', []), + paths: _.get(json, 'path', []), headers: _.get(json, 'headers', []), auth: _.get(json, 'auth', {}), script: _.get(json, 'script', {}), @@ -34,6 +35,7 @@ const jsonToCollectionBru = (json) => { try { const collectionBruJson = { query: _.get(json, 'request.params', []), + path: _.get(json, 'request.paths', []), headers: _.get(json, 'request.headers', []), auth: _.get(json, 'request.auth', {}), script: { @@ -112,6 +114,7 @@ const bruToJson = (bru) => { method: _.upperCase(_.get(json, 'http.method')), url: _.get(json, 'http.url'), params: _.get(json, 'query', []), + paths: _.get(json, 'path', []), headers: _.get(json, 'headers', []), auth: _.get(json, 'auth', {}), body: _.get(json, 'body', {}), @@ -163,6 +166,7 @@ const jsonToBru = (json) => { body: _.get(json, 'request.body.mode', 'none') }, query: _.get(json, 'request.params', []), + path: _.get(json, 'request.paths', []), headers: _.get(json, 'request.headers', []), auth: _.get(json, 'request.auth', {}), body: _.get(json, 'request.body', {}), diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 2139194a2..1885b11bd 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -1,5 +1,5 @@ const { interpolate } = require('@usebruno/common'); -const { each, forOwn, cloneDeep } = require('lodash'); +const { each, forOwn, cloneDeep, find } = require('lodash'); const getContentType = (headers = {}) => { let contentType = ''; @@ -86,6 +86,29 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces param.value = _interpolate(param.value); }); + if (request.paths.length) { + let url = new URL(request.url); + let urlPaths = url.pathname.split('/'); + urlPaths = urlPaths.reduce((acc, path) => { + if (path !== '') { + if (path[0] !== ':') { + acc += '/' + path; + } else { + let name = path.slice(1, path.length); + if (name) { + let existingPath = find(request.paths, (path) => path.name === name); + if (existingPath) { + acc += '/' + interpolate(existingPath.value); + } + } + } + } + return acc; + }, ''); + + request.url = url.origin + urlPaths + url.search; + } + if (request.proxy) { request.proxy.protocol = _interpolate(request.proxy.protocol); request.proxy.hostname = _interpolate(request.proxy.hostname); diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index e8c88275f..aa814bf0b 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -161,6 +161,7 @@ const prepareRequest = (request, collectionRoot, collectionPath) => { method: request.method, url, headers, + paths: request.paths, responseType: 'arraybuffer' }; diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 6f12a6ce5..f17de5432 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -22,7 +22,7 @@ const { outdentString } = require('../../v1/src/utils'); * */ const grammar = ohm.grammar(`Bru { - BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)* + BruFile = (meta | http | query | path | headers | auths | bodies | varsandassert | script | tests | docs)* auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2 bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body bodyforms = bodyformurlencoded | bodymultipart @@ -74,6 +74,8 @@ const grammar = ohm.grammar(`Bru { headers = "headers" dictionary query = "query" dictionary + + path = "path" dictionary varsandassert = varsreq | varsres | assert varsreq = "vars:pre-request" dictionary @@ -324,6 +326,11 @@ const sem = grammar.createSemantics().addAttribute('ast', { query: mapPairListToKeyValPairs(dictionary.ast) }; }, + path(_1, dictionary) { + return { + path: mapPairListToKeyValPairs(dictionary.ast) + }; + }, headers(_1, dictionary) { return { headers: mapPairListToKeyValPairs(dictionary.ast) diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 3357e5d09..0b59d7772 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -30,7 +30,7 @@ const getValueString = (value) => { }; const jsonToBru = (json) => { - const { meta, http, query, headers, auth, body, script, tests, vars, assertions, docs } = json; + const { meta, http, query, path, headers, auth, body, script, tests, vars, assertions, docs } = json; let bru = ''; @@ -83,6 +83,14 @@ const jsonToBru = (json) => { bru += '\n}\n\n'; } + if (path && path.length) { + bru += 'path {'; + + bru += `\n${indentString(path.map((item) => `${item.name}: ${item.value}`).join('\n'))}`; + + bru += '\n}\n\n'; + } + if (headers && headers.length) { bru += 'headers {'; if (enabled(headers).length) { diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index 033e68277..1e4a07354 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -193,6 +193,7 @@ const requestSchema = Yup.object({ method: requestMethodSchema, headers: Yup.array().of(keyValueSchema).required('headers are required'), params: Yup.array().of(keyValueSchema).required('params are required'), + paths: Yup.array().of(keyValueSchema).required('paths are required'), auth: authSchema, body: requestBodySchema, script: Yup.object({ diff --git a/packages/bruno-schema/src/collections/index.spec.js b/packages/bruno-schema/src/collections/index.spec.js index 16b683d08..0c6b69156 100644 --- a/packages/bruno-schema/src/collections/index.spec.js +++ b/packages/bruno-schema/src/collections/index.spec.js @@ -59,6 +59,7 @@ describe('Collection Schema Validation', () => { method: 'GET', headers: [], params: [], + paths: [], body: { mode: 'none' } @@ -116,6 +117,7 @@ describe('Collection Schema Validation', () => { method: 'GET', headers: [], params: [], + paths: [], body: { mode: 'none' } diff --git a/packages/bruno-schema/src/collections/requestSchema.spec.js b/packages/bruno-schema/src/collections/requestSchema.spec.js index 87399c690..2430b2862 100644 --- a/packages/bruno-schema/src/collections/requestSchema.spec.js +++ b/packages/bruno-schema/src/collections/requestSchema.spec.js @@ -9,6 +9,7 @@ describe('Request Schema Validation', () => { method: 'GET', headers: [], params: [], + paths: [], body: { mode: 'none' } @@ -24,6 +25,7 @@ describe('Request Schema Validation', () => { method: 'GET-junk', headers: [], params: [], + paths: [], body: { mode: 'none' }