feat(common): add patternHasher utility for hashing and restoring string from special characters (#6032)

* feat: add patternHasher utility for variable hashing

This utility function hashes input strings containing variables and allows for restoration of the original string. It includes support for custom matchers and handles cases where no variables are matched.

* Update packages/bruno-common/src/utils/template-hasher.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Sid <siddharth@usebruno.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Siddharth Gelera (reaper)
2025-11-07 19:09:55 +05:30
committed by GitHub
parent 1719cee440
commit 68b2625259
2 changed files with 81 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import { describe, it, expect } from '@jest/globals';
import { patternHasher } from './template-hasher';
describe('patternHasher', () => {
it('hashes and restore are mathematically reproducible', () => {
const originalUrl = '{{host}}.example.com';
const { hashed, restore } = patternHasher(originalUrl);
expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash--163450413.example.com"`);
expect(restore(hashed)).toEqual(originalUrl);
});
it('hashes more than once', () => {
const originalUrl = '{{host}}.example.{{new}}';
const { hashed, restore } = patternHasher(originalUrl);
expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash--163450413.example.bruno-var-hash-652560383"`);
expect(restore(hashed)).toEqual(originalUrl);
});
it('allows custom matchers', () => {
const originalUrl = '$name.example.com';
const { hashed, restore } = patternHasher(originalUrl, /\$(\w+)/);
expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash-180907786.example.com"`);
expect(restore(hashed)).toEqual(originalUrl);
});
it('ignore unless matched', () => {
const originalUrl = '$name.example.com';
const { hashed, restore } = patternHasher(originalUrl);
expect(hashed).toMatchInlineSnapshot(`"$name.example.com"`);
expect(restore(hashed)).toEqual(originalUrl);
});
});

View File

@@ -0,0 +1,49 @@
const VARIABLE_REGEX = /\{\{([^}]+)\}\}/g;
/**
* Was implemented specifically for request.url where the url might have variables
* that might need to be sanitised before being passed to a URL validator that doesn't
* allow special characters that bruno uses as variables (`{{var_name}}`)
*
* The function replaces the input string with a unique hash that can be restored
* later by the helper returned by this function
*/
export function patternHasher(input: string, pattern: string | RegExp = VARIABLE_REGEX) {
const usableRegex = new RegExp(pattern, 'g');
function hash(toHash: string) {
let hash = 5381;
let c;
for (let i = 0; i < toHash.length; i++) {
c = toHash.charCodeAt(i);
hash = ((hash << 5) + hash + c) | 0;
}
return '' + hash;
}
const prefix = `bruno-var-hash-`;
const hashToOriginal: Record<string, string> = {};
let result = input;
let hashed = false;
if (usableRegex.test(input)) {
hashed = true;
result = input.replace(usableRegex, function (matchedVar) {
const hashedValue = `${prefix}${hash(matchedVar)}`;
hashToOriginal[hashedValue] = matchedVar;
return hashedValue;
});
}
return {
hashed: result,
restore(current: string) {
if (!hashed) {
return current;
}
let clone = current;
for (const hash in hashToOriginal) {
const value = hashToOriginal[hash];
clone = clone.replace(hash, value);
}
return clone;
}
};
}