core/validators/section.validator.ts
core/validators/section.validator.ts is a file in GovLab Quality. 74 lines of code and 17 definitions.
import { loadInstallRegistry } from "#core/loaders/dependency.loader";
export const MUST_BE_OBJECT = "must be an object";
export const isObject = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === "object" && !Array.isArray(value);
export const isStringArray = (value: unknown): boolean =>
Array.isArray(value) && value.every((item) => typeof item === "string");
export const requireObjectField = (record: Record<string, unknown>, key: string): string[] =>
Object.hasOwn(record, key) && !isObject(record[key]) ? [`${key} must be an object`] : [];
export const stringFieldErrors = (value: Record<string, unknown>, keys: string[]): string[] =>
keys
.filter((key) => Object.hasOwn(value, key) && typeof value[key] !== "string")
.map((key) => `${key} must be a string`);
export const stringArrayFieldError = (value: Record<string, unknown>, key: string, message: string): string[] =>
Object.hasOwn(value, key) && !isStringArray(value[key]) ? [message] : [];
export const booleanFieldError = (value: Record<string, unknown>, key: string, message: string): string[] =>
Object.hasOwn(value, key) && typeof value[key] !== "boolean" ? [message] : [];
const stringItems = (value: unknown): string[] | null => {
if (!Array.isArray(value)) {
return null;
}
const strings = value.filter((item): item is string => typeof item === "string");
return strings.length === value.length ? strings : null;
};
export const validateEcosystems = (value: unknown): string[] => {
const items = stringItems(value);
if (items === null) {
return ["ecosystems must be an array of ecosystem-name strings"];
}
const known = new Set(loadInstallRegistry().records.map((record) => record.ecosystem));
return items
.filter((ecosystem) => !known.has(ecosystem))
.map((ecosystem) => `ecosystems: "${ecosystem}" is not a known ecosystem`);
};
export const validateOwners = (owners: unknown): string[] => {
if (!isObject(owners)) {
return ["owners must be an object of surface → { language: tool }"];
}
return Object.entries(owners).flatMap(([surface, langMap]) => {
if (!isObject(langMap)) {
return [`owners.${surface} must be an object of { language: tool }`];
}
return Object.entries(langMap)
.filter(([, tool]) => typeof tool !== "string")
.map(([lang]) => `owners.${surface}.${lang} must be a tool name (string)`);
});
};
const blockExclusionErrors = (entry: unknown, index: number): string[] => {
if (!isObject(entry)) {
return [`blockExclusions[${String(index)}] must be an object`];
}
const errors: string[] = [];
if (typeof entry["file"] !== "string") {
errors.push(`blockExclusions[${String(index)}].file must be a string path`);
}
if (typeof entry["rule"] !== "string") {
errors.push(`blockExclusions[${String(index)}].rule must be a string rule id`);
}
const { functions } = entry;
if (!isStringArray(functions) || (Array.isArray(functions) && functions.length === 0)) {
errors.push(
`blockExclusions[${String(index)}].functions must name at least one function block (array of strings)`,
);
}
return errors;
};
export const validateBlockExclusions = (value: unknown): string[] => {
if (value === undefined) {
return [];
}
if (!Array.isArray(value)) {
return ["blockExclusions must be an array of { file, rule, functions }"];
}
return value.flatMap((entry, index) => blockExclusionErrors(entry, index));
};