# core/validators/section.validator.ts

> 74 lines of code and 17 definitions.

Tree: GovLab Quality
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-validators-section-validator-ts
Source text: https://banes-lab.com/source/quality/core/validators/section.validator.ts.txt

Listed in [core/validators](https://banes-lab.com/api/source/quality/core/validators.md), after [core/validators/filename.validator.ts](https://banes-lab.com/source/quality/core/validators/filename.validator.ts.md) and before [core/validators/surface.validator.ts](https://banes-lab.com/source/quality/core/validators/surface.validator.ts.md).

## Definitions

- `stringArrayFieldError` (lexical_declaration, line 19, exported)
- `requireObjectField` (lexical_declaration, line 11, exported)
- `isObject` (lexical_declaration, line 5, exported)
- `isStringArray` (lexical_declaration, line 8, exported)
- `stringFieldErrors` (lexical_declaration, line 14, exported)
- `validateOwners` (lexical_declaration, line 44, exported)
- `blockExclusionErrors` (lexical_declaration, line 58)
- `validateEcosystems` (lexical_declaration, line 33, exported)
- `validateBlockExclusions` (lexical_declaration, line 78, exported)
- `MUST_BE_OBJECT` (lexical_declaration, line 3, exported)
- `booleanFieldError` (lexical_declaration, line 22, exported)
- `stringItems` (lexical_declaration, line 25)
- `strings` (lexical_declaration, line 29)
- `items` (lexical_declaration, line 34, exported)
- `known` (lexical_declaration, line 38, exported)
- `errors` (lexical_declaration, line 62)
- `{ functions }` (lexical_declaration, line 69)

## Contained in

- [core/validators](https://banes-lab.com/anatomy/quality/folder-quality-core-validators.md)

## Used by

- [core/schemas/config.schema.ts](https://banes-lab.com/source/quality/core/schemas/config.schema.ts.md)
- [core/schemas/eslint.schema.ts](https://banes-lab.com/source/quality/core/schemas/eslint.schema.ts.md)
- [core/schemas/tool.schema.ts](https://banes-lab.com/source/quality/core/schemas/tool.schema.ts.md)

## Linked from

- [core/schemas](https://banes-lab.com/anatomy/quality/folder-quality-core-schemas.md)
- [core/validators](https://banes-lab.com/anatomy/quality/folder-quality-core-validators.md)

## Source

```typescript
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));
};
```
