Files
diegosouzapw--omniroute/src/lib/combos/compositeTiers.ts
T
2026-07-13 13:39:12 +08:00

201 lines
5.3 KiB
TypeScript

import { normalizeComboModels } from "@/lib/combos/steps";
type JsonRecord = Record<string, unknown>;
type ValidationErrorDetail = {
field: string;
message: string;
};
type CompositeTierValidationFailure = {
success: false;
error: {
message: string;
details: ValidationErrorDetail[];
};
};
type CompositeTierValidationSuccess = {
success: true;
};
export type CompositeTierValidationResult =
| CompositeTierValidationFailure
| CompositeTierValidationSuccess;
function isRecord(value: unknown): value is JsonRecord {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function toTrimmedString(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function createFailure(details: ValidationErrorDetail[]): CompositeTierValidationFailure {
return {
success: false,
error: {
message: "Invalid composite tiers",
details,
},
};
}
export function validateCompositeTiersConfig(combo: {
name?: unknown;
models?: unknown;
config?: unknown;
}): CompositeTierValidationResult {
if (!isRecord(combo.config)) {
return { success: true };
}
const compositeTiers = combo.config.compositeTiers;
if (compositeTiers === undefined || compositeTiers === null) {
return { success: true };
}
if (!isRecord(compositeTiers)) {
return createFailure([
{
field: "config.compositeTiers",
message: "compositeTiers must be an object",
},
]);
}
const defaultTier = toTrimmedString(compositeTiers.defaultTier);
const tiers = isRecord(compositeTiers.tiers) ? compositeTiers.tiers : null;
const details: ValidationErrorDetail[] = [];
if (!defaultTier) {
details.push({
field: "config.compositeTiers.defaultTier",
message: "defaultTier is required",
});
}
if (!tiers || Object.keys(tiers).length === 0) {
details.push({
field: "config.compositeTiers.tiers",
message: "tiers must define at least one tier",
});
return createFailure(details);
}
const normalizedSteps = normalizeComboModels(combo.models, {
comboName: toTrimmedString(combo.name),
});
const stepIds = new Set(
normalizedSteps
.map((step) => toTrimmedString(step.id))
.filter((value): value is string => !!value)
);
const tierEntries = new Map<string, { stepId: string; fallbackTier: string | null }>();
const stepIdOwners = new Map<string, string>();
for (const [rawTierName, rawTierValue] of Object.entries(tiers)) {
const tierName = toTrimmedString(rawTierName);
const fieldBase = `config.compositeTiers.tiers.${rawTierName}`;
if (!tierName) {
details.push({
field: fieldBase,
message: "tier name must be a non-empty string",
});
continue;
}
if (!isRecord(rawTierValue)) {
details.push({
field: fieldBase,
message: "tier config must be an object",
});
continue;
}
const stepId = toTrimmedString(rawTierValue.stepId);
const fallbackTier = toTrimmedString(rawTierValue.fallbackTier);
if (!stepId) {
details.push({
field: `${fieldBase}.stepId`,
message: "stepId is required",
});
continue;
}
if (!stepIds.has(stepId)) {
details.push({
field: `${fieldBase}.stepId`,
message: `stepId "${stepId}" does not exist in combo.models`,
});
}
const previousTierForStep = stepIdOwners.get(stepId);
if (previousTierForStep && previousTierForStep !== tierName) {
details.push({
field: `${fieldBase}.stepId`,
message: `stepId "${stepId}" is already assigned to tier "${previousTierForStep}"`,
});
} else {
stepIdOwners.set(stepId, tierName);
}
if (fallbackTier && fallbackTier === tierName) {
details.push({
field: `${fieldBase}.fallbackTier`,
message: "fallbackTier cannot reference the same tier",
});
}
tierEntries.set(tierName, {
stepId,
fallbackTier,
});
}
if (defaultTier && !tierEntries.has(defaultTier)) {
details.push({
field: "config.compositeTiers.defaultTier",
message: `defaultTier "${defaultTier}" does not exist in tiers`,
});
}
for (const [tierName, entry] of tierEntries.entries()) {
if (entry.fallbackTier && !tierEntries.has(entry.fallbackTier)) {
details.push({
field: `config.compositeTiers.tiers.${tierName}.fallbackTier`,
message: `fallbackTier "${entry.fallbackTier}" does not exist in tiers`,
});
}
}
const visitState = new Map<string, "visiting" | "visited">();
function visit(tierName: string, path: string[]) {
const state = visitState.get(tierName);
if (state === "visited") return;
if (state === "visiting") {
details.push({
field: `config.compositeTiers.tiers.${tierName}.fallbackTier`,
message: `fallbackTier cycle detected: ${[...path, tierName].join(" -> ")}`,
});
return;
}
visitState.set(tierName, "visiting");
const fallbackTier = tierEntries.get(tierName)?.fallbackTier;
if (fallbackTier && tierEntries.has(fallbackTier)) {
visit(fallbackTier, [...path, tierName]);
}
visitState.set(tierName, "visited");
}
for (const tierName of tierEntries.keys()) {
visit(tierName, []);
}
return details.length > 0 ? createFailure(details) : { success: true };
}