@nmnmcc/ability 2.0.7 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -29
- package/dist/Authorizer.d.ts +21 -12
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +121 -19
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +32 -21
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +9 -0
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +5 -0
- package/dist/Domain.js.map +1 -1
- package/dist/Limits.d.ts +44 -13
- package/dist/Limits.d.ts.map +1 -1
- package/dist/Limits.js +52 -22
- package/dist/Limits.js.map +1 -1
- package/dist/Policy.d.ts +102 -18
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +7 -6
- package/dist/Policy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/internal/algebra.js +2 -2
- package/dist/internal/analysis.js +81 -75
- package/dist/internal/analysis.js.map +1 -1
- package/dist/internal/authorizer.d.ts.map +1 -1
- package/dist/internal/authorizer.js +11 -7
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +86 -64
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +33 -10
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +170 -77
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +27 -31
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/domain.js +19 -5
- package/dist/internal/domain.js.map +1 -1
- package/dist/internal/json.js +120 -76
- package/dist/internal/json.js.map +1 -1
- package/dist/internal/limits.d.ts +28 -0
- package/dist/internal/limits.d.ts.map +1 -1
- package/dist/internal/limits.js +75 -16
- package/dist/internal/limits.js.map +1 -1
- package/dist/internal/nodes.d.ts.map +1 -1
- package/dist/internal/nodes.js +53 -44
- package/dist/internal/nodes.js.map +1 -1
- package/dist/internal/patterns.js +50 -15
- package/dist/internal/patterns.js.map +1 -1
- package/dist/internal/policy-json.js +169 -127
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/policy-nodes.d.ts.map +1 -1
- package/dist/internal/policy-nodes.js +16 -13
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/policy.d.ts.map +1 -1
- package/dist/internal/policy.js +38 -22
- package/dist/internal/policy.js.map +1 -1
- package/dist/internal/resource.js +1 -1
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +6 -5
- package/dist/internal/rule.js.map +1 -1
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +106 -92
- package/dist/internal/search.js.map +1 -1
- package/dist/internal/trampoline.js +19 -5
- package/dist/internal/trampoline.js.map +1 -1
- package/package.json +1 -1
- package/src/Authorizer.ts +30 -20
- package/src/Condition.ts +317 -86
- package/src/Domain.ts +9 -0
- package/src/Limits.ts +69 -29
- package/src/Policy.ts +143 -21
- package/src/internal/analysis.ts +154 -124
- package/src/internal/authorizer.ts +29 -14
- package/src/internal/bind.ts +193 -136
- package/src/internal/catalog.ts +320 -118
- package/src/internal/decide.ts +61 -50
- package/src/internal/domain.ts +39 -3
- package/src/internal/json.ts +212 -117
- package/src/internal/limits.ts +101 -31
- package/src/internal/nodes.ts +65 -47
- package/src/internal/patterns.ts +79 -25
- package/src/internal/policy-json.ts +266 -197
- package/src/internal/policy-nodes.ts +31 -23
- package/src/internal/policy.ts +63 -23
- package/src/internal/rule.ts +16 -3
- package/src/internal/search.ts +186 -136
- package/src/internal/trampoline.ts +51 -4
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { LimitExceeded
|
|
2
|
-
import {
|
|
1
|
+
import { LimitExceeded } from "./limits.js";
|
|
2
|
+
import { adaptive, run } from "./trampoline.js";
|
|
3
3
|
import { at, pathOf } from "./place.js";
|
|
4
|
+
import { Problem, decodeAt, problemAt, read, readList, readString, spend, toJson as toJson$1 } from "./json.js";
|
|
4
5
|
import { make } from "./rule.js";
|
|
5
6
|
import { Ceiling, Rules, isAlgorithm, policy_nodes_exports, rulesOf } from "./policy-nodes.js";
|
|
7
|
+
import * as Arr from "effect/Array";
|
|
6
8
|
import * as Effect from "effect/Effect";
|
|
7
9
|
import * as Schema from "effect/Schema";
|
|
8
|
-
import * as Arr from "effect/Array";
|
|
9
10
|
// src/internal/policy-json.ts
|
|
10
11
|
/**
|
|
11
12
|
* The JSON form of a policy tree. The decoder is a plain walk, as the decoder
|
|
@@ -49,27 +50,32 @@ const ruleJson = (rule, metaJson, whenJson) => {
|
|
|
49
50
|
*/
|
|
50
51
|
const toJson = (node, meta) => {
|
|
51
52
|
const whens = new Map(Arr.map(rulesOf(node), (rule) => [rule, rule.when === void 0 ? void 0 : toJson$1(rule.when)]));
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
53
|
+
return run(node.depth, (R) => {
|
|
54
|
+
const tree = (current) => {
|
|
55
|
+
switch (current._tag) {
|
|
56
|
+
case "Rules": return R.succeed({
|
|
57
|
+
_tag: "Rules",
|
|
58
|
+
algorithm: current.algorithm,
|
|
59
|
+
rules: Arr.map(current.rules, (rule) => ruleJson(rule, meta, (found) => whens.get(found)))
|
|
60
|
+
});
|
|
61
|
+
case "All":
|
|
62
|
+
case "Any":
|
|
63
|
+
case "FirstOf": {
|
|
64
|
+
const tag = current._tag;
|
|
65
|
+
return R.map(R.forEach(current.policies, (child) => R.suspend(() => tree(child))), (policies) => ({
|
|
66
|
+
_tag: tag,
|
|
67
|
+
policies
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
case "Ceiling": return R.flatMap(R.suspend(() => tree(current.cap)), (cap) => R.map(R.suspend(() => tree(current.policy)), (policy) => ({
|
|
71
|
+
_tag: "Ceiling",
|
|
72
|
+
cap,
|
|
73
|
+
policy
|
|
74
|
+
})));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return tree(node);
|
|
78
|
+
});
|
|
73
79
|
};
|
|
74
80
|
/**
|
|
75
81
|
* Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the
|
|
@@ -82,12 +88,8 @@ const encode = (node, meta) => Effect.gen(function* () {
|
|
|
82
88
|
return toJson(node, (rule) => forms.get(rule));
|
|
83
89
|
});
|
|
84
90
|
/** Reads a list of strings. */
|
|
85
|
-
const readStrings = (input, name,
|
|
86
|
-
if (typeof value !== "string") throw
|
|
87
|
-
...path,
|
|
88
|
-
name,
|
|
89
|
-
index
|
|
90
|
-
], "The item must be a string");
|
|
91
|
+
const readStrings = (input, name, at$2, budget) => Arr.map(readList(input, name, at$2, budget), (value, index) => {
|
|
92
|
+
if (typeof value !== "string") throw problemAt(at(at$2, name, index), "The item must be a string");
|
|
91
93
|
return value;
|
|
92
94
|
});
|
|
93
95
|
/**
|
|
@@ -95,57 +97,65 @@ const readStrings = (input, name, path, budget) => Arr.map(readList(input, name,
|
|
|
95
97
|
* from the prototype of a class is not plain data, so it is a problem: a rule
|
|
96
98
|
* that loses its condition would allow more.
|
|
97
99
|
*/
|
|
98
|
-
const readOptional = (input, name,
|
|
100
|
+
const readOptional = (input, name, at, read$1) => {
|
|
99
101
|
if (Object.hasOwn(input, name)) return read$1();
|
|
100
|
-
read(input, name,
|
|
102
|
+
read(input, name, at);
|
|
101
103
|
};
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Counts the objects and items of a value, as the other parts count. It is a
|
|
106
|
+
* walk of the trampoline, so a deep value does not fill the stack.
|
|
107
|
+
*/
|
|
108
|
+
const spendValue = (budget) => adaptive((R, child) => (part, depth) => {
|
|
109
|
+
const { value, at: at$3 } = part;
|
|
110
|
+
if (typeof value !== "object" || value === null) return R.succeed(void 0);
|
|
106
111
|
const list = Array.isArray(value);
|
|
107
|
-
if (list) spend(budget, 1 + value.length,
|
|
112
|
+
if (list) spend(budget, 1 + value.length, at$3);
|
|
108
113
|
const names = list ? Array.from({ length: value.length }, (_, index) => String(index)) : Object.keys(value);
|
|
109
|
-
if (!list) spend(budget, 1 + names.length,
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
if (!list) spend(budget, 1 + names.length, at$3);
|
|
115
|
+
return R.map(R.forEach(names, (name) => child({
|
|
116
|
+
value: read(value, name, at$3),
|
|
117
|
+
at: at(at$3, name)
|
|
118
|
+
}, depth + 1)), () => void 0);
|
|
119
|
+
});
|
|
112
120
|
/**
|
|
113
121
|
* Decodes the `meta` of a rule with the `meta` Schema of the domain, as an
|
|
114
122
|
* Effect, since the Schema may need an async step. The Schema reads all of
|
|
115
123
|
* the value, so its size counts in the budget.
|
|
116
124
|
*/
|
|
117
125
|
const decodeMeta = (rule, domain, budget) => {
|
|
118
|
-
const { input } = rule;
|
|
119
|
-
const
|
|
120
|
-
return Effect.flatMap(attempt(() => typeof input !== "object" || input === null ? void 0 : readOptional(input, "meta",
|
|
121
|
-
const value = read(input, "meta",
|
|
122
|
-
if (domain.meta === void 0) throw
|
|
123
|
-
spendValue(
|
|
126
|
+
const { input, place } = rule;
|
|
127
|
+
const at$4 = at(place, "meta");
|
|
128
|
+
return Effect.flatMap(attempt(() => typeof input !== "object" || input === null ? void 0 : readOptional(input, "meta", place, () => {
|
|
129
|
+
const value = read(input, "meta", place);
|
|
130
|
+
if (domain.meta === void 0) throw problemAt(at$4, "The domain has no Schema for meta");
|
|
131
|
+
spendValue(budget)({
|
|
132
|
+
value,
|
|
133
|
+
at: at$4
|
|
134
|
+
});
|
|
124
135
|
return {
|
|
125
136
|
value,
|
|
126
137
|
schema: domain.meta
|
|
127
138
|
};
|
|
128
139
|
})), (found) => found === void 0 ? Effect.void : Effect.mapError(Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value), (error) => new Problem({
|
|
129
|
-
path:
|
|
140
|
+
path: pathOf(at$4),
|
|
130
141
|
message: error.message,
|
|
131
142
|
issue: error.issue
|
|
132
143
|
})));
|
|
133
144
|
};
|
|
134
145
|
const decodeRule = (rule, meta, budget, domain) => {
|
|
135
|
-
const { input } = rule;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const id = readOptional(input, "id", path, () => readString(input, "id", path));
|
|
146
|
+
const { input, place } = rule;
|
|
147
|
+
if (typeof input !== "object" || input === null) throw problemAt(place, "A rule must be an object");
|
|
148
|
+
spend(budget, 1, place);
|
|
149
|
+
const tag = read(input, "_tag", place);
|
|
150
|
+
if (tag !== "Allow" && tag !== "Deny") throw problemAt(at(place, "_tag"), "A rule must be \"Allow\" or \"Deny\"");
|
|
151
|
+
const when = readOptional(input, "when", place, () => decodeAt(read(input, "when", place), at(place, "when"), budget));
|
|
152
|
+
const fields = readOptional(input, "fields", place, () => readStrings(input, "fields", place, budget));
|
|
153
|
+
const reason = readOptional(input, "reason", place, () => readString(input, "reason", place));
|
|
154
|
+
const id = readOptional(input, "id", place, () => readString(input, "id", place));
|
|
145
155
|
return make({
|
|
146
156
|
_tag: tag,
|
|
147
|
-
actions: readStrings(input, "actions",
|
|
148
|
-
resources: readStrings(input, "resources",
|
|
157
|
+
actions: readStrings(input, "actions", place, budget),
|
|
158
|
+
resources: readStrings(input, "resources", place, budget),
|
|
149
159
|
...when === void 0 ? {} : { when },
|
|
150
160
|
...fields === void 0 ? {} : { fields },
|
|
151
161
|
...reason === void 0 ? {} : { reason },
|
|
@@ -157,82 +167,108 @@ const decodeRule = (rule, meta, budget, domain) => {
|
|
|
157
167
|
/**
|
|
158
168
|
* Reads the shape of a tree, and puts each rule in `rules`. The rules are
|
|
159
169
|
* decoded after the walk: a condition has levels of its own, and a decode
|
|
160
|
-
* inside the walk would go down the levels of both.
|
|
161
|
-
*
|
|
170
|
+
* inside the walk would go down the levels of both. The walk is a walk of
|
|
171
|
+
* the trampoline, so a deep tree does not fill the stack.
|
|
162
172
|
*/
|
|
163
|
-
const readTree = (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
const readTree = (budget, rules) => {
|
|
174
|
+
const count = { nodes: 0 };
|
|
175
|
+
const counted = (more, at) => {
|
|
176
|
+
count.nodes += more;
|
|
177
|
+
const most = budget.limits.policyNodes;
|
|
178
|
+
if (count.nodes > most) throw problemAt(at, `A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`);
|
|
179
|
+
};
|
|
180
|
+
return adaptive((R, child) => (part, depth) => {
|
|
181
|
+
const { input, at: at$1 } = part;
|
|
182
|
+
if (typeof input !== "object" || input === null) throw problemAt(at$1, "A node of a policy must be an object");
|
|
183
|
+
spend(budget, 1, at$1);
|
|
184
|
+
counted(1, at$1);
|
|
185
|
+
const below = (value, ...parts) => child({
|
|
186
|
+
input: value,
|
|
187
|
+
at: at(at$1, ...parts)
|
|
188
|
+
}, depth + 1);
|
|
189
|
+
const tag = read(input, "_tag", at$1);
|
|
190
|
+
switch (tag) {
|
|
191
|
+
case "Rules": {
|
|
192
|
+
const algorithm = readString(input, "algorithm", at$1);
|
|
193
|
+
if (!isAlgorithm(algorithm)) throw problemAt(at(at$1, "algorithm"), `"${algorithm}" is not a combining algorithm`);
|
|
194
|
+
const list = readList(input, "rules", at$1, budget);
|
|
195
|
+
counted(list.length, at$1);
|
|
196
|
+
const found = Arr.map(list, (rule, index) => rules.push({
|
|
197
|
+
input: rule,
|
|
198
|
+
place: at(at$1, "rules", index)
|
|
199
|
+
}) - 1);
|
|
200
|
+
return R.succeed(sized(budget, {
|
|
201
|
+
_tag: "Rules",
|
|
202
|
+
place: at$1,
|
|
203
|
+
size: 1 + found.length,
|
|
204
|
+
algorithm,
|
|
205
|
+
rules: found
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
case "All":
|
|
209
|
+
case "Any":
|
|
210
|
+
case "FirstOf": return R.map(R.forEach(readList(input, "policies", at$1, budget), (value, index) => below(value, "policies", index)), (policies) => sized(budget, {
|
|
190
211
|
_tag: tag,
|
|
191
|
-
place,
|
|
212
|
+
place: at$1,
|
|
192
213
|
size: Arr.reduce(policies, 1, (total, plan) => total + plan.size),
|
|
193
214
|
policies
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
215
|
+
}));
|
|
216
|
+
case "Ceiling": {
|
|
217
|
+
const cap = read(input, "cap", at$1);
|
|
218
|
+
const policy = read(input, "policy", at$1);
|
|
219
|
+
return R.flatMap(below(cap, "cap"), (capPlan) => R.map(below(policy, "policy"), (policyPlan) => sized(budget, {
|
|
220
|
+
_tag: "Ceiling",
|
|
221
|
+
place: at$1,
|
|
222
|
+
size: 1 + capPlan.size + policyPlan.size,
|
|
223
|
+
cap: capPlan,
|
|
224
|
+
policy: policyPlan
|
|
225
|
+
})));
|
|
226
|
+
}
|
|
227
|
+
default: throw problemAt(at(at$1, "_tag"), "The node has no known tag");
|
|
206
228
|
}
|
|
207
|
-
|
|
208
|
-
}
|
|
229
|
+
});
|
|
209
230
|
};
|
|
210
231
|
/** Stops a plan past `policyNodes`, with the message of the nodes. */
|
|
211
|
-
const sized = (
|
|
212
|
-
|
|
232
|
+
const sized = (budget, plan) => {
|
|
233
|
+
const most = budget.limits.policyNodes;
|
|
234
|
+
if (plan.size > most) throw problemAt(plan.place, `A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`);
|
|
213
235
|
return plan;
|
|
214
236
|
};
|
|
215
|
-
/**
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Makes the nodes of a plan, with the decoded rules. It is a walk of the
|
|
239
|
+
* trampoline, so a deep plan does not fill the stack.
|
|
240
|
+
*/
|
|
241
|
+
const build = (budget, rules) => adaptive((R, child) => (plan, depth) => {
|
|
242
|
+
const make = (made) => {
|
|
243
|
+
const node = (() => {
|
|
244
|
+
try {
|
|
245
|
+
return made();
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error instanceof LimitExceeded) throw problemAt(plan.place, error.message);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
})();
|
|
251
|
+
const most = budget.limits.policyNodes;
|
|
252
|
+
if (node.size > most) throw problemAt(plan.place, `A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`);
|
|
253
|
+
return node;
|
|
254
|
+
};
|
|
255
|
+
switch (plan._tag) {
|
|
256
|
+
case "Rules": return R.succeed(make(() => new Rules({
|
|
257
|
+
algorithm: plan.algorithm,
|
|
258
|
+
rules: Arr.map(plan.rules, (index) => rules[index])
|
|
259
|
+
})));
|
|
260
|
+
case "All":
|
|
261
|
+
case "Any":
|
|
262
|
+
case "FirstOf": {
|
|
263
|
+
const tag = plan._tag;
|
|
264
|
+
return R.map(R.forEach(plan.policies, (inner) => child(inner, depth + 1)), (policies) => make(() => new policy_nodes_exports[tag](policies)));
|
|
230
265
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
266
|
+
case "Ceiling": return R.flatMap(child(plan.cap, depth + 1), (cap) => R.map(child(plan.policy, depth + 1), (policy) => make(() => new Ceiling({
|
|
267
|
+
cap,
|
|
268
|
+
policy
|
|
269
|
+
}))));
|
|
234
270
|
}
|
|
235
|
-
};
|
|
271
|
+
});
|
|
236
272
|
/** Runs a step that may throw a problem, and puts the problem in the error. */
|
|
237
273
|
const attempt = (step) => Effect.try({
|
|
238
274
|
try: step,
|
|
@@ -247,12 +283,18 @@ const attempt = (step) => Effect.try({
|
|
|
247
283
|
* makes the nodes last.
|
|
248
284
|
*/
|
|
249
285
|
const decode = (input, domain) => Effect.gen(function* () {
|
|
250
|
-
const budget = {
|
|
286
|
+
const budget = {
|
|
287
|
+
used: 0,
|
|
288
|
+
limits: domain.limits
|
|
289
|
+
};
|
|
251
290
|
const inputs = [];
|
|
252
|
-
const plan = yield* attempt(() => readTree(
|
|
291
|
+
const plan = yield* attempt(() => readTree(budget, inputs)({
|
|
292
|
+
input,
|
|
293
|
+
at: void 0
|
|
294
|
+
}));
|
|
253
295
|
const metas = yield* Effect.forEach(inputs, (rule) => decodeMeta(rule, domain, budget));
|
|
254
296
|
const rules = yield* attempt(() => Arr.map(inputs, (rule, index) => decodeRule(rule, metas[index], budget, domain)));
|
|
255
|
-
return yield* attempt(() => build(
|
|
297
|
+
return yield* attempt(() => build(budget, rules)(plan));
|
|
256
298
|
});
|
|
257
299
|
export { decode, encode, syncMeta, toJson };
|
|
258
300
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Json.readList","Json.problem","read","Json.read","Place.pathOf","Json.Problem","Json.decodeAt","Json.readString","Rule.make","Place.at","PolicyNodes.isAlgorithm","Limits.policyNodes","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling","Limits.LimitExceeded"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\tconst tree = (current: Node): unknown => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: current._tag,\n\t\t\t\t\tpolicies: Arr.map(current.policies, tree),\n\t\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\tcap: tree(current.cap),\n\t\t\t\t\tpolicy: tree(current.policy),\n\t\t\t\t}\n\t\t}\n\t}\n\treturn tree(node)\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, path, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problem([...path, name, index], \"The item must be a string\")\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, path)\n\treturn undefined\n}\n\n/** Counts the objects and items of a value, as the other parts count. */\nconst spendValue = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n): void => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn\n\t}\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`The value has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\t// The length of an array counts before its items are read, so a sparse\n\t// array with a very large length stops at once.\n\tconst list = Array.isArray(value)\n\tif (list) {\n\t\tJson.spend(budget, 1 + value.length, path)\n\t}\n\tconst names = list\n\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t: Object.keys(value)\n\tif (!list) {\n\t\tJson.spend(budget, 1 + names.length, path)\n\t}\n\tArr.forEach(names, (name) =>\n\t\tspendValue(\n\t\t\tJson.read(value, name, path),\n\t\t\t[...path, name],\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t),\n\t)\n}\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input } = rule\n\tconst path = Place.pathOf(rule.place)\n\treturn Effect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", path, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", path)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problem(\n\t\t\t\t\t\t\t\t[...path, \"meta\"],\n\t\t\t\t\t\t\t\t\"The domain has no Schema for meta\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(value, [...path, \"meta\"], 1, budget)\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(metaCodec(found.schema))(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: [...path, \"meta\"],\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input } = rule\n\tconst path = Place.pathOf(rule.place)\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problem([...path, \"_tag\"], 'A rule must be \"Allow\" or \"Deny\"')\n\t}\n\tconst when = readOptional(input, \"when\", path, () =>\n\t\tJson.decodeAt(Json.read(input, \"when\", path), [...path, \"when\"], budget),\n\t)\n\tconst fields = readOptional(input, \"fields\", path, () =>\n\t\treadStrings(input, \"fields\", path, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", path, () =>\n\t\tJson.readString(input, \"reason\", path),\n\t)\n\tconst id = readOptional(input, \"id\", path, () =>\n\t\tJson.readString(input, \"id\", path),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", path, budget),\n\t\tresources: readStrings(input, \"resources\", path, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. `path` is the path of the\n * node for a problem here, and `place` keeps it for later.\n */\nconst readTree = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tplace: Place.Place | undefined,\n\tdepth: number,\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): Plan => {\n\tif (depth > Limits.policyDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A node of a policy must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst child = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\treadTree(\n\t\t\tvalue,\n\t\t\t[...path, ...parts],\n\t\t\tPlace.at(place, ...parts),\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t\trules,\n\t\t)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"Rules\": {\n\t\t\tconst algorithm = Json.readString(input, \"algorithm\", path)\n\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\tthrow Json.problem(\n\t\t\t\t\t[...path, \"algorithm\"],\n\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst found = Arr.map(\n\t\t\t\tJson.readList(input, \"rules\", path, budget),\n\t\t\t\t(rule, index) =>\n\t\t\t\t\trules.push({\n\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\tplace: Place.at(place, \"rules\", index),\n\t\t\t\t\t}) - 1,\n\t\t\t)\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: \"Rules\",\n\t\t\t\tplace,\n\t\t\t\tsize: 1 + found.length,\n\t\t\t\talgorithm,\n\t\t\t\trules: found,\n\t\t\t})\n\t\t}\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\": {\n\t\t\tconst policies = Arr.map(\n\t\t\t\tJson.readList(input, \"policies\", path, budget),\n\t\t\t\t(value, index) => child(value, \"policies\", index),\n\t\t\t)\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: tag,\n\t\t\t\tplace,\n\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\tpolicies,\n\t\t\t})\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = child(Json.read(input, \"cap\", path), \"cap\")\n\t\t\tconst policy = child(Json.read(input, \"policy\", path), \"policy\")\n\t\t\treturn sized(path, {\n\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\tplace,\n\t\t\t\tsize: 1 + cap.size + policy.size,\n\t\t\t\tcap,\n\t\t\t\tpolicy,\n\t\t\t})\n\t\t}\n\t\tdefault:\n\t\t\tthrow Json.problem([...path, \"_tag\"], \"The node has no known tag\")\n\t}\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (path: ReadonlyArray<PropertyKey>, plan: Plan): Plan => {\n\tif (plan.size > Limits.policyNodes) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(Limits.policyNodes)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/** Makes the nodes of a plan, with the decoded rules. */\nconst build = (plan: Plan, rules: ReadonlyArray<Rule.Rule>): Node => {\n\ttry {\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn new PolicyNodes.Rules({\n\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t})\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn new PolicyNodes[plan._tag](\n\t\t\t\t\tArr.map(plan.policies, (child) => build(child, rules)),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn new PolicyNodes.Ceiling({\n\t\t\t\t\tcap: build(plan.cap, rules),\n\t\t\t\t\tpolicy: build(plan.policy, rules),\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\t// A node past a limit is a problem at its place.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow Json.problem(Place.pathOf(plan.place), error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0 }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(input, [], undefined, 1, budget, inputs),\n\t\t)\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(plan, rules))\n\t})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Trampoline.run","Json.readList","at","Json.problemAt","Place.at","read","Trampoline.adaptive","Json.read","Json.Problem","Place.pathOf","Json.decodeAt","Json.readString","Rule.make","PolicyNodes.isAlgorithm","Limits.LimitExceeded","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\treturn Trampoline.run(node.depth, (R) => {\n\t\tconst tree = (current: Node): Trampoline.Step<unknown> => {\n\t\t\tswitch (current._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed({\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\": {\n\t\t\t\t\tconst tag = current._tag\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(current.policies, (child) =>\n\t\t\t\t\t\t\tR.suspend(() => tree(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(policies) => ({ _tag: tag, policies }),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => tree(current.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => tree(current.policy)),\n\t\t\t\t\t\t\t\t(policy) => ({ _tag: \"Ceiling\", cap, policy }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn tree(node)\n\t})\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, at, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tPlace.at(at, name, index),\n\t\t\t\t\"The item must be a string\",\n\t\t\t)\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, at)\n\treturn undefined\n}\n\n/** A value of the input to count, with its place. */\ninterface Part {\n\treadonly value: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Counts the objects and items of a value, as the other parts count. It is a\n * walk of the trampoline, so a deep value does not fill the stack.\n */\nconst spendValue = (budget: Json.Budget): ((part: Part) => void) =>\n\tTrampoline.adaptive<Part, void>((R, child) => (part, depth) => {\n\t\tconst { value, at } = part\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(undefined)\n\t\t}\n\t\t// The length of an array counts before its items are read, so a sparse\n\t\t// array with a very large length stops at once.\n\t\tconst list = Array.isArray(value)\n\t\tif (list) {\n\t\t\tJson.spend(budget, 1 + value.length, at)\n\t\t}\n\t\tconst names = list\n\t\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t\t: Object.keys(value)\n\t\tif (!list) {\n\t\t\tJson.spend(budget, 1 + names.length, at)\n\t\t}\n\t\treturn R.map(\n\t\t\tR.forEach(names, (name) =>\n\t\t\t\tchild(\n\t\t\t\t\t{ value: Json.read(value, name, at), at: Place.at(at, name) },\n\t\t\t\t\tdepth + 1,\n\t\t\t\t),\n\t\t\t),\n\t\t\t() => undefined,\n\t\t)\n\t})\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input, place } = rule\n\tconst at = Place.at(place, \"meta\")\n\treturn Effect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", place, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", place)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problemAt(at, \"The domain has no Schema for meta\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(budget)({ value, at })\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(metaCodec(found.schema))(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input, place } = rule\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problemAt(place, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, place)\n\tconst tag = Json.read(input, \"_tag\", place)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problemAt(\n\t\t\tPlace.at(place, \"_tag\"),\n\t\t\t'A rule must be \"Allow\" or \"Deny\"',\n\t\t)\n\t}\n\tconst when = readOptional(input, \"when\", place, () =>\n\t\tJson.decodeAt(\n\t\t\tJson.read(input, \"when\", place),\n\t\t\tPlace.at(place, \"when\"),\n\t\t\tbudget,\n\t\t),\n\t)\n\tconst fields = readOptional(input, \"fields\", place, () =>\n\t\treadStrings(input, \"fields\", place, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", place, () =>\n\t\tJson.readString(input, \"reason\", place),\n\t)\n\tconst id = readOptional(input, \"id\", place, () =>\n\t\tJson.readString(input, \"id\", place),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", place, budget),\n\t\tresources: readStrings(input, \"resources\", place, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/** A node of the input of a tree, with its place. */\ninterface TreePart {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. The walk is a walk of\n * the trampoline, so a deep tree does not fill the stack.\n */\nconst readTree = (\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): ((part: TreePart) => Plan) => {\n\t// The nodes and rules of the tree that the walk has read. A tree with more\n\t// of them than `policyNodes` does not load, so the walk stops at the first\n\t// one past it, and it does not read the rest of a very large input. The\n\t// count belongs to one decode.\n\tconst count = { nodes: 0 }\n\tconst counted = (more: number, at: Place.Place | undefined): void => {\n\t\tcount.nodes += more\n\t\tconst most = budget.limits.policyNodes\n\t\tif (count.nodes > most) {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tat,\n\t\t\t\t`A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`,\n\t\t\t)\n\t\t}\n\t}\n\treturn Trampoline.adaptive<TreePart, Plan>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow Json.problemAt(at, \"A node of a policy must be an object\")\n\t\t}\n\t\tJson.spend(budget, 1, at)\n\t\tcounted(1, at)\n\t\tconst below = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\t\tchild({ input: value, at: Place.at(at, ...parts) }, depth + 1)\n\t\tconst tag = Json.read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"Rules\": {\n\t\t\t\tconst algorithm = Json.readString(input, \"algorithm\", at)\n\t\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\t\tPlace.at(at, \"algorithm\"),\n\t\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst list = Json.readList(input, \"rules\", at, budget)\n\t\t\t\tcounted(list.length, at)\n\t\t\t\tconst found = Arr.map(\n\t\t\t\t\tlist,\n\t\t\t\t\t(rule, index) =>\n\t\t\t\t\t\trules.push({\n\t\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\t\tplace: Place.at(at, \"rules\", index),\n\t\t\t\t\t\t}) - 1,\n\t\t\t\t)\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\tsize: 1 + found.length,\n\t\t\t\t\t\talgorithm,\n\t\t\t\t\t\trules: found,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\tJson.readList(input, \"policies\", at, budget),\n\t\t\t\t\t\t(value, index) => below(value, \"policies\", index),\n\t\t\t\t\t),\n\t\t\t\t\t(policies) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: tag,\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\t\t\t\tpolicies,\n\t\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\": {\n\t\t\t\tconst cap = Json.read(input, \"cap\", at)\n\t\t\t\tconst policy = Json.read(input, \"policy\", at)\n\t\t\t\treturn R.flatMap(below(cap, \"cap\"), (capPlan) =>\n\t\t\t\t\tR.map(below(policy, \"policy\"), (policyPlan) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: 1 + capPlan.size + policyPlan.size,\n\t\t\t\t\t\t\tcap: capPlan,\n\t\t\t\t\t\t\tpolicy: policyPlan,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow Json.problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (budget: Json.Budget, plan: Plan): Plan => {\n\tconst most = budget.limits.policyNodes\n\tif (plan.size > most) {\n\t\tthrow Json.problemAt(\n\t\t\tplan.place,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/**\n * Makes the nodes of a plan, with the decoded rules. It is a walk of the\n * trampoline, so a deep plan does not fill the stack.\n */\nconst build = (\n\tbudget: Json.Budget,\n\trules: ReadonlyArray<Rule.Rule>,\n): ((plan: Plan) => Node) =>\n\tTrampoline.adaptive<Plan, Node>((R, child) => (plan, depth) => {\n\t\t// A node past a limit is a problem at its place. The constructor checks\n\t\t// the ceilings, and the decoder checks the limits of the domain, with\n\t\t// the nodes and values of the conditions.\n\t\tconst make = (made: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn made()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow Json.problemAt(plan.place, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.policyNodes\n\t\t\tif (node.size > most) {\n\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\tplan.place,\n\t\t\t\t\t`A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew PolicyNodes.Rules({\n\t\t\t\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\": {\n\t\t\t\tconst tag = plan._tag\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(plan.policies, (inner) => child(inner, depth + 1)),\n\t\t\t\t\t(policies) => make(() => new PolicyNodes[tag](policies)),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.flatMap(child(plan.cap, depth + 1), (cap) =>\n\t\t\t\t\tR.map(child(plan.policy, depth + 1), (policy) =>\n\t\t\t\t\t\tmake(() => new PolicyNodes.Ceiling({ cap, policy })),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t}\n\t})\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0, limits: domain.limits }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(budget, inputs)({ input, at: undefined }),\n\t\t)\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(budget, rules)(plan))\n\t})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-nodes.d.ts","names":["Rule.Rule"],"sources":["../../src/internal/policy-nodes.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"policy-nodes.d.ts","names":["Rule.Rule"],"sources":["../../src/internal/policy-nodes.ts"],"mappings":";;;;qBAoBa,WAAS,OAAA;YAOV,mBAAmB,UAAU;UAS/B;WACA;WACA;WACA;;;uBA4BK,aAAa,KAAK;;EAGhC,YAAY,eAAe,OAAO;;MAM9B;;MAKA;;MAKA;;;qBAaQ,cAAc;WACjB;WACQ,WAAW;WACX,OAAO,cAAcA;EACtC,YAAY;aACF,WAAW;aACX,OAAO,cAAcA;;;;qBA0CnB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,KAAK;WACL,QAAQ;EACzB,YAAY;aAAkB,KAAK;aAAe,QAAQ;;;;YAO/C,OAAO,QAAQ,MAAM,MAAM,UAAU"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ceilings, exceeded } from "./limits.js";
|
|
3
|
+
import { run } from "./trampoline.js";
|
|
3
4
|
import { isNode as isNode$1 } from "./nodes.js";
|
|
4
5
|
import { isRule } from "./rule.js";
|
|
6
|
+
import * as Arr from "effect/Array";
|
|
5
7
|
import * as Schema from "effect/Schema";
|
|
6
8
|
import * as Data from "effect/Data";
|
|
7
|
-
import * as Arr from "effect/Array";
|
|
8
9
|
// src/internal/policy-nodes.ts
|
|
9
10
|
/**
|
|
10
11
|
* The nodes of a policy tree. As with the nodes of a condition, each node is
|
|
@@ -42,8 +43,7 @@ const Algorithm = Schema.Literals([
|
|
|
42
43
|
const isAlgorithm = (u) => Algorithm.literals.includes(u);
|
|
43
44
|
/** Checks the facts of a node against the limits. */
|
|
44
45
|
const checked = (facts) => {
|
|
45
|
-
if (facts.size >
|
|
46
|
-
if (facts.depth > 256) throw exceeded("A policy tree", "policyDepth", "levels", facts.depth, 256);
|
|
46
|
+
if (facts.size > ceilings.policyNodes) throw exceeded("A policy tree", "policyNodes", "nodes", facts.size, ceilings.policyNodes);
|
|
47
47
|
return facts;
|
|
48
48
|
};
|
|
49
49
|
const factsOf = (children) => checked({
|
|
@@ -141,15 +141,18 @@ const isNode = (u) => u instanceof Base;
|
|
|
141
141
|
* into the condition of each rule takes the rules from this list first, so it
|
|
142
142
|
* does not go down the levels of the tree and of a condition at once.
|
|
143
143
|
*/
|
|
144
|
-
const rulesOf = (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
144
|
+
const rulesOf = (root) => run(root.depth, (R) => {
|
|
145
|
+
const go = (node) => {
|
|
146
|
+
switch (node._tag) {
|
|
147
|
+
case "Rules": return R.succeed(node.rules);
|
|
148
|
+
case "All":
|
|
149
|
+
case "Any":
|
|
150
|
+
case "FirstOf": return R.map(R.forEach(node.policies, (child) => R.suspend(() => go(child))), Arr.flatten);
|
|
151
|
+
case "Ceiling": return R.map(R.forEach([node.cap, node.policy], (child) => R.suspend(() => go(child))), Arr.flatten);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return go(root);
|
|
155
|
+
});
|
|
153
156
|
export { Algorithm, All, Any, Ceiling, FirstOf, Rules, isAlgorithm, isNode, policy_nodes_exports, rulesOf };
|
|
154
157
|
|
|
155
158
|
//# sourceMappingURL=policy-nodes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-nodes.js","names":["Limits.exceeded","Limits.policyNodes","Nodes.isNode","Rule.isRule"],"sources":["../../src/internal/policy-nodes.ts"],"sourcesContent":["/**\n * The nodes of a policy tree. As with the nodes of a condition, each node is\n * a class, only its constructor makes one, and it works out three facts once:\n * - `size`: the number of nodes, with each rule and each node of its condition,\n * and a shared child counted for each place;\n * - `depth`: the number of levels of the tree;\n * - `opaque`: a rule of the tree has a predicate.\n *\n * The constructor stops a tree past `policyNodes` or `policyDepth`. So a walk\n * over any tree, such as a decision, needs no memo.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Schema from \"effect/Schema\"\nimport * as Rule from \"./rule.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\n\n/** The combining algorithms of a list of rules. The decoder reads this list too. */\nexport const Algorithm = Schema.Literals([\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n])\n\nexport type Algorithm = typeof Algorithm.Type\n\n/**\n * Tests if a value is a combining algorithm. It reads the list of the Schema\n * with `includes`, as `Nodes.isCompareOperator` does.\n */\nexport const isAlgorithm = (u: unknown): u is Algorithm =>\n\t(Algorithm.literals as ReadonlyArray<unknown>).includes(u)\n\ninterface Facts {\n\treadonly size: number\n\treadonly depth: number\n\treadonly opaque: boolean\n}\n\n/** Checks the facts of a node against the limits. */\nconst checked = (facts: Facts): Facts => {\n\tif (facts.size > Limits.policyNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyNodes\",\n\t\t\t\"nodes\",\n\t\t\tfacts.size,\n\t\t\tLimits.policyNodes,\n\t\t)\n\t}\n\tif (facts.depth > Limits.policyDepth) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyDepth\",\n\t\t\t\"levels\",\n\t\t\tfacts.depth,\n\t\t\tLimits.policyDepth,\n\t\t)\n\t}\n\treturn facts\n}\n\nconst factsOf = (children: ReadonlyArray<Node>): Facts =>\n\tchecked({\n\t\tsize: Arr.reduce(children, 1, (total, child) => total + child.size),\n\t\tdepth:\n\t\t\t1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth)),\n\t\topaque: Arr.some(children, (child) => child.opaque),\n\t})\n\n/** The base of every node. It keeps the facts. */\nabstract class Base extends Data.Class {\n\treadonly #facts: Facts\n\n\tconstructor(props: object, facts: Facts) {\n\t\tsuper(props as never)\n\t\tthis.#facts = facts\n\t}\n\n\t/** The number of nodes, with the rules and their conditions. */\n\tget size(): number {\n\t\treturn this.#facts.size\n\t}\n\n\t/** The number of levels of the tree. */\n\tget depth(): number {\n\t\treturn this.#facts.depth\n\t}\n\n\t/** A rule of the tree has a predicate. */\n\tget opaque(): boolean {\n\t\treturn this.#facts.opaque\n\t}\n}\n\n/**\n * The condition of a rule. A value that is not a node counts as one node\n * here: the binding of the rule gives the issue for it, before any walk.\n */\nconst whenOf = (rule: Rule.Rule): Nodes.Node | undefined =>\n\tNodes.isNode(rule.when) ? rule.when : undefined\n\n/** A list of rules with a combining algorithm. */\nexport class Rules extends Base {\n\treadonly _tag = \"Rules\"\n\tdeclare readonly algorithm: Algorithm\n\tdeclare readonly rules: ReadonlyArray<Rule.Rule>\n\tconstructor(props: {\n\t\treadonly algorithm: Algorithm\n\t\treadonly rules: ReadonlyArray<Rule.Rule>\n\t}) {\n\t\tif (!isAlgorithm(props.algorithm)) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${String(props.algorithm)}\" is not a combining algorithm`,\n\t\t\t)\n\t\t}\n\t\tconst rules = Object.freeze(Array.from(props.rules))\n\t\t// A value that is not a rule, such as a copy of a rule made with a\n\t\t// spread, may keep the type fields of another rule, and it does not\n\t\t// work in a generator. It is a mistake in the code, so it is a defect.\n\t\tif (!Arr.every(rules, Rule.isRule)) {\n\t\t\tthrow new Error(\"A value in the list is not a rule\")\n\t\t}\n\t\tconst conditions = Arr.map(rules, whenOf)\n\t\tsuper(\n\t\t\t{ algorithm: props.algorithm, rules },\n\t\t\tchecked({\n\t\t\t\tsize: Arr.reduce(\n\t\t\t\t\tconditions,\n\t\t\t\t\t1,\n\t\t\t\t\t// A condition counts its nodes and the values of its tests.\n\t\t\t\t\t(total, when) => total + 1 + (when?.weight ?? 0),\n\t\t\t\t),\n\t\t\t\tdepth: 1,\n\t\t\t\topaque: Arr.some(conditions, (when) => when?.opaque ?? false),\n\t\t\t}),\n\t\t)\n\t}\n}\n\nconst checkChildren = (\n\tpolicies: ReadonlyArray<unknown>,\n): ReadonlyArray<Node> => {\n\tconst children = Array.from(policies)\n\tif (!Arr.every(children, isNode)) {\n\t\tthrow new Error(\"A child of a policy tree is not a node of a policy\")\n\t}\n\treturn Object.freeze(children)\n}\n\n/** Every policy must allow, and one deny is enough to deny. */\nexport class All extends Base {\n\treadonly _tag = \"All\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** One allow is enough. Without an allow, one deny gives a deny. */\nexport class Any extends Base {\n\treadonly _tag = \"Any\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** The first policy that allows or denies gives the answer. */\nexport class FirstOf extends Base {\n\treadonly _tag = \"FirstOf\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** A deny from `cap` always wins. An allow from `cap` does nothing. */\nexport class Ceiling extends Base {\n\treadonly _tag = \"Ceiling\"\n\tdeclare readonly cap: Node\n\tdeclare readonly policy: Node\n\tconstructor(props: { readonly cap: Node; readonly policy: Node }) {\n\t\tconst [cap, policy] = checkChildren([props.cap, props.policy])\n\t\tsuper({ cap, policy }, factsOf([cap!, policy!]))\n\t}\n}\n\n/** A node of a policy tree. */\nexport type Node = Rules | All | Any | FirstOf | Ceiling\n\n/** Tests if a value is a node of a policy tree. */\nexport const isNode = (u: unknown): u is Node => u instanceof Base\n\n/**\n * The rules of a tree, in order, once for each place. A walk that also goes\n * into the condition of each rule takes the rules from this list first, so it\n * does not go down the levels of the tree and of a condition at once.\n */\nexport const rulesOf = (node: Node): ReadonlyArray<Rule.Rule> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn Arr.flatMap(node.policies, rulesOf)\n\t\tcase \"Ceiling\":\n\t\t\treturn [...rulesOf(node.cap), ...rulesOf(node.policy)]\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"policy-nodes.js","names":["Limits.exceeded","Nodes.isNode","Rule.isRule","Trampoline.run"],"sources":["../../src/internal/policy-nodes.ts"],"sourcesContent":["/**\n * The nodes of a policy tree. As with the nodes of a condition, each node is\n * a class, only its constructor makes one, and it works out three facts once:\n * - `size`: the number of nodes, with each rule and each node of its condition,\n * and a shared child counted for each place;\n * - `depth`: the number of levels of the tree;\n * - `opaque`: a rule of the tree has a predicate.\n *\n * The constructor stops a tree past `policyNodes` or `policyDepth`. So a walk\n * over any tree, such as a decision, needs no memo.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Schema from \"effect/Schema\"\nimport * as Rule from \"./rule.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\n/** The combining algorithms of a list of rules. The decoder reads this list too. */\nexport const Algorithm = Schema.Literals([\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n])\n\nexport type Algorithm = typeof Algorithm.Type\n\n/**\n * Tests if a value is a combining algorithm. It reads the list of the Schema\n * with `includes`, as `Nodes.isCompareOperator` does.\n */\nexport const isAlgorithm = (u: unknown): u is Algorithm =>\n\t(Algorithm.literals as ReadonlyArray<unknown>).includes(u)\n\ninterface Facts {\n\treadonly size: number\n\treadonly depth: number\n\treadonly opaque: boolean\n}\n\n/** Checks the facts of a node against the limits. */\nconst checked = (facts: Facts): Facts => {\n\t// A node does not know its domain, so it checks the ceiling. A policy\n\t// checks the limit of its domain.\n\tif (facts.size > Limits.ceilings.policyNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyNodes\",\n\t\t\t\"nodes\",\n\t\t\tfacts.size,\n\t\t\tLimits.ceilings.policyNodes,\n\t\t)\n\t}\n\treturn facts\n}\n\nconst factsOf = (children: ReadonlyArray<Node>): Facts =>\n\tchecked({\n\t\tsize: Arr.reduce(children, 1, (total, child) => total + child.size),\n\t\tdepth:\n\t\t\t1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth)),\n\t\topaque: Arr.some(children, (child) => child.opaque),\n\t})\n\n/** The base of every node. It keeps the facts. */\nabstract class Base extends Data.Class {\n\treadonly #facts: Facts\n\n\tconstructor(props: object, facts: Facts) {\n\t\tsuper(props as never)\n\t\tthis.#facts = facts\n\t}\n\n\t/** The number of nodes, with the rules and their conditions. */\n\tget size(): number {\n\t\treturn this.#facts.size\n\t}\n\n\t/** The number of levels of the tree. */\n\tget depth(): number {\n\t\treturn this.#facts.depth\n\t}\n\n\t/** A rule of the tree has a predicate. */\n\tget opaque(): boolean {\n\t\treturn this.#facts.opaque\n\t}\n}\n\n/**\n * The condition of a rule. A value that is not a node counts as one node\n * here: the binding of the rule gives the issue for it, before any walk.\n */\nconst whenOf = (rule: Rule.Rule): Nodes.Node | undefined =>\n\tNodes.isNode(rule.when) ? rule.when : undefined\n\n/** A list of rules with a combining algorithm. */\nexport class Rules extends Base {\n\treadonly _tag = \"Rules\"\n\tdeclare readonly algorithm: Algorithm\n\tdeclare readonly rules: ReadonlyArray<Rule.Rule>\n\tconstructor(props: {\n\t\treadonly algorithm: Algorithm\n\t\treadonly rules: ReadonlyArray<Rule.Rule>\n\t}) {\n\t\tif (!isAlgorithm(props.algorithm)) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${String(props.algorithm)}\" is not a combining algorithm`,\n\t\t\t)\n\t\t}\n\t\tconst rules = Object.freeze(Array.from(props.rules))\n\t\t// A value that is not a rule, such as a copy of a rule made with a\n\t\t// spread, may keep the type fields of another rule, and it does not\n\t\t// work in a generator. It is a mistake in the code, so it is a defect.\n\t\tif (!Arr.every(rules, Rule.isRule)) {\n\t\t\tthrow new Error(\"A value in the list is not a rule\")\n\t\t}\n\t\tconst conditions = Arr.map(rules, whenOf)\n\t\tsuper(\n\t\t\t{ algorithm: props.algorithm, rules },\n\t\t\tchecked({\n\t\t\t\tsize: Arr.reduce(\n\t\t\t\t\tconditions,\n\t\t\t\t\t1,\n\t\t\t\t\t// A condition counts its nodes and the values of its tests.\n\t\t\t\t\t(total, when) => total + 1 + (when?.weight ?? 0),\n\t\t\t\t),\n\t\t\t\tdepth: 1,\n\t\t\t\topaque: Arr.some(conditions, (when) => when?.opaque ?? false),\n\t\t\t}),\n\t\t)\n\t}\n}\n\nconst checkChildren = (\n\tpolicies: ReadonlyArray<unknown>,\n): ReadonlyArray<Node> => {\n\tconst children = Array.from(policies)\n\tif (!Arr.every(children, isNode)) {\n\t\tthrow new Error(\"A child of a policy tree is not a node of a policy\")\n\t}\n\treturn Object.freeze(children)\n}\n\n/** Every policy must allow, and one deny is enough to deny. */\nexport class All extends Base {\n\treadonly _tag = \"All\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** One allow is enough. Without an allow, one deny gives a deny. */\nexport class Any extends Base {\n\treadonly _tag = \"Any\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** The first policy that allows or denies gives the answer. */\nexport class FirstOf extends Base {\n\treadonly _tag = \"FirstOf\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** A deny from `cap` always wins. An allow from `cap` does nothing. */\nexport class Ceiling extends Base {\n\treadonly _tag = \"Ceiling\"\n\tdeclare readonly cap: Node\n\tdeclare readonly policy: Node\n\tconstructor(props: { readonly cap: Node; readonly policy: Node }) {\n\t\tconst [cap, policy] = checkChildren([props.cap, props.policy])\n\t\tsuper({ cap, policy }, factsOf([cap!, policy!]))\n\t}\n}\n\n/** A node of a policy tree. */\nexport type Node = Rules | All | Any | FirstOf | Ceiling\n\n/** Tests if a value is a node of a policy tree. */\nexport const isNode = (u: unknown): u is Node => u instanceof Base\n\n/**\n * The rules of a tree, in order, once for each place. A walk that also goes\n * into the condition of each rule takes the rules from this list first, so it\n * does not go down the levels of the tree and of a condition at once.\n */\nexport const rulesOf = (root: Node): ReadonlyArray<Rule.Rule> =>\n\t// The walk takes the steps of a trampoline, so a deep tree does not fill\n\t// the stack.\n\tTrampoline.run(root.depth, (R) => {\n\t\tconst go = (node: Node): Trampoline.Step<ReadonlyArray<Rule.Rule>> => {\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed(node.rules)\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\":\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(node.policies, (child) => R.suspend(() => go(child))),\n\t\t\t\t\t\tArr.flatten,\n\t\t\t\t\t)\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach([node.cap, node.policy], (child) =>\n\t\t\t\t\t\t\tR.suspend(() => go(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tArr.flatten,\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn go(root)\n\t})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.d.ts","names":["Policy.Any","Policy.Node","Domain.Any"],"sources":["../../src/internal/policy.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"policy.d.ts","names":["Policy.Any","Policy.Node","Domain.Any"],"sources":["../../src/internal/policy.ts"],"mappings":";;;;;;KAwBK,MAAMA;KACN,OAAOC;;;;;qBAOC,WACZ,UAAUC,aACV,mBACS,KAAK;WACL,QAAQ;WACR,MAAM;;;;4BAOmB;;;;;uBAKL,QAAQ,MAAM;EAE3C;;SAKO,GAAG,aAAa,KAAK;;;;;;;;;;;;;qBA+BhB,oBAAoB"}
|