diff --git a/packages/bruno-app/src/utils/url/index.js b/packages/bruno-app/src/utils/url/index.js index db6ca7b62..2dcdccb2a 100644 --- a/packages/bruno-app/src/utils/url/index.js +++ b/packages/bruno-app/src/utils/url/index.js @@ -12,6 +12,24 @@ const hasLength = (str) => { return str.length > 0; }; +const hasResolvablePathParamValue = (pathParam) => { + if (!pathParam || pathParam.enabled === false) { + return false; + } + + const { value } = pathParam; + + if (value === null || value === undefined) { + return false; + } + + if (typeof value === 'string' && !hasLength(value)) { + return false; + } + + return true; +}; + export const parsePathParams = (url) => { let uri = url.slice(); @@ -124,7 +142,8 @@ export const interpolateUrlPathParams = (url, params, variables = {}, options = if (segment.startsWith(':')) { const name = segment.slice(1); const pathParam = params.find((p) => p?.name === name && p?.type === 'path'); - return pathParam ? substituteValue(pathParam.value) : segment; + return hasResolvablePathParamValue(pathParam) ? substituteValue(pathParam.value) : segment; + // return pathParam ? substituteValue(pathParam.value) : segment; } // for OData-style parameters (parameters inside parentheses) @@ -147,7 +166,7 @@ export const interpolateUrlPathParams = (url, params, variables = {}, options = if (!name) continue; const pathParam = params.find((p) => p?.name === name && p?.type === 'path'); - if (pathParam) { + if (hasResolvablePathParamValue(pathParam)) { result = result.replace(':' + match[1], substituteValue(pathParam.value)); } } diff --git a/packages/bruno-app/src/utils/url/index.spec.js b/packages/bruno-app/src/utils/url/index.spec.js index c4cefb3ff..f07a19025 100644 --- a/packages/bruno-app/src/utils/url/index.spec.js +++ b/packages/bruno-app/src/utils/url/index.spec.js @@ -412,6 +412,23 @@ describe('Url Utils - interpolateUrl, interpolateUrlPathParams', () => { expect(result).toEqual(expectedUrl); }); + + it('should keep colon path segments when the path param has no value', () => { + const url = 'https://httpbin.org/anything/:test-segment'; + const params = [{ name: 'test-segment', type: 'path', enabled: true, value: '' }]; + + const result = interpolateUrlPathParams(url, params); + + expect(result).toEqual('https://httpbin.org/anything/:test-segment'); + }); + + it('should keep colon path segments when no path param is defined', () => { + const url = 'https://httpbin.org/anything/:analyze-text'; + + const result = interpolateUrlPathParams(url, []); + + expect(result).toEqual('https://httpbin.org/anything/:analyze-text'); + }); }); describe('Url Utils - interpolateUrlPathParams with { raw: true }', () => { diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index fa531082f..eb3b4f95f 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -2,6 +2,24 @@ const { interpolate } = require('@usebruno/common'); const { each, forOwn, cloneDeep, find } = require('lodash'); const { isFormData } = require('@usebruno/common').utils; +const hasResolvablePathParamValue = (pathParam) => { + if (!pathParam || pathParam.enabled === false) { + return false; + } + + const { value } = pathParam; + + if (value === null || value === undefined) { + return false; + } + + if (typeof value === 'string' && value.trim() === '') { + return false; + } + + return true; +}; + const isBinaryRequestBody = (data) => Buffer.isBuffer(data) || typeof data?.pipe === 'function'; const getContentType = (headers = {}) => { @@ -142,7 +160,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc if (path.startsWith(':')) { const paramName = path.slice(1); const existingPathParam = request.pathParams.find((param) => param.name === paramName); - if (!existingPathParam) { + if (!hasResolvablePathParamValue(existingPathParam)) { return '/' + path; } return '/' + existingPathParam.value; @@ -163,7 +181,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc name = name.replace(/^[('"`]+/, ''); if (name) { const existingPathParam = request.pathParams.find((param) => param.name === name); - if (existingPathParam) { + if (hasResolvablePathParamValue(existingPathParam)) { result = result.replace(':' + match[1], existingPathParam.value); } } diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 7497926e7..33800a123 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -2,6 +2,24 @@ const { interpolate } = require('@usebruno/common'); const { each, forOwn, cloneDeep } = require('lodash'); const { isFormData } = require('@usebruno/common').utils; +const hasResolvablePathParamValue = (pathParam) => { + if (!pathParam || pathParam.enabled === false) { + return false; + } + + const { value } = pathParam; + + if (value === null || value === undefined) { + return false; + } + + if (typeof value === 'string' && value.trim() === '') { + return false; + } + + return true; +}; + const isBinaryRequestBody = (data) => Buffer.isBuffer(data) || typeof data?.pipe === 'function'; const getContentType = (headers = {}) => { @@ -180,7 +198,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc if (path.startsWith(':')) { const paramName = path.slice(1); const existingPathParam = request.pathParams.find((param) => param.name === paramName); - if (!existingPathParam) { + if (!hasResolvablePathParamValue(existingPathParam)) { return '/' + path; } return '/' + existingPathParam.value; @@ -201,7 +219,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc name = name.replace(/^[('"`]+/, ''); if (name) { const existingPathParam = request.pathParams.find((param) => param.name === name); - if (existingPathParam) { + if (hasResolvablePathParamValue(existingPathParam)) { result = result.replace(':' + match[1], existingPathParam.value); } } diff --git a/packages/bruno-electron/tests/network/interpolate-vars.spec.js b/packages/bruno-electron/tests/network/interpolate-vars.spec.js index 9622e8f6d..d570b1633 100644 --- a/packages/bruno-electron/tests/network/interpolate-vars.spec.js +++ b/packages/bruno-electron/tests/network/interpolate-vars.spec.js @@ -181,6 +181,41 @@ describe('interpolate-vars: interpolateVars', () => { const result = interpolateVars(request, null, null, null); expect(result.url).toBe('http://example.com/Category(\'foobar\')/Item(1)/foobar/Tags(%22tag%20test%22)'); }); + + it('keeps colon path segments when the path param has no value', async () => { + const request = { + method: 'POST', + url: 'https://httpbin.org/anything/:test-segment', + pathParams: [ + { + type: 'path', + name: 'test-segment', + value: '' + } + ] + }; + + const result = interpolateVars(request, null, null, null); + expect(result.url).toBe('https://httpbin.org/anything/:test-segment'); + }); + + it('keeps colon path segments when the path param is disabled', async () => { + const request = { + method: 'POST', + url: 'https://httpbin.org/anything/:test-segment', + pathParams: [ + { + type: 'path', + name: 'test-segment', + value: 'replaced', + enabled: false + } + ] + }; + + const result = interpolateVars(request, null, null, null); + expect(result.url).toBe('https://httpbin.org/anything/:test-segment'); + }); }); describe('With process environment variables', () => { diff --git a/packages/bruno-js/src/bruno-request.js b/packages/bruno-js/src/bruno-request.js index 4d7b7e13a..9c33a9bf7 100644 --- a/packages/bruno-js/src/bruno-request.js +++ b/packages/bruno-js/src/bruno-request.js @@ -68,7 +68,13 @@ class BrunoRequest { if (segment.startsWith(':')) { const paramName = segment.slice(1); const pathParam = this.req.pathParams.find((param) => param.name === paramName); - if (pathParam && pathParam.value) { + if ( + pathParam + && pathParam.enabled !== false + && pathParam.value !== null + && pathParam.value !== undefined + && (typeof pathParam.value !== 'string' || pathParam.value.trim() !== '') + ) { return pathParam.value; } }