Skip to content

Commit

Permalink
Improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
satazor committed Dec 3, 2024
1 parent 84c9542 commit f0fd7c9
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 13 deletions.
55 changes: 42 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ function validateSerializers(serializers) {
* During `parseAndSerialize` execution, we perform additional copies to avoid having a serializer updating the
* original object by reference. These copies are only done in the values passed to serializers to avoid two full
* copies of the original values. For this, we used `cloneDeepWith` with a custom clone only for errors. When an
* error is found, we compute a list with all properties (properties from the class itself and from extended cla-
* sses). Then we use these properties to get the original values and copying them into a new object.
* error is found, we compute a list with all properties (properties from the class itself and from extended classes).
* Then we use these properties to get the original values and copying them into a new object.
*/

function parseAndSerialize(values, serializers) {
Expand Down Expand Up @@ -106,6 +106,30 @@ function parseAndSerialize(values, serializers) {
return target;
}

/**
* Creates a path tester function used to check if a path matches a list of patterns.
*
* This function was built with optimization in mind.
* It separates the patterns into two lists, regular patterns and wildcard pattern, and applies the following strategy:
*
* 1. It tries to find an exact match in the regular patterns first, which is faster.
* 2. If no match is found, it uses a regular expression to test the wildcard patterns.
*/

const createPathTester = patterns => {
const regularPatterns = patterns.filter(pattern => !pattern.includes('*')).map(pattern => pattern.toLowerCase());
const wildcardPatterns = patterns.filter(pattern => pattern.includes('*')).map(pattern => pattern.toLowerCase());

const regularPatternsSet = new Set(regularPatterns);
const wildcardRegExp = new RegExp(`^(${wildcardPatterns.join('|').replace(/\./g, '\\.').replace(/\*/g, '.*')})$`);

return path => {
const lowercasedPath = path.toLowerCase();

return regularPatternsSet.has(lowercasedPath) || wildcardRegExp.test(lowercasedPath);
};
};

/**
* Module exports `anonymizer` function.
*/
Expand All @@ -114,13 +138,11 @@ module.exports.anonymizer = (
{ blacklist = [], whitelist = [] } = {},
{ replacement = () => DEFAULT_REPLACEMENT, serializers = [], trim = false } = {}
) => {
const whitelistTerms = whitelist.join('|');
const whitelistPaths = new RegExp(`^(${whitelistTerms.replace(/\./g, '\\.').replace(/\*/g, '.*')})$`, 'i');
const blacklistTerms = blacklist.join('|');
const blacklistPaths = new RegExp(`^(${blacklistTerms.replace(/\./g, '\\.').replace(/\*/g, '.*')})$`, 'i');

validateSerializers(serializers);

const isWhitelisted = createPathTester(whitelist);
const isBlacklisted = createPathTester(blacklist);

return values => {
if (!(values instanceof Object)) {
return values;
Expand All @@ -130,13 +152,17 @@ module.exports.anonymizer = (
const obj = parseAndSerialize(values, serializers);

traverse(obj).forEach(function () {
if (this.isRoot) {
return;
}

const path = this.path.join('.');
const isBuffer = Buffer.isBuffer(get(values, path));
const isBuffer = this.node?.type === 'Buffer' && Array.isArray(this.node.data);

if (trim) {
this.after(function (node) {
if (!this.isLeaf && Object.values(node).every(value => value === undefined)) {
return this.isRoot ? this.update(undefined, true) : this.delete();
return this.delete();
}
});
}
Expand All @@ -145,19 +171,22 @@ module.exports.anonymizer = (
return;
}

if (isBuffer && !blacklistPaths.test(path) && whitelistPaths.test(path)) {
const whitelisted = isWhitelisted(path);
const blacklisted = isBlacklisted(path);

if (isBuffer && !blacklisted && whitelisted) {
return this.update(Buffer.from(this.node), true);
}

const replacedValue = replacement(this.key, this.node, this.path);
if (blacklisted || !whitelisted) {
const replacedValue = replacement(this.key, this.node, this.path);

if (blacklistPaths.test(path) || !whitelistPaths.test(path)) {
if (trim && replacedValue === DEFAULT_REPLACEMENT) {
const path = this.path.map(value => (isNaN(value) ? value : '[]'));

blacklistedKeys.add(path.join('.'));

return this.isRoot ? this.update(undefined, true) : this.delete();
return this.delete();
}

this.update(replacedValue);
Expand Down
6 changes: 6 additions & 0 deletions test/src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,12 @@ describe('Anonymizer', () => {
});
});

it('should not add `__redacted__` when anonymizing an empty object', () => {
const anonymize = anonymizer({}, { trim: true });

expect(anonymize({})).toEqual({});
});

it('should not trim obfuscated values that have different obfuscation techniques', () => {
const replacement = (key, value) => {
if (key === 'biz') {
Expand Down

0 comments on commit f0fd7c9

Please sign in to comment.