core/validators/manifest.validator.ts

core/validators/manifest.validator.ts is a file in GovLab Docs. 101 lines of code and 18 definitions.

import {
    COMPUTED_KEYS,
    CORE_KEYS,
    MATURITY,
    RELATIONSHIP_KEYS,
    VISIBILITY_FLAGS,
} from "#configuration/constants/manifest.constants";
import {
    MANIFEST_ERRORS,
    computedField,
    nonEmptyField,
    relationshipShape,
    unknownKey,
    unknownVisibilityKey,
    visibilityFlag,
} from "#configuration/strings/manifest.strings";
import type { Manifest } from "#types/readme.types";
import type { ManifestPlugin } from "#types/manifest.types";
import { isNonEmptyString } from "#core/predicates/readme.predicate";
import { isPlainRecord } from "#core/predicates/record.predicate";

const REQUIRED_TEXT_FIELDS: readonly string[] = ["label", "summary"];
const OPTIONAL_FLAG_TYPES: ReadonlySet<string> = new Set(["boolean", "undefined"]);

const isRelationshipArray = function isRelationshipArray(value: unknown): boolean {
    return (
        Array.isArray(value) &&
        value.every(
            (entry) => isPlainRecord(entry) && isNonEmptyString(entry["package"]) && isNonEmptyString(entry["reason"]),
        )
    );
};

const allowedKeys = function allowedKeys(plugins: readonly ManifestPlugin[]): Set<string> {
    return new Set([...CORE_KEYS, ...plugins.flatMap((plugin) => plugin.section?.keys ?? [])]);
};

const visibilityErrors = function visibilityErrors(manifest: Manifest): string[] {
    const { visibility } = manifest;
    if (visibility === undefined) {
        return [MANIFEST_ERRORS.visibilityRequired];
    }
    if (!isPlainRecord(visibility)) {
        return [MANIFEST_ERRORS.visibilityShape];
    }
    const flagErrors = VISIBILITY_FLAGS.filter((flag) => !OPTIONAL_FLAG_TYPES.has(typeof visibility[flag])).map(
        visibilityFlag,
    );
    const keyErrors = Object.keys(visibility)
        .filter((key) => !VISIBILITY_FLAGS.includes(key))
        .map(unknownVisibilityKey);
    return [...flagErrors, ...keyErrors];
};

const capabilitiesErrors = function capabilitiesErrors(manifest: Manifest): string[] {
    const { capabilities } = manifest;
    if (capabilities === undefined) {
        return [];
    }
    return Array.isArray(capabilities) && capabilities.every(isNonEmptyString) ? [] : [MANIFEST_ERRORS.capabilities];
};

const maturityErrors = function maturityErrors(manifest: Manifest): string[] {
    const { maturity } = manifest;
    return typeof maturity === "string" && MATURITY.has(maturity) ? [] : [MANIFEST_ERRORS.maturity];
};

const ecosystemErrors = function ecosystemErrors(manifest: Manifest): string[] {
    const { ecosystem } = manifest;
    return ecosystem !== undefined && !isNonEmptyString(ecosystem) ? [MANIFEST_ERRORS.ecosystem] : [];
};

const relationshipErrors = function relationshipErrors(manifest: Manifest): string[] {
    return RELATIONSHIP_KEYS.filter(
        (field) => manifest[field] !== undefined && !isRelationshipArray(manifest[field]),
    ).map(relationshipShape);
};

const coreFieldErrors = function coreFieldErrors(manifest: Manifest): string[] {
    return [
        ...REQUIRED_TEXT_FIELDS.filter((field) => !isNonEmptyString(manifest[field])).map(nonEmptyField),
        ...maturityErrors(manifest),
        ...capabilitiesErrors(manifest),
        ...ecosystemErrors(manifest),
        ...relationshipErrors(manifest),
    ];
};

const keyErrors = function keyErrors(manifest: Manifest, plugins: readonly ManifestPlugin[]): string[] {
    const allowed = allowedKeys(plugins);
    return Object.keys(manifest).flatMap((key) => {
        if (COMPUTED_KEYS.has(key)) {
            return [computedField(key)];
        }
        return allowed.has(key) ? [] : [unknownKey(key)];
    });
};

export const validateManifest = function validateManifest(
    manifest: unknown,
    plugins: readonly ManifestPlugin[] = [],
): string[] {
    if (!isPlainRecord(manifest)) {
        return [MANIFEST_ERRORS.missingManifest];
    }
    return [
        ...coreFieldErrors(manifest),
        ...visibilityErrors(manifest),
        ...keyErrors(manifest, plugins),
        ...plugins.flatMap((plugin) => plugin.section?.validate?.(manifest) ?? []),
    ];
};