Skip to content
This repository has been archived by the owner on Apr 2, 2023. It is now read-only.

Commit

Permalink
feat: distinguish between Records and Objects when recursing. Expose …
Browse files Browse the repository at this point in the history
…visitPII
  • Loading branch information
tdreyno committed Mar 10, 2021
1 parent cbc7b3d commit 42f4896
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 9 deletions.
18 changes: 18 additions & 0 deletions src/__tests__/unwrapObject.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,22 @@ describe("unwrapObject", () => {
two: 2,
})
})

it("should ignore Map", () => {
const map = new Map()

expect(unwrapObject({ test: map, two: PII(2) })).toEqual({
test: map,
two: 2,
})
})

it("should ignore weird Numbers", () => {
const num = new Number(1)

expect(unwrapObject({ test: num, two: PII(2) })).toEqual({
test: num,
two: 2,
})
})
})
30 changes: 21 additions & 9 deletions src/pii.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface PII<T> {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isPIIType = <T>(val: any): val is PII<T> =>
isPojo(val) && val.__brand === "PII"
isRecord(val) && val.__brand === "PII"

export const PII = <T>(val: T, msg = "REDACTED"): PII<T> =>
isPIIType<T>(val)
Expand Down Expand Up @@ -83,46 +83,58 @@ export const zip4With = <A, B, C, D, E>(
const proto = Object.prototype
const gpo = Object.getPrototypeOf

// POJO: Plain Old Javascript Object
const isPojo = (obj: unknown): obj is Record<string, unknown> =>
const isRecord = (obj: unknown): obj is Record<string, unknown> =>
obj === null || typeof obj !== "object" ? false : gpo(obj) === proto

// Function, regex, object, Number, String, etc
const isObject = (value: unknown): boolean => {
const type = typeof value
return value != null && (type == "object" || type == "function")
}

// Does not handle Set or Map for now.
const visitPII = <A, T>(
export const visitPII = <A, T>(
input: A,
visitors: {
object: (value: Record<string, unknown>) => T
record: (value: Record<string, unknown>) => T
object: (value: unknown) => T
array: (value: Array<unknown>) => T
primitive: (value: A) => T
},
): T => {
if (isPojo(input)) {
return visitors.object(input)
if (isRecord(input)) {
return visitors.record(input)
}

if (Array.isArray(input)) {
return visitors.array(input)
}

if (isObject(input)) {
return visitors.object(input)
}

return visitors.primitive(input)
}

export const containsPII = (input: unknown): boolean =>
isPIIType(input)
? true
: visitPII(input, {
object: o => Object.values(o).some(containsPII),
record: o => Object.values(o).some(containsPII),
array: a => a.some(containsPII),
primitive: p => isPIIType(p),
object: p => isPIIType(p),
})

export const unwrapObject = (input: unknown): unknown =>
visitPII(isPIIType(input) ? unwrap(input) : input, {
object: o =>
record: o =>
Object.keys(o).reduce((sum, key) => {
sum[key] = unwrapObject(o[key])
return sum
}, {} as Record<string, unknown>),
array: a => a.map(unwrapObject),
primitive: p => p,
object: p => p,
})

0 comments on commit 42f4896

Please sign in to comment.