@snipsonian/core
Version:
Core/base reusable javascript code snippets
159 lines (138 loc) • 4.49 kB
text/typescript
import isSet from '../../is/isSet';
import isObjectPure from '../../is/isObjectPure';
import { TAnyObject } from '../../typings/object';
import cloneObjectDataProps from '../cloneObjectDataProps';
/**
* This method will
* - clone the input object
* - then search for (nested) properties that match the 'propPathsToObscure'
* - and obscure their values with the 'obscureValue'.
*
* @param obj The object to obscure some sensitive data from
* @param propPathsToObscure Array of property paths to obscure, where a . is used as path delimiter.
* E.g. ['headers.Authorization'] to obscure the Authorization prop within the headers prop.
* @param obscureValue Optional (default "***obscured***"). The string that will be used to obscure a property value.
* If false, then the properties will be removed from the resulting object instead of obscured.
*/
const DEFAULT_OBSCURE_VALUE = '***obscured***';
export function obscureObjectProps({
obj,
propPathsToObscure,
obscureValue = DEFAULT_OBSCURE_VALUE,
}: {
// eslint-disable-next-line @typescript-eslint/ban-types
obj: object;
propPathsToObscure: string[];
obscureValue?: false | string;
}) {
if (!isObjectPure(obj)) {
return obj;
}
return propPathsToObscure.reduce(
(accumulator, propPathToObscure) => {
if (isObjectPropSetByPath({
obj: accumulator as TAnyObject,
propPath: propPathToObscure,
})) {
if (obscureValue === false) {
removeObjectByPath({
obj: accumulator as TAnyObject,
propPath: propPathToObscure,
});
} else {
setObjectPropByPath({
obj: accumulator as TAnyObject,
propPath: propPathToObscure,
newValue: obscureValue,
});
}
}
return accumulator;
},
cloneObjectDataProps(obj),
);
}
function removeObjectByPath({
obj,
propPath,
}: {
obj: TAnyObject;
propPath: string; // . separated
}) {
const pathParts = propPath.split('.');
if (pathParts.length === 1) {
// eslint-disable-next-line no-param-reassign
delete obj[pathParts[0]];
} else if (pathParts.length > 1) {
const lastPathPart = pathParts.pop(); /* 'pop' removes the last element of an array */
const parentObj = getObjectPropByPathParts({
obj,
propPathParts: pathParts, /* does not contain the last path part anymore because of the pop */
});
if (isObjectPure(parentObj)) {
delete (parentObj as TAnyObject)[lastPathPart];
}
}
return obj;
}
function setObjectPropByPath({
obj,
propPath,
newValue,
}: {
obj: TAnyObject;
propPath: string; // . separated
newValue: unknown;
}) {
const pathParts = propPath.split('.');
if (pathParts.length === 1) {
// eslint-disable-next-line no-param-reassign
obj[pathParts[0]] = newValue;
} else if (pathParts.length > 1) {
const lastPathPart = pathParts.pop(); /* 'pop' removes the last element of an array */
const parentObj = getObjectPropByPathParts({
obj,
propPathParts: pathParts, /* does not contain the last path part anymore because of the pop */
});
if (isObjectPure(parentObj)) {
(parentObj as TAnyObject)[lastPathPart] = newValue;
}
}
return obj;
}
function getObjectPropByPathParts({
obj,
propPathParts,
}: {
obj: TAnyObject;
propPathParts: string[];
}): unknown {
if (!isObjectPure(obj)) {
return null;
}
if (!propPathParts || propPathParts.length === 0) {
return null;
}
const [firstPathPart, ...remainingPathParts] = propPathParts;
const first = obj[firstPathPart];
return propPathParts.length === 1
? first
: getObjectPropByPathParts({
obj: first as TAnyObject,
propPathParts: remainingPathParts,
});
}
function isObjectPropSetByPath({
obj,
propPath,
}: {
obj: TAnyObject;
propPath: string; // . separated
}): boolean {
return isSet(
getObjectPropByPathParts({
obj,
propPathParts: propPath.split('.'),
}),
);
}