core/validators/kind.validator.ts

core/validators/kind.validator.ts is a file in GovLab Context. 104 lines of code and 14 definitions.

import type { ArchRelations, EdgeRelation, KindDefinition, Principle } from "#types/architecture.types";
import { CANONICAL_KINDS, KIND_TAXONOMY } from "#configuration/constants/kind.constants";
import { EDGE_RELATIONS, RELATION_RANGES } from "#configuration/constants/architecture.constants";
import { EMPTY_DEFINITION, nonCanonicalKind } from "#configuration/strings/validation.strings";
import type { KindConsistencyViolation, LexDefect, RelationKindViolation } from "#types/validation.types";
import type { Lexicon, Term } from "#types/lexicon.types";
import type { Faces } from "#types/context.types";
import { slugify } from "#core/converters/identifier.converter";

interface KindEntry {
    id: string;
    name: string;
    aliases?: readonly string[];
    kind: string;
}

interface DefinedRecord {
    id: string;
    kind: string;
    definition: string;
}

const registerKinds = function registerKinds(kindByKey: Map<string, string>, entries: readonly KindEntry[]): void {
    for (const entry of entries) {
        for (const key of [entry.id, entry.name, ...(entry.aliases ?? [])]) {
            kindByKey.set(slugify(key), entry.kind);
        }
    }
};

export const buildKindLookup = function buildKindLookup(faces: Faces): (target: string) => string | null {
    const kindByKey = new Map<string, string>();
    registerKinds(
        kindByKey,
        faces.arch
            .all()
            .map((principle) => ({
                id: principle.id,
                kind: principle.type,
                name: principle.name,
                ...(principle.aliases ? { aliases: principle.aliases } : {}),
            })),
    );
    registerKinds(kindByKey, faces.lex.all());
    return (target) => kindByKey.get(slugify(target)) ?? null;
};

const relationViolations = function relationViolations(
    principle: Principle,
    relation: EdgeRelation,
    kindOf: (target: string) => string | null,
): RelationKindViolation[] {
    const range = RELATION_RANGES[relation];
    return principle[relation].flatMap((target) => {
        const kind = kindOf(target);
        return kind !== null && !range.has(kind)
            ? [{ allowed: [...range], from: principle.id, kind, relation, target }]
            : [];
    });
};

export const relationKindViolationsOf = function relationKindViolationsOf(
    arch: ArchRelations,
    kindOf: (target: string) => string | null,
): RelationKindViolation[] {
    return arch
        .all()
        .flatMap((principle) => EDGE_RELATIONS.flatMap((relation) => relationViolations(principle, relation, kindOf)));
};

const termDefects = function termDefects(term: Term): LexDefect[] {
    return [
        ...(CANONICAL_KINDS.has(term.kind) ? [] : [{ id: term.id, reason: nonCanonicalKind(term.kind) }]),
        ...(term.definition.trim() === "" ? [{ id: term.id, reason: EMPTY_DEFINITION }] : []),
    ];
};

const signatureViolations = function signatureViolations(
    record: DefinedRecord,
    entry: KindDefinition,
): KindConsistencyViolation[] {
    const opening = record.definition.trim().toLowerCase();
    return entry.definitionSignatures
        .filter((signature) => opening.startsWith(signature))
        .map((signature) => ({
            declaredKind: record.kind,
            id: record.id,
            opening: signature,
            signalsKind: entry.kind,
        }));
};

const kindViolationsOf = function kindViolationsOf(record: DefinedRecord): KindConsistencyViolation[] {
    return KIND_TAXONOMY.filter((entry) => entry.kind !== record.kind).flatMap((entry) =>
        signatureViolations(record, entry),
    );
};

export const lexKindIssues = function lexKindIssues(
    lex: Lexicon,
    arch: ArchRelations,
): { lexDefects: LexDefect[]; kindConsistencyViolations: KindConsistencyViolation[] } {
    return {
        kindConsistencyViolations: [
            ...lex.all().flatMap(kindViolationsOf),
            ...arch
                .all()
                .flatMap((principle) =>
                    kindViolationsOf({ definition: principle.definition, id: principle.id, kind: principle.type }),
                ),
        ],
        lexDefects: lex.all().flatMap(termDefects),
    };
};