@nmnmcc/ability 2.1.3 → 2.1.5

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.
@@ -207,12 +207,25 @@ const decoder = (budget: Budget): ((part: Part) => Node) => {
207
207
  throw problemAt(at, "A condition node must be an object")
208
208
  }
209
209
  spend(budget, 1, at)
210
- count.nodes += 1
211
- if (count.nodes > budget.limits.conditionNodes) {
212
- throw problemAt(
213
- at,
214
- `A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`,
215
- )
210
+ const counted = (more: number): void => {
211
+ count.nodes += more
212
+ if (count.nodes > budget.limits.conditionNodes) {
213
+ throw problemAt(
214
+ at,
215
+ `A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`,
216
+ )
217
+ }
218
+ }
219
+ counted(1)
220
+ // The values of an `In` or an `Under` test count in the weight of the
221
+ // condition. The length of the list counts before the decoder reads
222
+ // any value, so a very long list stops before it takes memory.
223
+ const operands = (name: string): ReadonlyArray<Nodes.Operand> => {
224
+ const list = read(input, name, at)
225
+ if (Array.isArray(list)) {
226
+ counted(list.length)
227
+ }
228
+ return decodeOperands(input, name, at, budget)
216
229
  }
217
230
  // A node past a limit, such as a field path with too many parts, is a
218
231
  // problem at its place. The constructor checks the ceilings, and the
@@ -276,7 +289,7 @@ const decoder = (budget: Budget): ((part: Part) => Node) => {
276
289
  () =>
277
290
  new Nodes.In({
278
291
  field: readPath(input, "field", at, budget),
279
- values: decodeOperands(input, "values", at, budget),
292
+ values: operands("values"),
280
293
  }),
281
294
  ),
282
295
  )
@@ -292,7 +305,7 @@ const decoder = (budget: Budget): ((part: Part) => Node) => {
292
305
  make(
293
306
  () =>
294
307
  new Nodes.Under({
295
- prefix: decodeOperands(input, "prefix", at, budget),
308
+ prefix: operands("prefix"),
296
309
  }),
297
310
  ),
298
311
  )
@@ -287,14 +287,30 @@ const decodeMeta = (
287
287
  (found) =>
288
288
  found === undefined
289
289
  ? Effect.void
290
- : Effect.mapError(
291
- Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value),
292
- (error) =>
293
- new Json.Problem({
294
- path: Place.pathOf(at),
295
- message: error.message,
296
- issue: error.issue,
297
- }),
290
+ : Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(
291
+ Effect.mapError(
292
+ (error) =>
293
+ new Json.Problem({
294
+ path: Place.pathOf(at),
295
+ message: error.message,
296
+ issue: error.issue,
297
+ }),
298
+ ),
299
+ // The decoder of a Schema in Effect calls itself for each level of
300
+ // the value, so a deep value that the budget takes, as 5,000 levels
301
+ // of a recursive `meta` Schema, fills its stack. That is a defect
302
+ // of the decoder, not of the policy, so it is a problem of the
303
+ // input here, and the policy does not load.
304
+ Effect.catchDefect((defect) =>
305
+ defect instanceof RangeError
306
+ ? Effect.fail(
307
+ Json.problemAt(
308
+ at,
309
+ "The meta of the rule has too many levels for the Schema of meta",
310
+ ),
311
+ )
312
+ : Effect.die(defect),
313
+ ),
298
314
  ),
299
315
  )
300
316
  }