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

Commit d8437a4

Browse files
committed
feat: add optional maxDepth option to recursive functions: containsPII, detect, redact, unwrapObject
1 parent 9dd6f37 commit d8437a4

32 files changed

+329
-221
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ module.exports = {
2424
"@typescript-eslint/no-unsafe-return": 0,
2525
"@typescript-eslint/no-unsafe-call": 0,
2626
"@typescript-eslint/no-unsafe-member-access": 0,
27+
"@typescript-eslint/no-explicit-any": 0,
2728
},
2829
}

src/PIITry.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import PII from "./pii"
2+
3+
export default async function PIITry<S>(fn: () => S | Promise<S>): Promise<S> {
4+
try {
5+
return fn()
6+
} catch (e) {
7+
throw PII(e, "REDACTED_ERROR")
8+
}
9+
}

src/__tests__/constructor.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PII, unwrap } from "../index"
1+
import PII from "../pii"
2+
import unwrap from "../unwrap"
23

34
describe("PII", () => {
45
it("should not leak into toString", () => {

src/__tests__/containsPII.spec.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PII, containsPII } from "../index"
1+
import PII from "../pii"
2+
import containsPII from "../containsPII"
23

34
describe("containsPII", () => {
45
it("should not find PII", () => {
@@ -20,4 +21,8 @@ describe("containsPII", () => {
2021
expect(containsPII([PII("test")])).toBeTruthy()
2122
expect(containsPII({ test: PII(1) })).toBeTruthy()
2223
})
24+
25+
it("should return true after max depth", () => {
26+
expect(containsPII({ test: [{ hello: "world" }] }, 2)).toBeTruthy()
27+
})
2328
})

src/__tests__/detect.spec.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { detect, isPII } from "../index"
1+
import { isPII } from "../pii"
2+
import detect from "../detect"
3+
import unwrap from "../unwrap"
34

45
const detector = (data: unknown) => Array.isArray(data)
56

@@ -25,4 +26,10 @@ describe("detect", () => {
2526
const detectedArrays = detect(detector, { test: set })
2627
expect(isPII(Array.from((detectedArrays as any).test)[0])).toBeTruthy()
2728
})
29+
30+
it("should return PII after max depth", () => {
31+
const result: any = detect(() => false, { test: [{ hello: "world" }] }, 2)
32+
33+
expect(unwrap(result.test[0])).toEqual({ hello: "world" })
34+
})
2835
})

src/__tests__/fold.spec.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { PII, fold, unwrap } from "../index"
1+
import PII from "../pii"
2+
import fold from "../fold"
3+
import unwrap from "../unwrap"
24

35
describe("fold", () => {
46
it("should fold multiple PII", () => {

src/__tests__/map.spec.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { PII, map, unwrap } from "../index"
1+
import PII from "../pii"
2+
import map from "../map"
3+
import unwrap from "../unwrap"
24

35
describe("map", () => {
46
it("should map inside PII", () => {

src/__tests__/redact.spec.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PII, redact } from "../index"
1+
import PII from "../pii"
2+
import redact from "../redact"
23

34
const REDACTED = "REDACTED"
45
const redactor = () => REDACTED
@@ -62,4 +63,10 @@ describe("redact", () => {
6263
two: REDACTED,
6364
})
6465
})
66+
67+
it("should return PII after max depth", () => {
68+
expect(redact(redactor, { test: [{ hello: "world" }] }, 2)).toEqual({
69+
test: [REDACTED],
70+
})
71+
})
6572
})

src/__tests__/tap.spec.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { PII, tap, unwrap } from "../index"
1+
import PII from "../pii"
2+
import tap from "../tap"
3+
import unwrap from "../unwrap"
24

35
describe("tap", () => {
46
it("should tap inside PII", () => {

src/__tests__/test.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PII, test } from "../index"
1+
import PII from "../pii"
2+
import test from "../test"
23

34
describe("test", () => {
45
it("should test predicate against PII", () => {

src/__tests__/try.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PIITry, unwrap } from "../index"
1+
import PIITry from "../PIITry"
2+
import unwrap from "../unwrap"
23

34
describe("PIITry", () => {
45
it("should wrap results in PII", async () => {

src/__tests__/unwrap.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PII, unwrap } from "../index"
1+
import PII from "../pii"
2+
import unwrap from "../unwrap"
23

34
describe("unwrap", () => {
45
it("upwraps a value", () => {

src/__tests__/unwrapObject.spec.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PII, unwrapObject } from "../index"
1+
import PII from "../pii"
2+
import unwrapObject from "../unwrapObject"
23

34
describe("unwrapObject", () => {
45
it("should remove all wrappers", () => {
@@ -59,4 +60,10 @@ describe("unwrapObject", () => {
5960
two: 2,
6061
})
6162
})
63+
64+
it("should return null after max depth", () => {
65+
expect(unwrapObject({ test: [{ hello: "world" }] }, 2)).toEqual({
66+
test: [null],
67+
})
68+
})
6269
})

src/__tests__/zipWith.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { PII, zip2With, zip3With, zip4With, unwrap } from "../index"
1+
import PII from "../pii"
2+
import zip2With from "../zip2With"
3+
import zip3With from "../zip3With"
4+
import zip4With from "../zip4With"
5+
import unwrap from "../unwrap"
26

37
describe("zipWith", () => {
48
it("should zipWith two different types of PII", () => {

src/containsPII.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { isPII } from "./pii"
2+
import visit from "./visit"
3+
4+
const containsPII = (input: unknown, maxDepth = Infinity): boolean =>
5+
maxDepth === 0 || isPII(input)
6+
? true
7+
: visit(input, {
8+
record: o => Object.values(o).some(i => containsPII(i, maxDepth - 1)),
9+
map: m =>
10+
Array.from(m).some(
11+
([k, v]) =>
12+
containsPII(k, maxDepth - 1) || containsPII(v, maxDepth - 1),
13+
),
14+
array: a => a.some(i => containsPII(i, maxDepth - 1)),
15+
set: s => Array.from(s).some(i => containsPII(i, maxDepth - 1)),
16+
primitive: p => isPII(p),
17+
object: p => isPII(p),
18+
})
19+
20+
export default containsPII

src/detect.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import PII, { isPII } from "./pii"
2+
import visit from "./visit"
3+
4+
const detect = (
5+
detector: (data: unknown) => boolean,
6+
input: unknown,
7+
maxDepth = Infinity,
8+
): unknown =>
9+
isPII(input)
10+
? input
11+
: detector(input) || maxDepth === 0
12+
? PII(input)
13+
: visit(input, {
14+
record: r =>
15+
Object.keys(r).reduce((sum, key) => {
16+
sum[key] = detect(detector, r[key], maxDepth - 1)
17+
return sum
18+
}, {} as Record<string, unknown>),
19+
map: m =>
20+
new Map(
21+
Array.from(m).map(([k, v]) => [
22+
detect(detector, k, maxDepth - 1),
23+
detect(detector, v, maxDepth - 1),
24+
]),
25+
),
26+
array: a => a.map(x => detect(detector, x, maxDepth - 1)),
27+
set: s =>
28+
new Set(Array.from(s).map(x => detect(detector, x, maxDepth - 1))),
29+
primitive: p => p,
30+
object: o => o,
31+
})
32+
33+
export default detect

src/fold.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import PII from "./pii"
2+
import unwrap from "./unwrap"
3+
4+
export default <A, B>(
5+
fn: (
6+
previousValue: B,
7+
currentValue: A,
8+
currentIndex: number,
9+
array: A[],
10+
) => B,
11+
initial: B,
12+
a: Array<PII<A> | A>,
13+
): PII<B> => PII(a.map<A>(unwrap).reduce(fn, initial))

src/index.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1-
export * from "./pii"
2-
export * from "./result"
1+
export { default as containsPII } from "./containsPII"
2+
export { default as detect } from "./detect"
3+
export { default as fold } from "./fold"
4+
export { default as map } from "./map"
5+
export { default as PII, isPII } from "./pii"
6+
export { default as PIITry } from "./PIITry"
7+
export { default as redact } from "./redact"
8+
export { default as tap } from "./tap"
9+
export { default as test } from "./test"
10+
export { default as unwrap } from "./unwrap"
11+
export { default as unwrapObject } from "./unwrapObject"
12+
export { default as zip2With } from "./zip2With"
13+
export { default as zip3With } from "./zip3With"
14+
export { default as zip4With } from "./zip4With"

src/isObject.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Function, regex, object, Number, String, etc
2+
export default (value: unknown): boolean => {
3+
const type = typeof value
4+
return value != null && (type == "object" || type == "function")
5+
}

src/isRecord.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const proto = Object.prototype
2+
const gpo = Object.getPrototypeOf
3+
4+
export default (obj: unknown): obj is Record<string, unknown> =>
5+
obj === null || typeof obj !== "object" ? false : gpo(obj) === proto

src/map.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import PII from "./pii"
2+
import unwrap from "./unwrap"
3+
4+
function map<T, T2>(fn: (item: T) => T2, item: PII<T>): PII<T2>
5+
function map<T, T2>(fn: (item: T) => T2, item: T): Exclude<T2, PII<any>>
6+
function map<T, T2>(
7+
fn: (item: T) => T2,
8+
item: PII<T> | T,
9+
): PII<T2> | Exclude<T2, PII<any>> {
10+
return PII(fn(unwrap(item)))
11+
}
12+
13+
export default map

0 commit comments

Comments
 (0)