-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
168 additions
and
184 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import lodash from 'lodash'; | ||
|
||
export enum Tag { | ||
PII = 'PII', | ||
SECRET = 'SECRET' | ||
} | ||
|
||
type PlainObject = Record<string, unknown>; | ||
|
||
export function tagString(value: string, tag: Tag): string { | ||
return `[${tag}]${value}[/${tag}]`; | ||
} | ||
|
||
export function tagObject<T extends PlainObject | Error>(object: T, tagSettings: Array<[string, Tag]>): T { | ||
if (isError(object)) { | ||
return tagError(object, tagSettings); | ||
} else if (isPlainObject(object)) { | ||
return tagPlainObject(object, tagSettings); | ||
} | ||
|
||
throw new Error('Input must be plain object or a class inheriting from Error'); | ||
} | ||
|
||
function tagPlainObject<T extends PlainObject>(plainObject: T, tagSettings: Array<[string, Tag]>): T { | ||
const clonedObj = clone(plainObject); | ||
const pathToTagMap = constructPathToTagMap(tagSettings); | ||
|
||
const allPaths = collectPaths(plainObject); | ||
for (const path of allPaths) { | ||
const tag = pathToTagMap.get(path); | ||
if (!tag) { | ||
continue; | ||
} | ||
|
||
const rawValue = lodash.get(clonedObj, path); | ||
if (typeof rawValue === 'string') { | ||
lodash.set(clonedObj, path, tagString(rawValue, tag)); | ||
} | ||
} | ||
|
||
return clonedObj; | ||
} | ||
|
||
function tagError<T extends Error>(err: T, tagSettings: Array<[string, Tag]>): T { | ||
const clonedErr = new Error(err.message); | ||
Object.setPrototypeOf(clonedErr, Object.getPrototypeOf(err)); | ||
|
||
const dataToAssign = {}; | ||
for (const prop of Object.getOwnPropertyNames(err)) { | ||
dataToAssign[prop] = clone(err[prop]); | ||
} | ||
|
||
Object.assign(clonedErr, tagPlainObject(dataToAssign, tagSettings)); | ||
|
||
return clonedErr as T; | ||
} | ||
|
||
function collectPaths(input: any, currentPath?: string) { | ||
const paths: Array<string> = []; | ||
|
||
if (lodash.isPlainObject(input)) { | ||
for (const key in input) { | ||
const fullPath = buildPath(key, currentPath); | ||
const value = input[key]; | ||
|
||
paths.push(fullPath, ...collectPaths(value).map((nestedPath) => buildPath(nestedPath, fullPath))); | ||
} | ||
} | ||
|
||
return paths; | ||
} | ||
|
||
function buildPath(propPath: string, basePath?: string) { | ||
return basePath === undefined ? String(propPath) : `${basePath}.${propPath}`; | ||
} | ||
|
||
function isPlainObject(value: unknown): value is PlainObject { | ||
return lodash.isPlainObject(value); | ||
} | ||
|
||
function isError(value: unknown): value is Error { | ||
return value instanceof Error; | ||
} | ||
|
||
function constructPathToTagMap(tagSettings: Array<[string, Tag]>) { | ||
const pathToTagMap = new Map(); | ||
for (const [path, tag] of tagSettings) { | ||
pathToTagMap.set(path, tag); | ||
} | ||
|
||
return pathToTagMap; | ||
} | ||
|
||
function clone<T extends any>(value: T): T { | ||
return lodash.cloneDeep(value); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,3 @@ | ||
import { LoggerFactory, Adapters, LogLevels, ConsoleAdapterSettings, SentryAdapterSettings } from './LoggerFactory'; | ||
import { Logger } from './Logger'; | ||
import { Obfuscator, Tag } from './Obfuscator'; | ||
|
||
export { | ||
LoggerFactory, | ||
Adapters, | ||
LogLevels, | ||
Logger, | ||
ConsoleAdapterSettings, | ||
SentryAdapterSettings, | ||
Obfuscator, | ||
Tag as ObfuscatorTag, | ||
}; | ||
export * from './LoggerFactory'; | ||
export * from './Logger'; | ||
export * from './data-tagging'; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { | ||
tagString, | ||
tagObject, | ||
Tag, | ||
} from '../../index'; | ||
|
||
describe('Data Tagging', () => { | ||
describe('tagString', () => { | ||
it('should wrap the provided tag around the string that needs to be tagd', () => { | ||
expect(tagString('string', Tag.PII)).toEqual('[PII]string[/PII]'); | ||
}); | ||
}); | ||
|
||
describe('tagObject', () => { | ||
it('should wrap the provided tag around root-level elements in object', () => { | ||
const originalObject = { name: 'Pencho' }; | ||
const taggedObject = { name: '[PII]Pencho[/PII]' }; | ||
expect(tagObject(originalObject, [['name', Tag.PII]])).toEqual(taggedObject); | ||
}); | ||
|
||
it('should wrap the provided tag around nested elements in object', () => { | ||
const originalObject = { id: 1, data: { name: 'Gosho', email: 'email@example.com' } }; | ||
const taggedObject = { id: 1, data: { name: '[PII]Gosho[/PII]', email: '[PII]email@example.com[/PII]' } }; | ||
expect(tagObject(originalObject, [['data.name', Tag.PII], ['data.email', Tag.PII]])).toEqual(taggedObject); | ||
}); | ||
|
||
it('should NOT wrap the provided tag around elements that are not specified for taging in object', () => { | ||
const originalObject = { favouriteColor: 'red', nested: { field: 'value' } }; | ||
expect(tagObject(originalObject, [['name', Tag.PII]])).toEqual(originalObject); | ||
}); | ||
|
||
it('should return a copy of the error and not modify the original', () => { | ||
const originalError = new Error(); | ||
const taggedError = tagObject(originalError, [['bar', Tag.PII]]); | ||
expect(taggedError).not.toBe(originalError); | ||
}); | ||
|
||
it('should preserve the prototype, name, message and stack of the error', () => { | ||
class CustomError extends Error {} | ||
const originalError = new CustomError(); | ||
const taggedError = tagObject(originalError, [['bar', Tag.PII]]); | ||
|
||
expect(taggedError).toBeInstanceOf(CustomError); | ||
expect(taggedError.name).toEqual(originalError.name); | ||
expect(taggedError.message).toEqual(originalError.message); | ||
expect(taggedError.stack).toEqual(originalError.stack); | ||
}); | ||
|
||
it('should tag error specific props', () => { | ||
class CustomError extends Error { | ||
bar = 'foo' | ||
foo = { | ||
test: 'test', | ||
} | ||
} | ||
const originalError = new CustomError(); | ||
const taggedError = tagObject(originalError, [ | ||
['bar', Tag.PII], | ||
['foo.test', Tag.PII], | ||
]); | ||
|
||
expect(taggedError.bar).toEqual('[PII]foo[/PII]'); | ||
expect(taggedError.foo.test).toEqual('[PII]test[/PII]'); | ||
}); | ||
}); | ||
}); |