mirror of
https://github.com/usebruno/bruno.git
synced 2025-05-05 15:32:58 +00:00
Move mock builtin vars interpolation to bruno-common for CLI support (#4497)
* move interpolateMockVars function inside the main interpolate logic inside bruno-common. * improve comments for JSON escaping logic in interpolate function * update faker-functions to use CommonJS module syntax to satisfy jest and add regex validation tests --------- Co-authored-by: sanjai0py <sanjailucifer666@gmail.com> Co-authored-by: ramki-bruno <ramki@usebruno.com>
This commit is contained in:
parent
59e38fbdb0
commit
d376947a91
61
package-lock.json
generated
61
package-lock.json
generated
@ -26292,15 +26292,72 @@
|
||||
"name": "@usebruno/common",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^9.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^23.0.2",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-typescript": "^9.0.2",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"rollup": "3.29.5",
|
||||
"rollup-plugin-dts": "^5.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"packages/bruno-common/node_modules/@faker-js/faker": {
|
||||
"version": "9.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz",
|
||||
"integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fakerjs"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
}
|
||||
},
|
||||
"packages/bruno-common/node_modules/@rollup/plugin-typescript": {
|
||||
"version": "12.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz",
|
||||
"integrity": "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.1.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.14.0||^3.0.0||^4.0.0",
|
||||
"tslib": "*",
|
||||
"typescript": ">=3.7.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
},
|
||||
"tslib": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages/bruno-common/node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"packages/bruno-converters": {
|
||||
|
@ -32,7 +32,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
});
|
||||
});
|
||||
|
||||
const _interpolate = (str) => {
|
||||
const _interpolate = (str, { escapeJSONStrings } = {}) => {
|
||||
if (!str || !str.length || typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
@ -51,7 +51,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
}
|
||||
};
|
||||
|
||||
return interpolate(str, combinedVars);
|
||||
return interpolate(str, combinedVars, { escapeJSONStrings });
|
||||
};
|
||||
|
||||
request.url = _interpolate(request.url);
|
||||
@ -67,14 +67,14 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
if (typeof request.data === 'object') {
|
||||
try {
|
||||
let parsed = JSON.stringify(request.data);
|
||||
parsed = _interpolate(parsed);
|
||||
parsed = _interpolate(parsed, { escapeJSONStrings: true });
|
||||
request.data = JSON.parse(parsed);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
if (typeof request.data === 'string') {
|
||||
if (request?.data?.length) {
|
||||
request.data = _interpolate(request.data);
|
||||
request.data = _interpolate(request.data, { escapeJSONStrings: true });
|
||||
}
|
||||
}
|
||||
} else if (contentType === 'application/x-www-form-urlencoded') {
|
||||
|
@ -18,17 +18,20 @@
|
||||
"build": "rollup -c",
|
||||
"prepack": "npm run test && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^9.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^23.0.2",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-typescript": "^9.0.2",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"rollup":"3.29.5",
|
||||
"rollup-plugin-dts": "^5.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup":"3.29.5"
|
||||
"rollup": "3.29.5"
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,5 @@ module.exports = [
|
||||
typescript({ tsconfig: './tsconfig.json' }),
|
||||
terser()
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'dist/esm/index.d.ts',
|
||||
output: [{ file: 'dist/index.d.ts', format: 'esm' }],
|
||||
plugins: [dts.default()]
|
||||
}
|
||||
];
|
||||
|
@ -11,16 +11,46 @@
|
||||
* Output: Hello, my name is Bruno and I am 4 years old
|
||||
*/
|
||||
|
||||
import { Set } from 'typescript';
|
||||
import { flattenObject } from '../utils';
|
||||
import { mockDataFunctions } from '../utils/faker-functions';
|
||||
|
||||
const interpolate = (str: string, obj: Record<string, any>): string => {
|
||||
if (!str || typeof str !== 'string' || !obj || typeof obj !== 'object') {
|
||||
const interpolate = (
|
||||
str: string,
|
||||
obj: Record<string, any>,
|
||||
options: { escapeJSONStrings?: boolean } = { escapeJSONStrings: false }
|
||||
): string => {
|
||||
if (!str || typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
|
||||
const { escapeJSONStrings } = options;
|
||||
|
||||
const patternRegex = /\{\{\$(\w+)\}\}/g;
|
||||
str = str.replace(patternRegex, (match, keyword) => {
|
||||
let replacement = mockDataFunctions[keyword as keyof typeof mockDataFunctions]?.();
|
||||
|
||||
if (replacement === undefined) return match;
|
||||
replacement = String(replacement);
|
||||
|
||||
if (!escapeJSONStrings) return replacement;
|
||||
|
||||
// All the below chars inside of a JSON String field
|
||||
// will make it invalid JSON. So we will have to escape them with `\`.
|
||||
// This is not exhaustive but selective to what faker-js can output.
|
||||
if (!/[\\\n\r\t\"]/.test(replacement)) return replacement;
|
||||
return replacement
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\"/g, '\\"');
|
||||
});
|
||||
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return str;
|
||||
}
|
||||
|
||||
const flattenedObj = flattenObject(obj);
|
||||
|
||||
return replace(str, flattenedObj);
|
||||
};
|
||||
|
||||
|
141
packages/bruno-common/src/utils/faker-functions.spec.ts
Normal file
141
packages/bruno-common/src/utils/faker-functions.spec.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { mockDataFunctions } from "./faker-functions";
|
||||
|
||||
describe("mockDataFunctions Regex Validation", () => {
|
||||
test("all values should match their expected patterns", () => {
|
||||
const patterns: Record<string, RegExp> = {
|
||||
guid: /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/,
|
||||
timestamp: /^\d{13,}$/,
|
||||
isoTimestamp: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
|
||||
randomUUID: /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/,
|
||||
randomAlphaNumeric: /^[\w]$/,
|
||||
randomBoolean: /^(true|false)$/,
|
||||
randomInt: /^\d+$/,
|
||||
randomColor: /^[\w\s]+$/,
|
||||
randomHexColor: /^#[\da-f]{6}$/,
|
||||
randomAbbreviation: /^\w{2,6}$/,
|
||||
randomIP: /^([\da-f]{1,4}:){7}[\da-f]{1,4}$|^(\d{1,3}\.){3}\d{1,3}$/,
|
||||
randomIPV4: /^(\d{1,3}\.){3}\d{1,3}$/,
|
||||
randomIPV6: /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/,
|
||||
randomMACAddress: /^([\da-f]{2}:){5}[\da-f]{2}$/,
|
||||
randomPassword: /^[\w\d]{8,}$/,
|
||||
randomLocale: /^[A-Z]{2}$/,
|
||||
randomUserAgent: /^[\w\/\.\s\(\)\+\-;:_,]+$/,
|
||||
randomProtocol: /^(http|https|ftp)s?$/,
|
||||
randomSemver: /^\d+\.\d+\.\d+$/,
|
||||
randomFirstName: /^[\s\S]+$/,
|
||||
randomLastName: /^[\s\S]+$/,
|
||||
randomFullName: /^[\s\S]+$/,
|
||||
randomNamePrefix: /^[\s\S]+$/,
|
||||
randomNameSuffix: /^[\s\S]+$/,
|
||||
randomJobArea: /^[\s\S]+$/,
|
||||
randomJobDescriptor: /^[\s\S]+$/,
|
||||
randomJobTitle: /^[\s\S]+$/,
|
||||
randomJobType: /^[\s\S]+$/,
|
||||
randomPhoneNumber: /^[\s\S]+$/,
|
||||
randomPhoneNumberExt: /^[\s\S]+$/,
|
||||
randomCity: /^[\s\S]+$/,
|
||||
randomStreetName: /^[\s\S]+$/,
|
||||
randomStreetAddress: /^[\s\S]+$/,
|
||||
randomCountry: /^[\s\S]+$/,
|
||||
randomCountryCode: /^[\s\S]+$/,
|
||||
randomLatitude: /^[\s\S]+$/,
|
||||
randomLongitude: /^[\s\S]+$/,
|
||||
randomAvatarImage: /^[\s\S]+$/,
|
||||
randomImageUrl: /^[\s\S]+$/,
|
||||
randomAbstractImage: /^[\s\S]+$/,
|
||||
randomAnimalsImage: /^[\s\S]+$/,
|
||||
randomBusinessImage: /^[\s\S]+$/,
|
||||
randomCatsImage: /^[\s\S]+$/,
|
||||
randomCityImage: /^[\s\S]+$/,
|
||||
randomFoodImage: /^[\s\S]+$/,
|
||||
randomNightlifeImage: /^[\s\S]+$/,
|
||||
randomFashionImage: /^[\s\S]+$/,
|
||||
randomPeopleImage: /^[\s\S]+$/,
|
||||
randomNatureImage: /^[\s\S]+$/,
|
||||
randomSportsImage: /^[\s\S]+$/,
|
||||
randomTransportImage: /^[\s\S]+$/,
|
||||
randomImageDataUri: /^[\s\S]+$/,
|
||||
randomBankAccount: /^[\s\S]+$/,
|
||||
randomBankAccountName: /^[\s\S]+$/,
|
||||
randomCreditCardMask: /^[\s\S]+$/,
|
||||
randomBankAccountBic: /^[\s\S]+$/,
|
||||
randomBankAccountIban: /^[\s\S]+$/,
|
||||
randomTransactionType: /^[\s\S]+$/,
|
||||
randomCurrencyCode: /^[\s\S]+$/,
|
||||
randomCurrencyName: /^[\s\S]+$/,
|
||||
randomCurrencySymbol: /^[\s\S]+$/,
|
||||
randomBitcoin: /^[\s\S]+$/,
|
||||
randomCompanyName: /^[\s\S]+$/,
|
||||
randomCompanySuffix: /^[\s\S]+$/,
|
||||
randomBs: /^[\s\S]+$/,
|
||||
randomBsAdjective: /^[\s\S]+$/,
|
||||
randomBsBuzz: /^[\s\S]+$/,
|
||||
randomBsNoun: /^[\s\S]+$/,
|
||||
randomCatchPhrase: /^[\s\S]+$/,
|
||||
randomCatchPhraseAdjective: /^[\s\S]+$/,
|
||||
randomCatchPhraseDescriptor: /^[\s\S]+$/,
|
||||
randomCatchPhraseNoun: /^[\s\S]+$/,
|
||||
randomDatabaseColumn: /^[\s\S]+$/,
|
||||
randomDatabaseType: /^[\s\S]+$/,
|
||||
randomDatabaseCollation: /^[\s\S]+$/,
|
||||
randomDatabaseEngine: /^[\s\S]+$/,
|
||||
randomDateFuture: /^[\s\S]+$/,
|
||||
randomDatePast: /^[\s\S]+$/,
|
||||
randomDateRecent: /^[\s\S]+$/,
|
||||
randomWeekday: /^[\s\S]+$/,
|
||||
randomMonth: /^[\s\S]+$/,
|
||||
randomDomainName: /^[\s\S]+$/,
|
||||
randomDomainSuffix: /^[\s\S]+$/,
|
||||
randomDomainWord: /^[\s\S]+$/,
|
||||
randomEmail: /^[\w_.\-]+@[\w]+\.[a-z]+$/,
|
||||
randomExampleEmail: /^[\w\.-]+@example\.[a-z]+$/,
|
||||
randomUserName: /^[\w.\-]+$/,
|
||||
randomUrl: /^https:\/\/[\w\-]+\.[a-z]+\/?$/,
|
||||
randomFileName: /^[\w\_]+\.[\w\d]+$/,
|
||||
randomFileType: /^[\w]+$/,
|
||||
randomFileExt: /^[\w\d]+$/,
|
||||
randomCommonFileName: /^[\w\_]+\.[\w\d]+$/,
|
||||
randomCommonFileType: /^[\w]+$/,
|
||||
randomCommonFileExt: /^[\w\d]+$/,
|
||||
randomFilePath: /^[\s\S]+$/,
|
||||
randomDirectoryPath: /^\/[-\w\+\/]+$/,
|
||||
randomMimeType: /^[\w]+\/[\w\d\-\+\.]+$/,
|
||||
randomPrice: /^\d+\.\d{2}$/,
|
||||
randomProduct: /^[\s\S]+$/,
|
||||
randomProductAdjective: /^[\s\S]+$/,
|
||||
randomProductMaterial: /^[\s\S]+$/,
|
||||
randomProductName: /^[\s\S]+$/,
|
||||
randomDepartment: /^[\s\S]+$/,
|
||||
randomNoun: /^[\s\S]+$/,
|
||||
randomVerb: /^[\s\S]+$/,
|
||||
randomIngverb: /^[\s\S]+$/,
|
||||
randomAdjective: /^[\s\S]+$/,
|
||||
randomWord: /^[\s\S]+$/,
|
||||
randomWords: /^[\s\S]+$/,
|
||||
randomPhrase: /^[\s\S]+$/,
|
||||
randomLoremWord: /^[\s\S]+$/,
|
||||
randomLoremWords: /^[\s\S]+$/,
|
||||
randomLoremSentence: /^[\s\S]+$/,
|
||||
randomLoremSentences: /^[\s\S]+$/,
|
||||
randomLoremParagraph: /^[\s\S]+$/,
|
||||
randomLoremParagraphs: /^[\s\S]+$/,
|
||||
randomLoremText: /^[\s\S]+$/,
|
||||
randomLoremSlug: /^[\s\S]+$/,
|
||||
randomLoremLines: /^[\s\S]+$/,
|
||||
};
|
||||
|
||||
const errors: string[] = [];
|
||||
|
||||
Object.entries(mockDataFunctions).forEach(([key, func]) => {
|
||||
const pattern = patterns[key];
|
||||
const value = String(func());
|
||||
if (!value.match(pattern)) {
|
||||
errors.push(`Pattern mismatch for ${key}: expected ${pattern}, received ${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.join("\n"));
|
||||
}
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
const { faker } = require('@faker-js/faker');
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
const mockDataFunctions = {
|
||||
export const mockDataFunctions = {
|
||||
guid: () => faker.string.uuid(),
|
||||
timestamp: () => faker.date.anytime().getTime().toString(),
|
||||
isoTimestamp: () => faker.date.anytime().toISOString(),
|
||||
@ -9,7 +9,7 @@ const mockDataFunctions = {
|
||||
randomBoolean: () => faker.datatype.boolean(),
|
||||
randomInt: () => faker.number.int(),
|
||||
randomColor: () => faker.color.human(),
|
||||
randomHexColor: () => faker.internet.color(),
|
||||
randomHexColor: () => faker.color.rgb(),
|
||||
randomAbbreviation: () => faker.hacker.abbreviation(),
|
||||
randomIP: () => faker.internet.ip(),
|
||||
randomIPV4: () => faker.internet.ipv4(),
|
||||
@ -121,7 +121,3 @@ const mockDataFunctions = {
|
||||
randomLoremSlug: () => faker.lorem.slug(),
|
||||
randomLoremLines: () => faker.lorem.lines()
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
mockDataFunctions
|
||||
};
|
@ -6,14 +6,14 @@
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react",
|
||||
"module": "ESNext",
|
||||
"declaration": true,
|
||||
"declarationDir": "types",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "node",
|
||||
"emitDeclarationOnly": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
"checkJs": false
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js"],
|
||||
"exclude": ["dist", "node_modules", "tests"]
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
const { interpolate } = require('@usebruno/common');
|
||||
const { each, forOwn, cloneDeep, find } = require('lodash');
|
||||
const FormData = require('form-data');
|
||||
const { mockDataFunctions } = require('./faker-functions');
|
||||
|
||||
const getContentType = (headers = {}) => {
|
||||
let contentType = '';
|
||||
@ -14,28 +13,6 @@ const getContentType = (headers = {}) => {
|
||||
return contentType;
|
||||
};
|
||||
|
||||
const interpolateMockVars = (str, { escapeJSONStrings }) => {
|
||||
const patternRegex = /\{\{\$(\w+)\}\}/g;
|
||||
return str.replace(patternRegex, (match, keyword) => {
|
||||
let replacement = mockDataFunctions[keyword]?.();
|
||||
|
||||
if (replacement === undefined) return match;
|
||||
replacement = String(replacement);
|
||||
|
||||
if (!escapeJSONStrings) return replacement;
|
||||
// All the below chars inside of a JSON String field
|
||||
// will make it invalid JSON. So we will have to escape them with `\`.
|
||||
// This is not exhaustive but selective to what faker-js can output.
|
||||
if (!/[\\\n\r\t\"]/.test(replacement)) return replacement;
|
||||
return replacement
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\"/g, '\\"');
|
||||
});
|
||||
};
|
||||
|
||||
const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => {
|
||||
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
|
||||
const oauth2CredentialVariables = request?.oauth2CredentialVariables || {};
|
||||
@ -78,7 +55,9 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
}
|
||||
};
|
||||
|
||||
return interpolateMockVars(interpolate(str, combinedVars), { escapeJSONStrings });
|
||||
return interpolate(str, combinedVars, {
|
||||
escapeJSONStrings
|
||||
});
|
||||
};
|
||||
|
||||
request.url = _interpolate(request.url);
|
||||
@ -97,12 +76,16 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
if (contentType.includes('json') && !Buffer.isBuffer(request.data)) {
|
||||
if (typeof request.data === 'string') {
|
||||
if (request.data.length) {
|
||||
request.data = _interpolate(request.data, { escapeJSONStrings: true });
|
||||
request.data = _interpolate(request.data, {
|
||||
escapeJSONStrings: true
|
||||
});
|
||||
}
|
||||
} else if (typeof request.data === 'object') {
|
||||
try {
|
||||
const jsonDoc = JSON.stringify(request.data);
|
||||
const parsed = _interpolate(jsonDoc, { escapeJSONStrings: true });
|
||||
const parsed = _interpolate(jsonDoc, {
|
||||
escapeJSONStrings: true
|
||||
});
|
||||
request.data = JSON.parse(parsed);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user