From 9e5148f0322b51a7c71174811210c136186c0780 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 27 Aug 2024 14:12:56 +0530 Subject: [PATCH] fix(#2767): Fix serilization issues of bigint in json body (#2773) Fix serilization issues of bigint in json body --------- Co-authored-by: lohit --- .../src/components/CodeEditor/index.js | 1 + .../components/ResponsePane/Timeline/index.js | 7 +- .../bruno-electron/src/ipc/network/index.js | 12 +- .../src/ipc/network/interpolate-vars.js | 8 -- .../src/ipc/network/prepare-request.js | 11 +- .../tests/network/prepare-request.spec.js | 2 +- packages/bruno-js/src/bruno-request.js | 103 +++++++++++++++++- .../collection/echo/echo bigint.bru | 44 ++++++++ 8 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 packages/bruno-tests/collection/echo/echo bigint.bru diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index 6e3bf48dd..43fc1118d 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -67,6 +67,7 @@ if (!SERVER_RENDERED) { 'bru.setVar(key,value)', 'bru.deleteVar(key)', 'bru.setNextRequest(requestName)', + 'req.disableParsingResponseJson()' 'bru.getRequestVar(key)', 'bru.sleep(ms)' ]; diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/index.js index d8a4770a5..97745f060 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/index.js @@ -17,8 +17,6 @@ const Timeline = ({ request, response }) => { }); }); - let requestData = safeStringifyJSON(request.data); - return (
@@ -33,9 +31,10 @@ const Timeline = ({ request, response }) => { ); })} - {requestData ? ( + {request.data ? (
-            {'>'} data {requestData}
+            {'>'} data{' '}
+            
{request.data}
) : null}
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index b343e62c2..bbdcdb55b 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -272,7 +272,7 @@ const configureRequest = async ( return axiosInstance; }; -const parseDataFromResponse = (response) => { +const parseDataFromResponse = (response, disableParsingResponseJson = false) => { // Parse the charset from content type: https://stackoverflow.com/a/33192813 const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || ''); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals @@ -290,7 +290,9 @@ const parseDataFromResponse = (response) => { // Filter out ZWNBSP character // https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d data = data.replace(/^\uFEFF/, ''); - data = JSON.parse(data); + if(!disableParsingResponseJson) { + data = JSON.parse(data); + } } catch {} return { data, dataBuffer }; @@ -540,7 +542,7 @@ const registerNetworkIpc = (mainWindow) => { // Continue with the rest of the request lifecycle - post response vars, script, assertions, tests - const { data, dataBuffer } = parseDataFromResponse(response); + const { data, dataBuffer } = parseDataFromResponse(response, request.__brunoDisableParsingResponseJson); response.data = data; response.responseTime = responseTime; @@ -701,7 +703,7 @@ const registerNetworkIpc = (mainWindow) => { } } - const { data } = parseDataFromResponse(response); + const { data } = parseDataFromResponse(response, request.__brunoDisableParsingResponseJson); response.data = data; await runPostResponse( @@ -969,7 +971,7 @@ const registerNetworkIpc = (mainWindow) => { response = await axiosInstance(request); timeEnd = Date.now(); - const { data, dataBuffer } = parseDataFromResponse(response); + const { data, dataBuffer } = parseDataFromResponse(response, request.__brunoDisableParsingResponseJson); response.data = data; response.responseTime = response.headers.get('request-duration'); diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 0d95867b8..899b3d0f2 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -59,14 +59,6 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn const contentType = getContentType(request.headers); if (contentType.includes('json')) { - if (typeof request.data === 'object') { - try { - let parsed = JSON.stringify(request.data); - parsed = _interpolate(parsed); - request.data = JSON.parse(parsed); - } catch (err) {} - } - if (typeof request.data === 'string') { if (request.data.length) { request.data = _interpolate(request.data); diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 8a747f5d8..74cb85ac6 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -1,7 +1,6 @@ const os = require('os'); const { get, each, filter, extend, compact } = require('lodash'); const decomment = require('decomment'); -var JSONbig = require('json-bigint'); const FormData = require('form-data'); const fs = require('fs'); const path = require('path'); @@ -342,16 +341,10 @@ const prepareRequest = (item, collection) => { if (!contentTypeDefined) { axiosRequest.headers['content-type'] = 'application/json'; } - let jsonBody; try { - jsonBody = decomment(request?.body?.json); + axiosRequest.data = decomment(request?.body?.json); } catch (error) { - jsonBody = request?.body?.json; - } - try { - axiosRequest.data = JSONbig.parse(jsonBody); - } catch (error) { - axiosRequest.data = jsonBody; + axiosRequest.data = request?.body?.json; } } diff --git a/packages/bruno-electron/tests/network/prepare-request.spec.js b/packages/bruno-electron/tests/network/prepare-request.spec.js index e3441953b..808a127d9 100644 --- a/packages/bruno-electron/tests/network/prepare-request.spec.js +++ b/packages/bruno-electron/tests/network/prepare-request.spec.js @@ -6,7 +6,7 @@ describe('prepare-request: prepareRequest', () => { describe('Decomments request body', () => { it('If request body is valid JSON', async () => { const body = { mode: 'json', json: '{\n"test": "{{someVar}}" // comment\n}' }; - const expected = { test: '{{someVar}}' }; + const expected = '{\n"test": "{{someVar}}" \n}'; const result = prepareRequest({ request: { body } }, {}); expect(result.data).toEqual(expected); }); diff --git a/packages/bruno-js/src/bruno-request.js b/packages/bruno-js/src/bruno-request.js index 909adf92a..cf5f59aca 100644 --- a/packages/bruno-js/src/bruno-request.js +++ b/packages/bruno-js/src/bruno-request.js @@ -1,11 +1,34 @@ class BrunoRequest { + /** + * The following properties are available as shorthand: + * - req.url + * - req.method + * - req.headers + * - req.timeout + * - req.body + * + * Above shorthands are useful for accessing the request properties directly in the scripts + * It must be noted that the user cannot set these properties directly. + * They should use the respective setter methods to set these properties. + */ constructor(req) { this.req = req; this.url = req.url; this.method = req.method; this.headers = req.headers; - this.body = req.data; this.timeout = req.timeout; + + /** + * We automatically parse the JSON body if the content type is JSON + * This is to make it easier for the user to access the body directly + * + * It must be noted that the request data is always a string and is what gets sent over the network + * If the user wants to access the raw data, they can use getBody({raw: true}) method + */ + const isJson = this.hasJSONContentType(this.req.headers); + if (isJson) { + this.body = this.__safeParseJSON(req.data); + } } getUrl() { @@ -13,6 +36,7 @@ class BrunoRequest { } setUrl(url) { + this.url = url; this.req.url = url; } @@ -37,6 +61,7 @@ class BrunoRequest { } setMethod(method) { + this.method = method; this.req.method = method; } @@ -45,6 +70,7 @@ class BrunoRequest { } setHeaders(headers) { + this.headers = headers; this.req.headers = headers; } @@ -53,15 +79,60 @@ class BrunoRequest { } setHeader(name, value) { + this.headers[name] = value; this.req.headers[name] = value; } - getBody() { + hasJSONContentType(headers) { + const contentType = headers?.['Content-Type'] || headers?.['content-type'] || ''; + return contentType.includes('json'); + } + + /** + * Get the body of the request + * + * We automatically parse and return the JSON body if the content type is JSON + * If the user wants the raw body, they can pass the raw option as true + */ + getBody(options = {}) { + if (options.raw) { + return this.req.data; + } + + const isJson = this.hasJSONContentType(this.req.headers); + if (isJson) { + return this.__safeParseJSON(this.req.data); + } + return this.req.data; } - setBody(data) { + /** + * If the content type is JSON and if the data is an object + * - We set the body property as the object itself + * - We set the request data as the stringified JSON as it is what gets sent over the network + * Otherwise + * - We set the request data as the data itself + * - We set the body property as the data itself + * + * If the user wants to override this behavior, they can pass the raw option as true + */ + setBody(data, options = {}) { + if (options.raw) { + this.req.data = data; + this.body = data; + return; + } + + const isJson = this.hasJSONContentType(this.req.headers); + if (isJson && this.__isObject(data)) { + this.body = data; + this.req.data = this.__safeStringifyJSON(data); + return; + } + this.req.data = data; + this.body = data; } setMaxRedirects(maxRedirects) { @@ -73,8 +144,34 @@ class BrunoRequest { } setTimeout(timeout) { + this.timeout = timeout; this.req.timeout = timeout; } + + __safeParseJSON(str) { + try { + return JSON.parse(str); + } catch (e) { + return str; + } + } + + __safeStringifyJSON(obj) { + try { + return JSON.stringify(obj); + } catch (e) { + return obj; + } + } + + __isObject(obj) { + return obj !== null && typeof obj === 'object'; + } + + + disableParsingResponseJson() { + this.req.__brunoDisableParsingResponseJson = true; + } } module.exports = BrunoRequest; diff --git a/packages/bruno-tests/collection/echo/echo bigint.bru b/packages/bruno-tests/collection/echo/echo bigint.bru new file mode 100644 index 000000000..32a6f145b --- /dev/null +++ b/packages/bruno-tests/collection/echo/echo bigint.bru @@ -0,0 +1,44 @@ +meta { + name: echo bigint + type: http + seq: 6 +} + +post { + url: {{host}}/api/echo/json + body: json + auth: none +} + +headers { + foo: bar +} + +auth:basic { + username: asd + password: j +} + +auth:bearer { + token: +} + +body:json { + { + "hello": 990531470713421825 + } +} + +body:text { + { + "hello": 990531470713421825 + } +} + +assert { + res.status: eq 200 +} + +script:pre-request { + bru.setVar("foo", "foo-world-2"); +}