69 lines
3.0 KiB
TypeScript
69 lines
3.0 KiB
TypeScript
// Responsibility: validate the independently YAML-owned Workbench Kafka debug stream.
|
|
// This capability is intentionally separate from the five product realtime capabilities.
|
|
|
|
export const WORKBENCH_KAFKA_DEBUG_ENVS = Object.freeze({
|
|
enabled: "HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED",
|
|
topic: "HWLAB_KAFKA_HWLAB_DEBUG_EVENT_TOPIC",
|
|
groupIdPrefix: "HWLAB_KAFKA_HWLAB_DEBUG_GROUP_PREFIX",
|
|
replayLimit: "HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_LIMIT",
|
|
replayTimeoutMs: "HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_TIMEOUT_MS"
|
|
});
|
|
|
|
export function workbenchKafkaDebugCapability(env = process.env) {
|
|
const enabled = requiredBoolean(env?.[WORKBENCH_KAFKA_DEBUG_ENVS.enabled], WORKBENCH_KAFKA_DEBUG_ENVS.enabled);
|
|
const topic = requiredIsolatedKafkaName(
|
|
env?.[WORKBENCH_KAFKA_DEBUG_ENVS.topic],
|
|
WORKBENCH_KAFKA_DEBUG_ENVS.topic,
|
|
249,
|
|
["hwlab.event.v1", env?.HWLAB_KAFKA_HWLAB_EVENT_TOPIC, env?.HWLAB_KAFKA_EVENT_TOPIC]
|
|
);
|
|
const groupIdPrefix = requiredIsolatedKafkaName(
|
|
env?.[WORKBENCH_KAFKA_DEBUG_ENVS.groupIdPrefix],
|
|
WORKBENCH_KAFKA_DEBUG_ENVS.groupIdPrefix,
|
|
180,
|
|
[env?.HWLAB_KAFKA_HWLAB_EVENT_GROUP_ID]
|
|
);
|
|
const replayLimit = requiredPositiveInteger(env?.[WORKBENCH_KAFKA_DEBUG_ENVS.replayLimit], WORKBENCH_KAFKA_DEBUG_ENVS.replayLimit);
|
|
const replayTimeoutMs = requiredPositiveInteger(env?.[WORKBENCH_KAFKA_DEBUG_ENVS.replayTimeoutMs], WORKBENCH_KAFKA_DEBUG_ENVS.replayTimeoutMs);
|
|
return { enabled, topic, groupIdPrefix, replayLimit, replayTimeoutMs, valuesPrinted: false };
|
|
}
|
|
|
|
function requiredBoolean(value, name) {
|
|
const normalized = String(value ?? "").trim().toLowerCase();
|
|
if (normalized === "true" || normalized === "1") return true;
|
|
if (normalized === "false" || normalized === "0") return false;
|
|
throw capabilityError(`${name} is required and must be explicitly true or false.`, name);
|
|
}
|
|
|
|
function requiredKafkaName(value, name, maxLength) {
|
|
const text = String(value ?? "").trim();
|
|
if (!text || text.length > maxLength || !/^[A-Za-z0-9._-]+$/u.test(text)) {
|
|
throw capabilityError(`${name} is required and must be a valid Kafka identifier.`, name);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
function requiredIsolatedKafkaName(value, name, maxLength, productValues) {
|
|
const text = requiredKafkaName(value, name, maxLength);
|
|
const products = new Set(productValues.map((candidate) => String(candidate ?? "").trim()).filter(Boolean));
|
|
if (products.has(text) || !/(?:^|[.-])debug(?:[.-]|$)/u.test(text)) {
|
|
throw capabilityError(`${name} must identify an isolated debug Kafka resource and must not reuse a product topic or group.`, name);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
function requiredPositiveInteger(value, name) {
|
|
const number = Number(value);
|
|
if (!Number.isInteger(number) || number <= 0) throw capabilityError(`${name} is required and must be a positive integer.`, name);
|
|
return number;
|
|
}
|
|
|
|
function capabilityError(message, envName) {
|
|
return Object.assign(new Error(message), {
|
|
code: "workbench_kafka_debug_capability_invalid",
|
|
envName,
|
|
statusCode: 503,
|
|
valuesRedacted: true
|
|
});
|
|
}
|