refactor(config): share bounded decimal setting validation

This commit is contained in:
morluto committed 2026-10-26 16:14:49 +08:00
1 parent 2411d60b5a
commit 91bacc5c14
6 files changed
+54 -89

No files matched your search

+6 -18
View File
@@ -1,4 +1,4 @@
import { z } from "zod";
import { positiveDecimalIntegerSchema } from "./positiveDecimalInteger.js";
import { AnalysisResourceConstraintError } from "../domain/analysisErrorCore.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
@@ -64,24 +64,12 @@ export type BridgePeer = "Hopper" | "Cutter" | "Ghidra";
export const bridgeMaxRequestBytesConstraint = `${BRIDGE_MAX_REQUEST_BYTES_SETTING} must be a positive decimal integer from ${String(MINIMUM_BRIDGE_MAX_REQUEST_BYTES)} to ${String(MAXIMUM_BRIDGE_MAX_REQUEST_BYTES)} bytes; the default is ${String(DEFAULT_BRIDGE_MAX_REQUEST_BYTES)} bytes (16 MiB). A value below ${String(MINIMUM_BRIDGE_MAX_REQUEST_BYTES)} bytes cannot carry the largest admitted symbol-name command, and a larger value is refused so the encoded payload plus its framing LF stays one string under Node's single-string ceiling.`;
const decimalInteger = /^[1-9]\d*$/u;
const withinRequestRange = (value: string): boolean => {
// The pattern check reports a non-decimal value. Refine only bounds integers.
if (!decimalInteger.test(value)) return true;
const parsed = BigInt(value);
return (
parsed >= BigInt(MINIMUM_BRIDGE_MAX_REQUEST_BYTES) &&
parsed <= BigInt(MAXIMUM_BRIDGE_MAX_REQUEST_BYTES)
);
};
/** Optional decimal byte count for the Hopper and Cutter request payload ceiling. */
export const bridgeMaxRequestBytesSchema = z
.string()
.regex(decimalInteger, { error: bridgeMaxRequestBytesConstraint })
.refine(withinRequestRange, { error: bridgeMaxRequestBytesConstraint })
.transform(Number);
export const bridgeMaxRequestBytesSchema = positiveDecimalIntegerSchema(
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
bridgeMaxRequestBytesConstraint,
);
/** Resolve the effective ceiling from one environment value. */
export const parseBridgeMaxRequestBytes = (
+7 -18
View File
@@ -3,7 +3,7 @@ import {
CAPTURE_DECODER_SEMI_SPACE_MIB,
MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB,
} from "../domain/analysisResourceLimits.js";
import { z } from "zod";
import { positiveDecimalIntegerSchema } from "./positiveDecimalInteger.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
import { err, ok, type Result } from "../domain/result.js";
@@ -24,24 +24,13 @@ export const DEFAULT_CAPTURE_DECODER_HEAP: CaptureDecoderHeap = {
export const captureDecoderHeapConstraint = `${CAPTURE_DECODER_OLD_GENERATION_SETTING} must be a decimal integer from ${String(CAPTURE_DECODER_SEMI_SPACE_MIB)} to ${String(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB; the default is ${String(DEFAULT_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB. An old generation below the decoder's ${String(CAPTURE_DECODER_SEMI_SPACE_MIB)} MiB semi-space aborts Node during startup instead of enforcing a limit, and a larger value is refused.`;
const decimalInteger = /^[1-9]\d*$/u;
const withinHeapRange = (value: string): boolean => {
// The pattern check reports a non-decimal value. Refine only bounds integers.
if (!decimalInteger.test(value)) return true;
const parsed = BigInt(value);
return (
parsed >= BigInt(CAPTURE_DECODER_SEMI_SPACE_MIB) &&
parsed <= BigInt(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)
);
};
/** Optional decimal mebibyte count for the HAR/SAZ/qlog old-generation heap. */
export const captureDecoderOldGenerationMiBSchema = z
.string()
.regex(decimalInteger, { error: captureDecoderHeapConstraint })
.refine(withinHeapRange, { error: captureDecoderHeapConstraint })
.transform(Number);
export const captureDecoderOldGenerationMiBSchema =
positiveDecimalIntegerSchema(
CAPTURE_DECODER_SEMI_SPACE_MIB,
MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB,
captureDecoderHeapConstraint,
);
/** Resolve the effective heap from one environment value. */
export const parseCaptureDecoderHeap = (
+6 -20
View File
@@ -1,4 +1,4 @@
import { z } from "zod";
import { positiveDecimalIntegerSchema } from "./positiveDecimalInteger.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
import { err, ok, type Result } from "../domain/result.js";
@@ -69,26 +69,12 @@ const PHASES = [
const phaseConstraint = (phase: McpPhaseBudgetDefinition): string =>
`${phase.setting} must be a positive decimal integer from ${String(MCP_PHASE_BUDGET_FLOOR_MS)} to ${String(MCP_PHASE_BUDGET_MAXIMUM_MS)} ms; the default is ${String(phase.defaultMilliseconds)} ms. Node schedules a delay above that maximum as 1 ms, so a larger value is refused.`;
const decimalInteger = /^[1-9]\d*$/u;
const withinPhaseBudgetRange = (value: string): boolean => {
// The pattern check reports a non-decimal value. Refine only bounds integers.
if (!decimalInteger.test(value)) return true;
const parsed = BigInt(value);
return (
parsed >= BigInt(MCP_PHASE_BUDGET_FLOOR_MS) &&
parsed <= BigInt(MCP_PHASE_BUDGET_MAXIMUM_MS)
const phaseBudgetSchema = (phase: McpPhaseBudgetDefinition) =>
positiveDecimalIntegerSchema(
MCP_PHASE_BUDGET_FLOOR_MS,
MCP_PHASE_BUDGET_MAXIMUM_MS,
phaseConstraint(phase),
);
};
const phaseBudgetSchema = (phase: McpPhaseBudgetDefinition) => {
const constraint = phaseConstraint(phase);
return z
.string()
.regex(decimalInteger, { error: constraint })
.refine(withinPhaseBudgetRange, { error: constraint })
.transform(Number);
};
/** Optional decimal millisecond count for the MCP initialize phase budget. */
export const mcpInitializeBudgetMsSchema = phaseBudgetSchema(PHASES[0]);
+23
View File
@@ -0,0 +1,23 @@
import { z } from "zod";
const decimalInteger = /^[1-9]\d*$/u;
/** Preserve exact decimal bounds before conversion to a runtime number. */
export const positiveDecimalIntegerSchema = (
minimum: number,
maximum: number,
constraint: string,
) =>
z
.string()
.regex(decimalInteger, { error: constraint })
.refine(
(value) => {
// The pattern owns malformed input; range checks only interpret integers.
if (!decimalInteger.test(value)) return true;
const parsed = BigInt(value);
return parsed >= BigInt(minimum) && parsed <= BigInt(maximum);
},
{ error: constraint },
)
.transform(Number);
+6 -15
View File
@@ -2,7 +2,7 @@ import {
DEFAULT_REGEX_MATCHING_DEADLINE_MS,
MAXIMUM_REGEX_MATCHING_DEADLINE_MS,
} from "../domain/analysisResourceLimits.js";
import { z } from "zod";
import { positiveDecimalIntegerSchema } from "./positiveDecimalInteger.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
import { err, ok, type Result } from "../domain/result.js";
@@ -22,21 +22,12 @@ export const DEFAULT_REGEX_MATCHING_DEADLINE: RegexMatchingDeadline = {
export const regexMatchingDeadlineConstraint = `${REGEX_MATCHING_DEADLINE_SETTING} must be a positive decimal integer from 1 to ${String(MAXIMUM_REGEX_MATCHING_DEADLINE_MS)} ms; the default is ${String(DEFAULT_REGEX_MATCHING_DEADLINE_MS)} ms. Node schedules a delay above that maximum as 1 ms, so a larger value is refused.`;
const decimalInteger = /^[1-9]\d*$/u;
const withinDeadlineRange = (value: string): boolean => {
// The pattern check reports a non-decimal value. Refine only bounds integers.
if (!decimalInteger.test(value)) return true;
const parsed = BigInt(value);
return parsed >= 1n && parsed <= BigInt(MAXIMUM_REGEX_MATCHING_DEADLINE_MS);
};
/** Optional decimal millisecond count for the supervised regex matching deadline. */
export const regexMatchingDeadlineMsSchema = z
.string()
.regex(decimalInteger, { error: regexMatchingDeadlineConstraint })
.refine(withinDeadlineRange, { error: regexMatchingDeadlineConstraint })
.transform(Number);
export const regexMatchingDeadlineMsSchema = positiveDecimalIntegerSchema(
1,
MAXIMUM_REGEX_MATCHING_DEADLINE_MS,
regexMatchingDeadlineConstraint,
);
/** Resolve the effective deadline from one environment value. */
export const parseRegexMatchingDeadline = (
+6 -18
View File
@@ -3,7 +3,7 @@ import {
MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB,
MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB,
} from "../domain/analysisResourceLimits.js";
import { z } from "zod";
import { positiveDecimalIntegerSchema } from "./positiveDecimalInteger.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
import { err, ok, type Result } from "../domain/result.js";
@@ -24,24 +24,12 @@ export const DEFAULT_REGEX_WORKER_HEAP_REQUEST: RegexWorkerHeapRequest = {
export const regexWorkerHeapRequestConstraint = `${REGEX_WORKER_HEAP_REQUEST_SETTING} must be a decimal integer from ${String(MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} to ${String(MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB; the default is ${String(DEFAULT_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB. A request below ${String(MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB aborts the worker during startup, and a larger value is refused.`;
const decimalInteger = /^[1-9]\d*$/u;
const withinHeapRange = (value: string): boolean => {
// The pattern check reports a non-decimal value. Refine only bounds integers.
if (!decimalInteger.test(value)) return true;
const parsed = BigInt(value);
return (
parsed >= BigInt(MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB) &&
parsed <= BigInt(MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)
);
};
/** Optional decimal mebibyte count for the regex worker old-generation request. */
export const regexWorkerHeapRequestMiBSchema = z
.string()
.regex(decimalInteger, { error: regexWorkerHeapRequestConstraint })
.refine(withinHeapRange, { error: regexWorkerHeapRequestConstraint })
.transform(Number);
export const regexWorkerHeapRequestMiBSchema = positiveDecimalIntegerSchema(
MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB,
MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB,
regexWorkerHeapRequestConstraint,
);
/** Resolve the effective request from one environment value. */
export const parseRegexWorkerHeapRequest = (