From 5f407d4805a941733240da3bb48bbba52c0dd109 Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 00:05:02 +0300 Subject: [PATCH 01/14] Added a unit test to encode a javascript object --- packages/common/test/util/index.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/common/test/util/index.test.js b/packages/common/test/util/index.test.js index eb7f91ddc..667ef9611 100644 --- a/packages/common/test/util/index.test.js +++ b/packages/common/test/util/index.test.js @@ -36,6 +36,11 @@ describe('encode', () => { expect(() => encode({})).to.throw(errorMsg); expect(() => encode(() => {})).to.throw(errorMsg); }); + it('should encode a javascript object', () => { + const obj = {"name": "Jane Doe"} + + expect(encode(obj)).to.eql("eyJuYW1lIjoiSmFuZSBEb2UifQ=="); + }); }); describe('decode', () => { From 60f32795626cc520911ebad933e8b48fe21b748f Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 00:07:05 +0300 Subject: [PATCH 02/14] Added the logic to encode Javascript objects by stringifying them first --- packages/common/src/util/base64.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index 810f8cea0..71500ba20 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -9,9 +9,17 @@ * const encoded = encode('Hello World'); * console.log(encoded); // Output: SGVsbG8gV29ybGQ= */ -export const encode = data => Buffer.from(data, 'utf-8').toString('base64'); +export const encode = data => { + let str = data; -/** + if(typeof str === "object" && str !== null && Object.keys(str).length > 0){ + str = JSON.stringify(str); + } + + return Buffer.from(str, 'utf-8').toString('base64'); +} + +/**4 * Decodes a Base64 encoded string back to its original format. * @function * @public From 4d77f48e754faa5281622e9dbc468f1ca635eef2 Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 00:07:33 +0300 Subject: [PATCH 03/14] Added a unit test to decode a javascript object --- packages/common/test/util/index.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/common/test/util/index.test.js b/packages/common/test/util/index.test.js index 667ef9611..ae385c228 100644 --- a/packages/common/test/util/index.test.js +++ b/packages/common/test/util/index.test.js @@ -61,4 +61,8 @@ describe('decode', () => { it('should decode an empty Base64 string', () => { expect(decode('')).to.eql(''); }); + it('should decode a JSON object into a standard javascript object', () => { + const obj = {name: "Jane Doe"} + expect(decode('eyJuYW1lIjoiSmFuZSBEb2UifQ==')).to.eql(obj); + }); }); From d7cd95c4b99f29a5a369564b38ba66ac65c966ab Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 00:08:07 +0300 Subject: [PATCH 04/14] Added a logic to decode a JSON object and parse it back to a standard javascript object --- packages/common/src/util/base64.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index 71500ba20..f21fce3d3 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -30,5 +30,13 @@ export const encode = data => { * const decoded = decode('SGVsbG8gV29ybGQ='); * console.log(decoded); // Output: Hello World */ -export const decode = base64Data => - Buffer.from(base64Data, 'base64').toString('utf-8'); +export const decode = base64Data =>{ + let decodedObject = Buffer.from(base64Data, 'base64').toString('utf-8'); + + if(['[', '{'].includes(decodedObject[0])){ + decodedObject = JSON.parse(decodedObject); + } + + return decodedObject; +} + From 9f908c0162d1b4688439fc265a9a2c3524a2cd18 Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 20:40:21 +0300 Subject: [PATCH 05/14] (refactor) Stringify any object parsed that is not a string --- packages/common/src/util/base64.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index f21fce3d3..24cd4375e 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -12,7 +12,7 @@ export const encode = data => { let str = data; - if(typeof str === "object" && str !== null && Object.keys(str).length > 0){ + if(typeof data !== "string"){ str = JSON.stringify(str); } From 8109acd6da0d9077d6c90184d573d567f23e7898 Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 20:41:05 +0300 Subject: [PATCH 06/14] (refactor) remove this since we are stringifying anything that isn't a string empty objects included --- packages/common/test/util/index.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/common/test/util/index.test.js b/packages/common/test/util/index.test.js index ae385c228..7faaa40a8 100644 --- a/packages/common/test/util/index.test.js +++ b/packages/common/test/util/index.test.js @@ -33,7 +33,6 @@ describe('encode', () => { expect(() => encode(123)).to.throw(errorMsg); expect(() => encode(true)).to.throw(errorMsg); expect(() => encode(null)).to.throw(errorMsg); - expect(() => encode({})).to.throw(errorMsg); expect(() => encode(() => {})).to.throw(errorMsg); }); it('should encode a javascript object', () => { From 8166c2ebc4df881c227aeb70283c7015106a0a8e Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 20:49:43 +0300 Subject: [PATCH 07/14] Updated the docs to make it clear that we can pass any string ot JSON-serializable object --- packages/common/src/util/base64.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index 24cd4375e..0beb4a900 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -1,13 +1,17 @@ /** - * Encodes a given string into Base64 format. + * Encodes a given string or Javascript object into Base64 format. * @function * @public * @namespace util - * @param {string} data - The string to be encoded. + * @param {string | object} data - The string or object to be encoded. * @returns {string} - The Base64 encoded string. * @example Encode a string - * const encoded = encode('Hello World'); + * const encodedString = encode('Hello World'); * console.log(encoded); // Output: SGVsbG8gV29ybGQ= + * @example Encode an object + * const encodedObject = encode({name: 'Jane Doe'}) + * console.log(encodedObject); //output eyJuYW1lIjoiSmFuZSBEb2UifQ== + * */ export const encode = data => { let str = data; From 35bf1b90c5fc0ddd00e964f1b598ea3a1f6525fb Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 21:25:06 +0300 Subject: [PATCH 08/14] Update the test suite to expect any non-string values to be stringified before encoding. This means no errors thrown when numbers, booleans and regex expressions are passed --- packages/common/test/util/index.test.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/common/test/util/index.test.js b/packages/common/test/util/index.test.js index 7faaa40a8..d671175d2 100644 --- a/packages/common/test/util/index.test.js +++ b/packages/common/test/util/index.test.js @@ -29,15 +29,11 @@ describe('encode', () => { it('should encode emoji to Base64', () => { expect(encode('😀')).to.eql('8J+YgA=='); }); - it('should throw an error if the string is not a string', () => { - expect(() => encode(123)).to.throw(errorMsg); - expect(() => encode(true)).to.throw(errorMsg); - expect(() => encode(null)).to.throw(errorMsg); + it('should throw an error if a function is passed', () => { expect(() => encode(() => {})).to.throw(errorMsg); }); it('should encode a javascript object', () => { const obj = {"name": "Jane Doe"} - expect(encode(obj)).to.eql("eyJuYW1lIjoiSmFuZSBEb2UifQ=="); }); }); From 90dba913b95bf729a07c8d511160963423645d71 Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 21:27:37 +0300 Subject: [PATCH 09/14] Log and error in case there's a problem in the stringification step --- packages/common/src/util/base64.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index 0beb4a900..3611cccb0 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -17,7 +17,11 @@ export const encode = data => { let str = data; if(typeof data !== "string"){ - str = JSON.stringify(str); + try { + str = JSON.stringify(str); + } catch (e){ + console.log(e.message); + } } return Buffer.from(str, 'utf-8').toString('base64'); From 07a4dec92528141f1e2a6447eb07d69c40fa1044 Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 21:36:45 +0300 Subject: [PATCH 10/14] Updated the docs of the decode function to include JSON objects too --- packages/common/src/util/base64.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index 3611cccb0..d5d0ea3c8 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -33,10 +33,12 @@ export const encode = data => { * @public * @namespace util * @param {string} base64Data - The Base64 encoded string. - * @returns {string} - The decoded string. + * @returns {string | object} - The decoded string or JavaScript Object. * @example Decode a Base64 string * const decoded = decode('SGVsbG8gV29ybGQ='); - * console.log(decoded); // Output: Hello World + * @example Decode a Base64 JSON object to a standard JavaScript object + * const decoded = decode('eyJuYW1lIjoiSmFuZSBEb2UifQ=='); + * console.log(decoded); // Output: {name: 'Jane Doe'} */ export const decode = base64Data =>{ let decodedObject = Buffer.from(base64Data, 'base64').toString('utf-8'); From 557a7f532bff289b555d450a900e35626dfc0620 Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 22:46:16 +0300 Subject: [PATCH 11/14] Changed the signatures of the decode and encode functions to include an options object that allows users to explicitly disable the JSON parse step --- packages/common/src/util/base64.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index d5d0ea3c8..5f179f116 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -1,9 +1,12 @@ +import _ from 'lodash'; + /** * Encodes a given string or Javascript object into Base64 format. * @function * @public * @namespace util * @param {string | object} data - The string or object to be encoded. + * @param {object} options - Options. * @returns {string} - The Base64 encoded string. * @example Encode a string * const encodedString = encode('Hello World'); @@ -11,12 +14,13 @@ * @example Encode an object * const encodedObject = encode({name: 'Jane Doe'}) * console.log(encodedObject); //output eyJuYW1lIjoiSmFuZSBEb2UifQ== - * + * @example To skip the JSON stringification step + * const encodedObject = encode('Hello World', {parseJson: false}) */ -export const encode = data => { +export const encode = (data, options = {parseJson: true}) => { let str = data; - if(typeof data !== "string"){ + if(typeof data !== "string" && options.parseJson){ try { str = JSON.stringify(str); } catch (e){ @@ -33,18 +37,25 @@ export const encode = data => { * @public * @namespace util * @param {string} base64Data - The Base64 encoded string. + * @param {object} options - Options. * @returns {string | object} - The decoded string or JavaScript Object. * @example Decode a Base64 string * const decoded = decode('SGVsbG8gV29ybGQ='); * @example Decode a Base64 JSON object to a standard JavaScript object * const decoded = decode('eyJuYW1lIjoiSmFuZSBEb2UifQ=='); * console.log(decoded); // Output: {name: 'Jane Doe'} + * @example To skip the JSON stringification step + * const decodedString = decode('Hello World', {parseJson: false}) */ -export const decode = base64Data =>{ +export const decode = (base64Data, options = {parseJson: true}) =>{ let decodedObject = Buffer.from(base64Data, 'base64').toString('utf-8'); - if(['[', '{'].includes(decodedObject[0])){ - decodedObject = JSON.parse(decodedObject); + if((_.startsWith(decodedObject, '[') || _.startsWith(decodedObject, '{')) && options.parseJson) { + try { + decodedObject = JSON.parse(decodedObject); + } catch (e) { + console.log(e.message); + } } return decodedObject; From 0b31a9f67a9494c013636f8db223b6a052989d0e Mon Sep 17 00:00:00 2001 From: pius Date: Tue, 14 Jan 2025 22:47:59 +0300 Subject: [PATCH 12/14] Expanded the tests to cater for the extra complexity added to the encode and decode functions i.e. the `parseJson` parameter --- packages/common/test/util/index.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/common/test/util/index.test.js b/packages/common/test/util/index.test.js index d671175d2..cea484550 100644 --- a/packages/common/test/util/index.test.js +++ b/packages/common/test/util/index.test.js @@ -22,6 +22,9 @@ describe('encode', () => { it('should encode a string to Base64', () => { expect(encode('Hello World')).to.eql('SGVsbG8gV29ybGQ='); }); + it('should encode a string to Base64 while skipping the JSON stringification step', () => { + expect(encode('Hello World', {parseJson: false})).to.eql('SGVsbG8gV29ybGQ='); + }); it('should encode an empty string to Base64', () => { expect(encode('')).to.eql(''); @@ -52,6 +55,9 @@ describe('decode', () => { it('should decode a Base64 string back to its original string', () => { expect(decode('SGVsbG8gV29ybGQ=')).to.eql('Hello World'); }); + it('should decode a Base64 string back to its original string without needing to JSON parse', () => { + expect(decode('SGVsbG8gV29ybGQ=', {parseJson: false})).to.eql('Hello World'); + }); it('should decode an empty Base64 string', () => { expect(decode('')).to.eql(''); From b3d7f595b34709fcf8b63b664dc1020c3ce92ea3 Mon Sep 17 00:00:00 2001 From: pius Date: Wed, 15 Jan 2025 08:53:59 +0300 Subject: [PATCH 13/14] Changeset markdown file --- .changeset/light-spiders-move.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/light-spiders-move.md diff --git a/.changeset/light-spiders-move.md b/.changeset/light-spiders-move.md new file mode 100644 index 000000000..9679ef656 --- /dev/null +++ b/.changeset/light-spiders-move.md @@ -0,0 +1,6 @@ +--- +'@openfn/language-common': minor +--- + +Common util functions `encode` and `decode` can now take a JavaScript object and +stringify From 0078389c5ff616c352e2b0f4f17d891caea7ce78 Mon Sep 17 00:00:00 2001 From: pius Date: Wed, 15 Jan 2025 12:11:37 +0300 Subject: [PATCH 14/14] (refactor) renamed the `decodedObject` to `decodedValue` --- packages/common/src/util/base64.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/common/src/util/base64.js b/packages/common/src/util/base64.js index 5f179f116..86db0ff9d 100644 --- a/packages/common/src/util/base64.js +++ b/packages/common/src/util/base64.js @@ -48,16 +48,16 @@ export const encode = (data, options = {parseJson: true}) => { * const decodedString = decode('Hello World', {parseJson: false}) */ export const decode = (base64Data, options = {parseJson: true}) =>{ - let decodedObject = Buffer.from(base64Data, 'base64').toString('utf-8'); + let decodedValue = Buffer.from(base64Data, 'base64').toString('utf-8'); - if((_.startsWith(decodedObject, '[') || _.startsWith(decodedObject, '{')) && options.parseJson) { + if((_.startsWith(decodedValue, '[') || _.startsWith(decodedValue, '{')) && options.parseJson) { try { - decodedObject = JSON.parse(decodedObject); + decodedValue = JSON.parse(decodedValue); } catch (e) { console.log(e.message); } } - return decodedObject; + return decodedValue; }