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

Commit

Permalink
unwrap test
Browse files Browse the repository at this point in the history
  • Loading branch information
tdreyno committed Mar 4, 2021
1 parent a293623 commit e201367
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"dbaeumer.vscode-eslint",
"ms-vscode.vscode-typescript-tslint-plugin"
"ms-vscode.vscode-typescript-tslint-plugin",
"esbenp.prettier-vscode"
],

// Use 'forwardPorts' to make a list of ports inside the container available locally.
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
7 changes: 7 additions & 0 deletions src/__tests__/unwrap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import PII, { unwrap } from "../pii"

describe("unwrap", () => {
it("upwraps a value", () => {
expect(unwrap(PII("test"))).toBe("test")
})
})
87 changes: 1 addition & 86 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1 @@
// Class which wraps PII and keeps logging from accessing it.
class PII<T> {
constructor(
private _fire_me_if_you_see_me_accessing_this_property_outside_pii_ts: T,
) {}

toString() {
return "PII<REDACTED>"
}
}

const markPII = <T>(value: T): PII<T> => new PII(value)

export const unwrap = <A>(item: PII<A>): A =>
item["_fire_me_if_you_see_me_accessing_this_property_outside_pii_ts"]

export const map = <T, T2>(fn: (item: T) => T2, item: PII<T>): PII<T2> =>
markPII(fn(unwrap(item)))

export const test = <T>(fn: (item: T) => boolean, item: PII<T>): boolean =>
fn(unwrap(item))

export const fold = <A, B>(
fn: (
previousValue: B,
currentValue: A,
currentIndex: number,
array: A[],
) => B,
initial: B,
a: Array<PII<A>>,
): PII<B> => markPII(a.map(unwrap).reduce(fn, initial))

export const zipWith = <A, B, C>(
fn: (a: A, b: B) => C,
a: PII<A>,
b: PII<B>,
): PII<C> => markPII(fn(unwrap(a), unwrap(b)))

const proto = Object.prototype
const gpo = Object.getPrototypeOf

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

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

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

return visitors.primitive(input)
}

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

// Does not handle Set or Map for now.
export const unwrapObject = (input: unknown): any =>
visitPII(input, {
object: 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 instanceof PII ? unwrap(p) : p),
})

export default markPII
export * from "./pii"
86 changes: 86 additions & 0 deletions src/pii.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Class which wraps PII and keeps logging from accessing it.
class PII<T> {
constructor(
private _fire_me_if_you_see_me_accessing_this_property_outside_pii_ts: T,
) { }

toString() {
return "PII<REDACTED>"
}
}

const markPII = <T>(value: T): PII<T> => new PII(value)

export const unwrap = <A>(item: PII<A>): A =>
item["_fire_me_if_you_see_me_accessing_this_property_outside_pii_ts"]

export const map = <T, T2>(fn: (item: T) => T2, item: PII<T>): PII<T2> =>
markPII(fn(unwrap(item)))

export const test = <T>(fn: (item: T) => boolean, item: PII<T>): boolean =>
fn(unwrap(item))

export const fold = <A, B>(
fn: (
previousValue: B,
currentValue: A,
currentIndex: number,
array: A[],
) => B,
initial: B,
a: Array<PII<A>>,
): PII<B> => markPII(a.map(unwrap).reduce(fn, initial))

export const zipWith = <A, B, C>(
fn: (a: A, b: B) => C,
a: PII<A>,
b: PII<B>,
): PII<C> => markPII(fn(unwrap(a), unwrap(b)))

const proto = Object.prototype
const gpo = Object.getPrototypeOf

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

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

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

return visitors.primitive(input)
}

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

// Does not handle Set or Map for now.
export const unwrapObject = (input: unknown): any =>
visitPII(input, {
object: 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 instanceof PII ? unwrap(p) : p),
})

export default markPII

0 comments on commit e201367

Please sign in to comment.