Skip to content

Commit

Permalink
feat: add recursive interpolate
Browse files Browse the repository at this point in the history
fixes #2227
  • Loading branch information
leoferreiralima committed May 3, 2024
1 parent c17e4ef commit 9f72495
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 5 deletions.
7 changes: 6 additions & 1 deletion packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
* LICENSE file at https://github.com/graphql/codemirror-graphql/tree/v0.8.3
*/

// Todo: Fix this
// import { interpolate } from '@usebruno/common';
import brunoCommon from '@usebruno/common';
const { interpolate } = brunoCommon;

let CodeMirror;
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
const { get } = require('lodash');
Expand All @@ -21,7 +26,7 @@ if (!SERVER_RENDERED) {
// str is of format {{variableName}}, extract variableName
// we are seeing that from the gql query editor, the token string is of format variableName
const variableName = str.replace('{{', '').replace('}}', '').trim();
const variableValue = get(options.variables, variableName);
const variableValue = interpolate(get(options.variables, variableName), options.variables);

const into = document.createElement('div');
const descriptionDiv = document.createElement('div');
Expand Down
1 change: 1 addition & 0 deletions packages/bruno-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"scripts": {
"clean": "rimraf dist",
"test": "jest",
"test:watch": "jest --watch",
"prebuild": "npm run clean",
"build": "rollup -c",
"prepack": "npm run test && npm run build"
Expand Down
128 changes: 128 additions & 0 deletions packages/bruno-common/src/interpolate/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,131 @@ describe('interpolate - value edge cases', () => {
expect(result).toBe(inputString);
});
});

describe('interpolate - recursive', () => {
it('should replace placeholders with 1 level of recursion with values from the object', () => {
const inputString = '{{user.message}}';
const inputObject = {
'user.message': 'Hello, my name is {{user.name}} and I am {{user.age}} years old',
'user.name': 'Bruno',
user: {
age: 4
}
};

const result = interpolate(inputString, inputObject);

expect(result).toBe('Hello, my name is Bruno and I am 4 years old');
});

it('should replace placeholders with 2 level of recursion with values from the object', () => {
const inputString = '{{user.message}}';
const inputObject = {
'user.message': 'Hello, my name is {{user.name}} and I am {{user.age}} years old',
'user.name': 'Bruno {{user.lastName}}',
'user.lastName': 'Dog',
user: {
age: 4
}
};

const result = interpolate(inputString, inputObject);

expect(result).toBe('Hello, my name is Bruno Dog and I am 4 years old');
});

it('should replace placeholders with 3 level of recursion with values from the object', () => {
const inputString = '{{user.message}}';
const inputObject = {
'user.message': 'Hello, my name is {{user.full_name}} and I am {{user.age}} years old',
'user.full_name': '{{user.name}} {{user.lastName}}',
'user.name': 'Bruno',
'user.lastName': 'Dog',
user: {
age: 4
}
};

const result = interpolate(inputString, inputObject);

expect(result).toBe('Hello, my name is Bruno Dog and I am 4 years old');
});

it('should handle missing values with 1 level of recursion by leaving the placeholders unchanged using {{}} as delimiters', () => {
const inputString = '{{user.message}}';
const inputObject = {
'user.message': 'Hello, my name is {{user.name}} and I am {{user.age}} years old',
user: {
age: 4
}
};

const result = interpolate(inputString, inputObject);

expect(result).toBe('Hello, my name is {{user.name}} and I am 4 years old');
});

it('should handle all valid keys with 1 level of recursion', () => {
const message = `
Hi, I am {{user.full_name}},
I am {{user.age}} years old.
My favorite food is {{user.fav-food[0]}} and {{user.fav-food[1]}}.
I like attention: {{user.want.attention}}
`;
const inputObject = {
user: {
message,
full_name: 'Bruno',
age: 4,
'fav-food': ['egg', 'meat'],
'want.attention': true
}
};

const inputStr = '{{user.message}}';
const expectedStr = `
Hi, I am Bruno,
I am 4 years old.
My favorite food is egg and meat.
I like attention: true
`;
const result = interpolate(inputStr, inputObject);
expect(result).toBe(expectedStr);
});

it('should not process 1 level of cycle recursion with values from the object', () => {
const inputString = '{{recursion}}';
const inputObject = {
recursion: '{{recursion}}'
};

const result = interpolate(inputString, inputObject);

expect(result).toBe('{{recursion}}');
});

it('should not process 2 level of cycle recursion with values from the object', () => {
const inputString = '{{recursion}}';
const inputObject = {
recursion: '{{recursion2}}',
recursion2: '{{recursion}}'
};

const result = interpolate(inputString, inputObject);

expect(result).toBe('{{recursion2}}');
});

it('should not process 3 level of cycle recursion with values from the object', () => {
const inputString = '{{recursion}}';
const inputObject = {
recursion: '{{recursion2}}',
recursion2: '{{recursion3}}',
recursion3: '{{recursion}}'
};

const result = interpolate(inputString, inputObject);

expect(result).toBe('{{recursion2}}');
});
});
20 changes: 16 additions & 4 deletions packages/bruno-common/src/interpolate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,33 @@
* Output: Hello, my name is Bruno and I am 4 years old
*/

import { Set } from 'typescript';
import { flattenObject } from '../utils';

const interpolate = (str: string, obj: Record<string, any>): string => {
if (!str || typeof str !== 'string' || !obj || typeof obj !== 'object') {
return str;
}

const patternRegex = /\{\{([^}]+)\}\}/g;
const flattenedObj = flattenObject(obj);
const result = str.replace(patternRegex, (match, placeholder) => {

return replace(str, flattenedObj);
};

const replace = (str: string, flattenedObj: Record<string, any>, matches: Set<string> = new Set<string>()): string => {
const patternRegex = /\{\{([^}]+)\}\}/g;

return str.replace(patternRegex, (match, placeholder) => {
const replacement = flattenedObj[placeholder];

if (patternRegex.test(replacement) && !matches.has(match)) {
matches.add(match);
return replace(replacement, flattenedObj, matches);
}

matches.add(match);
return replacement !== undefined ? replacement : match;
});

return result;
};

export default interpolate;

0 comments on commit 9f72495

Please sign in to comment.