# core/validators/schema.validator.ts

> 78 lines of code and 20 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-validators-schema-validator-ts
Source text: https://banes-lab.com/source/build-scripts/core/validators/schema.validator.ts.txt

Listed in [core/validators](https://banes-lab.com/api/source/build-scripts/core/validators.md), after [core/validators/payload.validator.ts](https://banes-lab.com/source/build-scripts/core/validators/payload.validator.ts.md) and before [core/validators/security.validator.ts](https://banes-lab.com/source/build-scripts/core/validators/security.validator.ts.md).

## Definitions

- `admits` (lexical_declaration, line 25)
- `typesOf` (lexical_declaration, line 18)
- `objectDefects` (lexical_declaration, line 51)
- `instanceDefects` (lexical_declaration, line 67, exported)
- `OBJECT_TYPE` (lexical_declaration, line 10)
- `ARRAY_TYPE` (lexical_declaration, line 11)
- `NUMBER_TYPE` (lexical_declaration, line 12)
- `INTEGER_TYPE` (lexical_declaration, line 13)
- `PATH_SEPARATOR` (lexical_declaration, line 14)
- `ITEM_MARK` (lexical_declaration, line 15)
- `OBJECT_KEYS` (lexical_declaration, line 16)
- `types` (lexical_declaration, line 26)
- `schemaShapeDefects` (lexical_declaration, line 30, exported)
- `own` (lexical_declaration, line 31, exported)
- `nested` (lexical_declaration, line 39, exported)
- `missing` (lexical_declaration, line 56)
- `fields` (lexical_declaration, line 57)
- `declared` (lexical_declaration, line 58)
- `type` (lexical_declaration, line 72, exported)
- `{ items }` (lexical_declaration, line 80, exported)

## Contained in

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

## Uses

- [configuration/strings/catalog.strings.ts](https://banes-lab.com/source/build-scripts/configuration/strings/catalog.strings.ts.md)
- [core/converters/schema.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/schema.converter.ts.md)

## Used by

- [core/converters/markup.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/markup.converter.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-configuration-strings.md)
- [core/converters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-converters.md)
- [core/validators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-validators.md)

## Source

```typescript
import { isObjectValue, typeOf } from "#core/converters/schema.converter";
import {
    schemaShapeDefect,
    schemaTypeDefect,
    schemaUndeclared,
    schemaUnrequired,
} from "#configuration/strings/catalog.strings";
import type { JsonSchema } from "#types/schema.types";

const OBJECT_TYPE = "object";
const ARRAY_TYPE = "array";
const NUMBER_TYPE = "number";
const INTEGER_TYPE = "integer";
const PATH_SEPARATOR = ".";
const ITEM_MARK = "[]";
const OBJECT_KEYS = ["additionalProperties", "properties", "required"] as const;

const typesOf = function typesOf(schema: JsonSchema): readonly string[] {
    if (schema.type === undefined) {
        return [];
    }
    return typeof schema.type === "string" ? [schema.type] : schema.type;
};

const admits = function admits(schema: JsonSchema, type: string): boolean {
    const types = typesOf(schema);
    return types.length === 0 || types.includes(type) || (type === INTEGER_TYPE && types.includes(NUMBER_TYPE));
};

export const schemaShapeDefects = function schemaShapeDefects(schema: JsonSchema, path: string): readonly string[] {
    const own = [
        ...OBJECT_KEYS.filter((key) => schema[key] !== undefined && !admits(schema, OBJECT_TYPE)).map((key) =>
            schemaShapeDefect(path, key, OBJECT_TYPE),
        ),
        ...(schema.items !== undefined && !admits(schema, ARRAY_TYPE)
            ? [schemaShapeDefect(path, "items", ARRAY_TYPE)]
            : []),
    ];
    const nested = [
        ...Object.entries(schema.properties ?? {}).flatMap(([key, child]) =>
            schemaShapeDefects(child, path + PATH_SEPARATOR + key),
        ),
        ...(schema.additionalProperties === undefined
            ? []
            : schemaShapeDefects(schema.additionalProperties, path + PATH_SEPARATOR + OBJECT_TYPE)),
        ...(schema.items === undefined ? [] : schemaShapeDefects(schema.items, path + ITEM_MARK)),
    ];
    return [...own, ...nested];
};

const objectDefects = function objectDefects(
    value: Record<string, unknown>,
    schema: JsonSchema,
    path: string,
): readonly string[] {
    const missing = (schema.required ?? []).filter((key) => !(key in value)).map((key) => schemaUnrequired(path, key));
    const fields = Object.entries(value).flatMap(([key, child]) => {
        const declared = schema.properties?.[key] ?? schema.additionalProperties;
        if (declared === undefined) {
            return schema.properties === undefined ? [] : [schemaUndeclared(path, key)];
        }
        return instanceDefects(child, declared, path + PATH_SEPARATOR + key);
    });
    return [...missing, ...fields];
};

export const instanceDefects = function instanceDefects(
    value: unknown,
    schema: JsonSchema,
    path: string,
): readonly string[] {
    const type = typeOf(value);
    if (!admits(schema, type)) {
        return [schemaTypeDefect(path, type, typesOf(schema))];
    }
    if (isObjectValue(value)) {
        return objectDefects(value, schema, path);
    }
    if (Array.isArray(value) && schema.items !== undefined) {
        const { items } = schema;
        return value.flatMap((item) => instanceDefects(item, items, path + ITEM_MARK));
    }
    return [];
};
```
