1 | const { flatten, unflatten } = require('flat');
|
2 | const micromatch = require('micromatch');
|
3 | const { get } = require('lodash');
|
4 |
|
5 | const ifArray = (input, next) =>
|
6 | !Array.isArray(input) ? next(input) : input.map(next);
|
7 |
|
8 | class Redact {
|
9 | constructor(id, target = {}) {
|
10 | if (!id || !['response', 'request'].includes(id))
|
11 | throw new Error(
|
12 | 'ID must equal "request" or "response"',
|
13 | );
|
14 |
|
15 | this.id = id;
|
16 | this.mutable = target;
|
17 | }
|
18 |
|
19 | $append(doc) {
|
20 | return ifArray(doc, (v) =>
|
21 | this.prefix
|
22 | ? {
|
23 | [this.prefix]: v,
|
24 | }
|
25 | : v,
|
26 | );
|
27 | }
|
28 |
|
29 | $detach(doc) {
|
30 | return ifArray(doc, (v) =>
|
31 | this.prefix ? v[this.prefix] : v,
|
32 | );
|
33 | }
|
34 |
|
35 | $filter(doc = {}) {
|
36 | const flat = flatten(doc);
|
37 |
|
38 | const match = micromatch(
|
39 | Object.keys(flat),
|
40 | this.fields,
|
41 | );
|
42 |
|
43 | const unwind = match.reduce(
|
44 | (acc, key) =>
|
45 | Object.assign(acc, {
|
46 | [key]: flat[key],
|
47 | }),
|
48 | {},
|
49 | );
|
50 |
|
51 | return unflatten(unwind);
|
52 | }
|
53 |
|
54 | $runTransformers(acc, v) {
|
55 | const input = this.$append(this.mutable[v]);
|
56 | const output = ifArray(input, this.$filter.bind(this));
|
57 | const transformed = this.$detach(output);
|
58 |
|
59 | return Object.assign(acc, {
|
60 | [v]: transformed,
|
61 | });
|
62 | }
|
63 |
|
64 | $getEntries() {
|
65 | return this.rules.reduce(
|
66 | this.$runTransformers.bind(this),
|
67 | {},
|
68 | );
|
69 | }
|
70 |
|
71 | exec({ fields = [], readOnly = [], locations = {} }) {
|
72 | const { prefix } = locations;
|
73 | this.rules = get(locations, this.id, []);
|
74 | this.prefix = prefix;
|
75 |
|
76 | // for response payloads,
|
77 | // only reference the readOnly field rules
|
78 | this.fields =
|
79 | this.id === 'response' ? readOnly : fields;
|
80 |
|
81 | return this.$getEntries();
|
82 | }
|
83 | }
|
84 |
|
85 | module.exports = (
|
86 | { redactions },
|
87 | mutable,
|
88 | targetLocation,
|
89 | ) => {
|
90 | const redact = new Redact(targetLocation, mutable);
|
91 |
|
92 | return typeof redactions === 'object' &&
|
93 | Object.keys(redactions).length
|
94 | ? Object.assign(
|
95 | mutable,
|
96 | Object.values(redactions)
|
97 | .map(redact.exec.bind(redact))
|
98 | .reduce(
|
99 | (prev, next) => Object.assign(prev, next),
|
100 | {},
|
101 | ),
|
102 | )
|
103 | : mutable;
|
104 | };
|