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

Commit 170cc65

Browse files
committed
feat: add detect method to allow discovering PII in normal data
1 parent e5cf46b commit 170cc65

File tree

3 files changed

+77
-8
lines changed

3 files changed

+77
-8
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ const name = PII("Thomas")
9090
const lowercaseName = tap(n => console.log(n), name) // Logs "Thomas"
9191
```
9292

93+
#### Detecting PII in Data
94+
95+
Recurses through a data structure and uses a callback to detect values that should become PII.
96+
97+
```typescript
98+
import { PII, detect } from "@tdreyno/pii"
99+
100+
const person = { name: "Thomas" }
101+
const lowercaseName = detect(
102+
data => isObject(person) && Object.keys().some(k => k.includes(name)),
103+
person,
104+
) // Returns PII({ name: "Thomas" })
105+
```
106+
93107
#### Custom PII Redaction
94108

95109
```typescript

src/__tests__/detect.spec.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { detect, isPII } from "../index"
3+
4+
const detector = (data: unknown) => Array.isArray(data)
5+
6+
describe("detect", () => {
7+
it("should detect variables", () => {
8+
expect(detect(detector, "test")).toBe("test")
9+
expect(isPII(detect(detector, []))).toBeTruthy()
10+
})
11+
12+
it("should handle Map", () => {
13+
const map = new Map<string, any>([
14+
["a", 1],
15+
["b", []],
16+
])
17+
18+
const detectedArrays = detect(detector, { test: map })
19+
expect(isPII((detectedArrays as any).test.get("b"))).toBeTruthy()
20+
})
21+
22+
it("should handle Set", () => {
23+
const set = new Set([[]])
24+
25+
const detectedArrays = detect(detector, { test: set })
26+
expect(isPII(Array.from((detectedArrays as any).test)[0])).toBeTruthy()
27+
})
28+
})

src/pii.ts

+35-8
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ export interface PII<T> {
77
}
88

99
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10-
const isPIIType = <T>(val: any): val is PII<T> =>
10+
export const isPII = <T>(val: any): val is PII<T> =>
1111
isRecord(val) && val.__brand === "PII"
1212

1313
export const PII = <T>(val: T, msg = "REDACTED"): PII<T> =>
14-
isPIIType<T>(val)
14+
isPII<T>(val)
1515
? val
1616
: ({
1717
__brand: "PII",
@@ -24,7 +24,7 @@ export function unwrap<T>(item: PII<T>): Exclude<T, PII<any>>
2424
export function unwrap<T>(item: T): Exclude<T, PII<any>>
2525
export function unwrap<T>(item: T | PII<T>): Exclude<T, PII<any>> {
2626
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27-
return isPIIType(item)
27+
return isPII(item)
2828
? (item as any)[
2929
"__fire_me_if_you_see_me_accessing_this_property_outside_pii_ts"
3030
]
@@ -135,20 +135,20 @@ export const visitPII = <A, T>(
135135
}
136136

137137
export const containsPII = (input: unknown): boolean =>
138-
isPIIType(input)
138+
isPII(input)
139139
? true
140140
: visitPII(input, {
141141
record: o => Object.values(o).some(containsPII),
142142
map: m =>
143143
Array.from(m).some(([k, v]) => containsPII(k) || containsPII(v)),
144144
array: a => a.some(containsPII),
145145
set: s => Array.from(s).some(containsPII),
146-
primitive: p => isPIIType(p),
147-
object: p => isPIIType(p),
146+
primitive: p => isPII(p),
147+
object: p => isPII(p),
148148
})
149149

150150
export const unwrapObject = (input: unknown): unknown =>
151-
visitPII(isPIIType(input) ? unwrap(input) : input, {
151+
visitPII(isPII(input) ? unwrap(input) : input, {
152152
record: o =>
153153
Object.keys(o).reduce((sum, key) => {
154154
sum[key] = unwrapObject(o[key])
@@ -165,7 +165,7 @@ export const unwrapObject = (input: unknown): unknown =>
165165
})
166166

167167
export const redact = (redactor: (data: any) => any, input: unknown): unknown =>
168-
visitPII(isPIIType(input) ? redactor(input) : input, {
168+
visitPII(isPII(input) ? redactor(input) : input, {
169169
record: o =>
170170
Object.keys(o).reduce((sum, key) => {
171171
sum[key] = redact(redactor, o[key])
@@ -183,3 +183,30 @@ export const redact = (redactor: (data: any) => any, input: unknown): unknown =>
183183
primitive: p => p,
184184
object: p => p,
185185
})
186+
187+
export const detect = (
188+
detector: (data: any) => boolean,
189+
input: unknown,
190+
): unknown =>
191+
isPII(input)
192+
? input
193+
: detector(input)
194+
? PII(input)
195+
: visitPII(input, {
196+
record: o =>
197+
Object.keys(o).reduce((sum, key) => {
198+
sum[key] = detect(detector, o[key])
199+
return sum
200+
}, {} as Record<string, unknown>),
201+
map: m =>
202+
new Map(
203+
Array.from(m).map(([k, v]) => [
204+
detect(detector, k),
205+
detect(detector, v),
206+
]),
207+
),
208+
array: a => a.map(x => detect(detector, x)),
209+
set: s => new Set(Array.from(s).map(x => detect(detector, x))),
210+
primitive: p => p,
211+
object: p => p,
212+
})

0 commit comments

Comments
 (0)