From 103e0a2444f9625a2e81ce6d7da91b2bdad85667 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 7 Aug 2024 13:11:58 +0530 Subject: [PATCH] fix: lossless json serialization --- package-lock.json | 4 ++- packages/bruno-electron/package.json | 5 ++- packages/bruno-electron/src/ipc/collection.js | 10 +++++- .../src/ipc/network/axios-instance.js | 18 +++++++++- .../bruno-electron/src/ipc/network/index.js | 4 +-- .../src/ipc/network/interpolate-vars.js | 11 +++--- .../src/ipc/network/prepare-request.js | 4 +-- packages/bruno-electron/src/utils/common.js | 29 ++++++++-------- .../collection/echo/echo bigint.bru | 34 +++++++++++++++++++ .../collection/echo/echo bom json.bru | 2 +- .../bruno-tests/collection/echo/echo json.bru | 2 +- .../collection/echo/echo plaintext.bru | 2 +- .../collection/echo/echo xml parsed.bru | 2 +- .../collection/echo/echo xml raw.bru | 2 +- 14 files changed, 97 insertions(+), 32 deletions(-) create mode 100644 packages/bruno-tests/collection/echo/echo bigint.bru diff --git a/package-lock.json b/package-lock.json index e021bf38f..cd3df9bdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19668,7 +19668,7 @@ }, "packages/bruno-electron": { "name": "bruno", - "version": "v1.23.1", + "version": "v1.24.0", "dependencies": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", @@ -19698,6 +19698,7 @@ "js-yaml": "^4.1.0", "json-bigint": "^1.0.0", "lodash": "^4.17.21", + "lossless-json": "^4.0.1", "mime-types": "^2.1.35", "mustache": "^4.2.0", "nanoid": "3.3.4", @@ -26347,6 +26348,7 @@ "js-yaml": "^4.1.0", "json-bigint": "^1.0.0", "lodash": "^4.17.21", + "lossless-json": "^4.0.1", "mime-types": "^2.1.35", "mustache": "^4.2.0", "nanoid": "3.3.4", diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 3fd713d86..6e81f056a 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -19,7 +19,9 @@ "test": "jest" }, "jest": { - "modulePaths": ["node_modules"] + "modulePaths": [ + "node_modules" + ] }, "dependencies": { "@aws-sdk/credential-providers": "3.525.0", @@ -50,6 +52,7 @@ "js-yaml": "^4.1.0", "json-bigint": "^1.0.0", "lodash": "^4.17.21", + "lossless-json": "^4.0.1", "mime-types": "^2.1.35", "mustache": "^4.2.0", "nanoid": "3.3.4", diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 2233f81bc..2d8479e84 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -16,13 +16,21 @@ const { sanitizeDirectoryName } = require('../utils/filesystem'); const { openCollectionDialog } = require('../app/collections'); -const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON } = require('../utils/common'); +const { generateUidBasedOnHash, safeParseJSON, safeStringifyJSON } = require('../utils/common'); const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies'); const EnvironmentSecretsStore = require('../store/env-secrets'); const environmentSecretsStore = new EnvironmentSecretsStore(); +const stringifyJson = async (str) => { + try { + return JSON.stringify(str, null, 2); + } catch (err) { + return Promise.reject(err); + } +}; + const envHasSecrets = (environment = {}) => { const secrets = _.filter(environment.variables, (v) => v.secret); diff --git a/packages/bruno-electron/src/ipc/network/axios-instance.js b/packages/bruno-electron/src/ipc/network/axios-instance.js index dcc57a07e..83da9206f 100644 --- a/packages/bruno-electron/src/ipc/network/axios-instance.js +++ b/packages/bruno-electron/src/ipc/network/axios-instance.js @@ -1,6 +1,7 @@ const URL = require('url'); const Socket = require('net').Socket; const axios = require('axios'); +const { safeStringifyJSON } = require('../../utils/common'); const connectionCache = new Map(); // Cache to store checkConnection() results const LOCAL_IPV6 = '::1'; @@ -49,7 +50,22 @@ const checkConnection = (host, port) => */ function makeAxiosInstance() { /** @type {axios.AxiosInstance} */ - const instance = axios.create(); + const instance = axios.create({ + transformRequest: function transformRequest(data, headers) { + const isObject = (thing) => thing !== null && typeof thing === 'object'; + const hasJSONContentType = () => { + const contentType = headers?.['Content-Type'] || headers?.['content-type'] || ''; + return contentType.includes('application/json'); + }; + + if (isObject(data) && hasJSONContentType()) { + headers.setContentType('application/json', false); + return safeStringifyJSON(data); + } + + axios.defaults.transformRequest.forEach((tr) => tr(data, headers)); + } + }); instance.interceptors.request.use(async (config) => { const url = URL.parse(config.url); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 9659f45b4..5e094aa60 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -16,7 +16,7 @@ const prepareRequest = require('./prepare-request'); const prepareCollectionRequest = require('./prepare-collection-request'); const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request'); const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token'); -const { uuid } = require('../../utils/common'); +const { uuid, parseJson } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { interpolateString } = require('./interpolate-string'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); @@ -285,7 +285,7 @@ const parseDataFromResponse = (response) => { // Filter out ZWNBSP character // https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d data = data.replace(/^\uFEFF/, ''); - data = JSON.parse(data); + data = parseJson(data); } catch {} return { data, dataBuffer }; diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 0d95867b8..b00af5948 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -1,5 +1,6 @@ const { interpolate } = require('@usebruno/common'); -const { each, forOwn, cloneDeep, find } = require('lodash'); +const { each, forOwn, cloneDeep } = require('lodash'); +const { parseJson, stringifyJson } = require('../../utils/common'); const getContentType = (headers = {}) => { let contentType = ''; @@ -61,9 +62,9 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn if (contentType.includes('json')) { if (typeof request.data === 'object') { try { - let parsed = JSON.stringify(request.data); + let parsed = stringifyJson(request.data); parsed = _interpolate(parsed); - request.data = JSON.parse(parsed); + request.data = parseJson(parsed); } catch (err) {} } @@ -75,9 +76,9 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn } else if (contentType === 'application/x-www-form-urlencoded') { if (typeof request.data === 'object') { try { - let parsed = JSON.stringify(request.data); + let parsed = stringifyJson(request.data); parsed = _interpolate(parsed); - request.data = JSON.parse(parsed); + request.data = parseJson(parsed); } catch (err) {} } } else { diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 8a747f5d8..ee2b6e88a 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -1,7 +1,7 @@ const os = require('os'); const { get, each, filter, extend, compact } = require('lodash'); const decomment = require('decomment'); -var JSONbig = require('json-bigint'); +const { parseJson } = require('../../utils/common'); const FormData = require('form-data'); const fs = require('fs'); const path = require('path'); @@ -349,7 +349,7 @@ const prepareRequest = (item, collection) => { jsonBody = request?.body?.json; } try { - axiosRequest.data = JSONbig.parse(jsonBody); + axiosRequest.data = parseJson(jsonBody); } catch (error) { axiosRequest.data = jsonBody; } diff --git a/packages/bruno-electron/src/utils/common.js b/packages/bruno-electron/src/utils/common.js index 50b17bb38..6a9b2cd60 100644 --- a/packages/bruno-electron/src/utils/common.js +++ b/packages/bruno-electron/src/utils/common.js @@ -1,4 +1,5 @@ const { customAlphabet } = require('nanoid'); +const { stringify, parse, LosslessNumber } = require('lossless-json'); // a customized version of nanoid without using _ and - const uuid = () => { @@ -9,25 +10,21 @@ const uuid = () => { return customNanoId(); }; -const stringifyJson = async (str) => { - try { - return JSON.stringify(str, null, 2); - } catch (err) { - return Promise.reject(err); - } +const stringifyJson = (data) => { + return stringify(data); }; -const parseJson = async (obj) => { - try { - return JSON.parse(obj); - } catch (err) { - return Promise.reject(err); - } +const parseJson = (data) => { + return parse(data, null, (value) => { + // By default, this will return the LosslessNumber object for big ints + // need to convert it into a number + return new LosslessNumber(value).valueOf(); + }); }; const safeStringifyJSON = (data) => { try { - return JSON.stringify(data); + return stringify(data); } catch (e) { return data; } @@ -35,7 +32,11 @@ const safeStringifyJSON = (data) => { const safeParseJSON = (data) => { try { - return JSON.parse(data); + return parse(data, null, (value) => { + // By default, this will return the LosslessNumber object for big ints + // need to convert it into a number + return new LosslessNumber(value).valueOf(); + }); } catch (e) { return data; } 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..601cf0cd8 --- /dev/null +++ b/packages/bruno-tests/collection/echo/echo bigint.bru @@ -0,0 +1,34 @@ +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 + } +} + +assert { + res.status: eq 200 +} diff --git a/packages/bruno-tests/collection/echo/echo bom json.bru b/packages/bruno-tests/collection/echo/echo bom json.bru index 117c329ba..6bb320226 100644 --- a/packages/bruno-tests/collection/echo/echo bom json.bru +++ b/packages/bruno-tests/collection/echo/echo bom json.bru @@ -8,4 +8,4 @@ get { url: {{host}}/api/echo/bom-json-test body: none auth: none -} \ No newline at end of file +} diff --git a/packages/bruno-tests/collection/echo/echo json.bru b/packages/bruno-tests/collection/echo/echo json.bru index 09a8ed90c..1749eda6b 100644 --- a/packages/bruno-tests/collection/echo/echo json.bru +++ b/packages/bruno-tests/collection/echo/echo json.bru @@ -1,7 +1,7 @@ meta { name: echo json type: http - seq: 1 + seq: 2 } post { diff --git a/packages/bruno-tests/collection/echo/echo plaintext.bru b/packages/bruno-tests/collection/echo/echo plaintext.bru index e6c9b3fdc..56a23d345 100644 --- a/packages/bruno-tests/collection/echo/echo plaintext.bru +++ b/packages/bruno-tests/collection/echo/echo plaintext.bru @@ -1,7 +1,7 @@ meta { name: echo plaintext type: http - seq: 2 + seq: 3 } post { diff --git a/packages/bruno-tests/collection/echo/echo xml parsed.bru b/packages/bruno-tests/collection/echo/echo xml parsed.bru index a8ff5e26a..586541664 100644 --- a/packages/bruno-tests/collection/echo/echo xml parsed.bru +++ b/packages/bruno-tests/collection/echo/echo xml parsed.bru @@ -1,7 +1,7 @@ meta { name: echo xml parsed type: http - seq: 3 + seq: 4 } post { diff --git a/packages/bruno-tests/collection/echo/echo xml raw.bru b/packages/bruno-tests/collection/echo/echo xml raw.bru index 9773d4a3d..6a02ac238 100644 --- a/packages/bruno-tests/collection/echo/echo xml raw.bru +++ b/packages/bruno-tests/collection/echo/echo xml raw.bru @@ -1,7 +1,7 @@ meta { name: echo xml raw type: http - seq: 4 + seq: 5 } post {