ts / dist / cjs / spec-check.js
504 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.responseSchema = exports.check = void 0;
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
/** How VALUE would be spelled as an OpenAPI type, for problem messages. */
const typeName = (value) => {
    if (value === null || value === undefined)
        return 'null';
    if (Array.isArray(value))
        return 'array';
    if (typeof value === 'number')
        return Number.isInteger(value) ? 'integer' : 'number';
    return typeof value;
};
const typeMatches = (declared, value) => {
    switch (declared) {
        case 'object':
            return isObject(value);
        case 'array':
            return Array.isArray(value);
        case 'string':
            return typeof value === 'string';
        case 'integer':
            return typeof value === 'number' && Number.isInteger(value);
        case 'number':
            return typeof value === 'number';
        case 'boolean':
            return typeof value === 'boolean';
        case 'null':
            return value === null || value === undefined;
        default:
            return true;
    }
};
const show = (value) => JSON.stringify(value) ?? String(value);
const checkNode = (schema, value, path) => {
    const { type, properties, required, items, oneOf, additionalProperties } = schema;
    const allowed = schema.enum ?? ('const' in schema ? [schema.const] : undefined);
    const types = type === undefined ? undefined : typeof type === 'string' ? [type] : type;
    if (oneOf) {
        return oneOf.some((alternative) => (0, exports.check)(alternative, value, path).length === 0)
            ? []
            : [`${path} matches none of oneOf ${show(oneOf.map((a) => a.type))} — got ${typeName(value)}`];
    }
    if (allowed && !allowed.includes(value)) {
        return [`${path} = ${show(value)} is not in the declared enum ${show(allowed)}`];
    }
    if (types && !types.some((t) => typeMatches(t, value))) {
        return [`${path} is declared ${types.join('|')} but the API returned ${typeName(value)}`];
    }
    const problems = [];
    const object = types?.includes('object') && isObject(value) ? value : undefined;
    if (object) {
        for (const key of required ?? []) {
            if (!(key in object))
                problems.push(`${path}.${key} is required by the spec but absent from the response`);
        }
        // the inverse, and the one that finds under-documented responses:
        // additionalProperties:false makes an unlisted field a violation
        if (additionalProperties === false && properties) {
            for (const key of Object.keys(object)) {
                if (!(key in properties))
                    problems.push(`${path}.${key} is returned by the API but absent from the spec`);
            }
        }
    }
    if (properties && isObject(value)) {
        for (const [name, propertySchema] of Object.entries(properties)) {
            if (name in value)
                problems.push(...(0, exports.check)(propertySchema, value[name], `${path}.${name}`));
        }
    }
    // every row, never a sample: a value that appears only in rare rows (an
    // uncommon action type, say) is exactly the one a schema omits
    if (items && Array.isArray(value)) {
        value.forEach((row, i) => problems.push(...(0, exports.check)(items, row, `${path}[${i}]`)));
    }
    return problems;
};
/**
 * Problems found validating VALUE against SCHEMA, as human-readable
 * strings; empty when the response conforms. PATH prefixes each message so
 * a problem can be located in a nested body.
 */
const check = (schema, value, path = '') => schema === undefined || Object.keys(schema).length === 0 ? [] : checkNode(schema, value, path);
exports.check = check;
/** The schema SPEC declares for METHOD PATH's STATUS response, or undefined when it documents no JSON body. */
const responseSchema = (spec, method, path, status) => spec.paths?.[path]?.[method]?.responses?.[String(status)]?.content?.['application/json']?.schema;
exports.responseSchema = responseSchema;

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/ts.git