UNPKG

@samchon/openapi

Version:

OpenAPI definitions and converters for 'typia' and 'nestia'.

532 lines (531 loc) 25.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenApiTypeCheckerBase = void 0; const AccessorUtil_1 = require("../AccessorUtil"); const MapUtil_1 = require("../MapUtil"); const JsonDescriptionUtil_1 = require("./JsonDescriptionUtil"); /** * @internal */ var OpenApiTypeCheckerBase; (function (OpenApiTypeCheckerBase) { /* ----------------------------------------------------------- TYPE CHECKERS ----------------------------------------------------------- */ OpenApiTypeCheckerBase.isNull = (schema) => schema.type === "null"; OpenApiTypeCheckerBase.isUnknown = (schema) => schema.type === undefined && !OpenApiTypeCheckerBase.isConstant(schema) && !OpenApiTypeCheckerBase.isOneOf(schema) && !OpenApiTypeCheckerBase.isReference(schema); OpenApiTypeCheckerBase.isConstant = (schema) => schema.const !== undefined; OpenApiTypeCheckerBase.isBoolean = (schema) => schema.type === "boolean"; OpenApiTypeCheckerBase.isInteger = (schema) => schema.type === "integer"; OpenApiTypeCheckerBase.isNumber = (schema) => schema.type === "number"; OpenApiTypeCheckerBase.isString = (schema) => schema.type === "string"; OpenApiTypeCheckerBase.isArray = (schema) => schema.type === "array" && schema.items !== undefined; OpenApiTypeCheckerBase.isTuple = (schema) => schema.type === "array" && schema.prefixItems !== undefined; OpenApiTypeCheckerBase.isObject = (schema) => schema.type === "object"; OpenApiTypeCheckerBase.isReference = (schema) => schema.$ref !== undefined; OpenApiTypeCheckerBase.isOneOf = (schema) => schema.oneOf !== undefined; OpenApiTypeCheckerBase.isRecursiveReference = (props) => { if (OpenApiTypeCheckerBase.isReference(props.schema) === false) return false; const current = props.schema.$ref.split(props.prefix)[1]; let counter = 0; OpenApiTypeCheckerBase.visit({ prefix: props.prefix, components: props.components, schema: props.schema, closure: (schema) => { if (OpenApiTypeCheckerBase.isReference(schema)) { const next = schema.$ref.split(props.prefix)[1]; if (current === next) ++counter; } }, }); return counter > 1; }; /* ----------------------------------------------------------- OPERATORS ----------------------------------------------------------- */ OpenApiTypeCheckerBase.unreference = (props) => { var _a, _b; const reasons = []; const result = unreferenceSchema({ prefix: props.prefix, refAccessor: (_a = props.refAccessor) !== null && _a !== void 0 ? _a : `$input.${props.prefix .substring(2) .split("/") .filter((s) => !!s.length) .join(".")}`, accessor: (_b = props.accessor) !== null && _b !== void 0 ? _b : "$input.schema", components: props.components, schema: props.schema, reasons, }); if (result === null) return { success: false, error: { method: props.method, message: `failed to unreference due to unable to find.`, reasons, }, }; return { success: true, value: result, }; }; OpenApiTypeCheckerBase.escape = (props) => { var _a, _b; const reasons = []; const result = escapeSchema(Object.assign(Object.assign({}, props), { reasons, visited: new Map(), accessor: (_a = props.accessor) !== null && _a !== void 0 ? _a : "$input.schema", refAccessor: (_b = props.refAccessor) !== null && _b !== void 0 ? _b : AccessorUtil_1.AccessorUtil.reference(props.prefix) })) || null; if (result === null) return { success: false, error: { method: props.method, message: `failed to escape some reference type(s) due to unable to find${Number(props.recursive) === 0 ? " or recursive relationship" : ""}.`, reasons, }, }; return { success: true, value: result, }; }; OpenApiTypeCheckerBase.visit = (props) => { var _a, _b; const already = new Set(); const refAccessor = (_a = props.refAccessor) !== null && _a !== void 0 ? _a : `$input.${AccessorUtil_1.AccessorUtil.reference(props.prefix)}`; const next = (schema, accessor) => { var _a, _b, _c; props.closure(schema, accessor); if (OpenApiTypeCheckerBase.isReference(schema)) { const key = schema.$ref.split(props.prefix).pop(); if (already.has(key) === true) return; already.add(key); const found = (_a = props.components.schemas) === null || _a === void 0 ? void 0 : _a[key]; if (found !== undefined) next(found, `${refAccessor}[${JSON.stringify(key)}]`); } else if (OpenApiTypeCheckerBase.isOneOf(schema)) schema.oneOf.forEach((s, i) => next(s, `${accessor}.oneOf[${i}]`)); else if (OpenApiTypeCheckerBase.isObject(schema)) { for (const [key, value] of Object.entries((_b = schema.properties) !== null && _b !== void 0 ? _b : {})) next(value, `${accessor}.properties[${JSON.stringify(key)}]`); if (typeof schema.additionalProperties === "object" && schema.additionalProperties !== null) next(schema.additionalProperties, `${accessor}.additionalProperties`); } else if (OpenApiTypeCheckerBase.isArray(schema)) next(schema.items, `${accessor}.items`); else if (OpenApiTypeCheckerBase.isTuple(schema)) { ((_c = schema.prefixItems) !== null && _c !== void 0 ? _c : []).forEach((s, i) => next(s, `${accessor}.prefixItems[${i}]`)); if (typeof schema.additionalItems === "object" && schema.additionalItems !== null) next(schema.additionalItems, `${accessor}.additionalItems`); } }; next(props.schema, (_b = props.accessor) !== null && _b !== void 0 ? _b : "$input.schema"); }; OpenApiTypeCheckerBase.covers = (props) => coverStation({ prefix: props.prefix, components: props.components, x: props.x, y: props.y, visited: new Map(), }); const unreferenceSchema = (props) => { var _a; if (OpenApiTypeCheckerBase.isReference(props.schema) === false) return props.schema; const key = props.schema.$ref.split(props.prefix).pop(); const found = (_a = props.components.schemas) === null || _a === void 0 ? void 0 : _a[key]; if (found === undefined) { props.reasons.push({ schema: props.schema, accessor: props.accessor, message: `unable to find reference type ${JSON.stringify(key)}.`, }); return null; } else if (OpenApiTypeCheckerBase.isReference(found) === false) return found; else if (props.first === key) { props.reasons.push({ schema: props.schema, accessor: props.accessor, message: `recursive reference type ${JSON.stringify(key)}.`, }); return null; } return unreferenceSchema(Object.assign(Object.assign({}, props), { accessor: `${props.refAccessor}[${JSON.stringify(key)}]`, first: key })); }; const escapeSchema = (props) => { var _a, _b, _c, _d; if (OpenApiTypeCheckerBase.isReference(props.schema)) { // REFERENCE const key = props.schema.$ref.split(props.prefix)[1]; const target = (_a = props.components.schemas) === null || _a === void 0 ? void 0 : _a[key]; if (target === undefined) { props.reasons.push({ schema: props.schema, accessor: props.accessor, message: `unable to find reference type ${JSON.stringify(key)}.`, }); return null; } else if (props.visited.has(key) === true) { if (props.recursive === false) return null; const depth = props.visited.get(key); if (depth > props.recursive) { if (props.recursive === 0) { props.reasons.push({ schema: props.schema, accessor: props.accessor, message: `recursive reference type ${JSON.stringify(key)}.`, }); return null; } return undefined; } props.visited.set(key, depth + 1); const res = escapeSchema(Object.assign(Object.assign({}, props), { schema: target, accessor: `${props.refAccessor}[${JSON.stringify(key)}]` })); return res ? Object.assign(Object.assign({}, res), { description: JsonDescriptionUtil_1.JsonDescriptionUtil.cascade({ prefix: props.prefix, components: props.components, schema: props.schema, escape: true, }) }) : res; } else { const res = escapeSchema(Object.assign(Object.assign({}, props), { schema: target, accessor: `${props.refAccessor}[${JSON.stringify(key)}]`, visited: new Map([...props.visited, [key, 1]]) })); return res ? Object.assign(Object.assign({}, res), { description: JsonDescriptionUtil_1.JsonDescriptionUtil.cascade({ prefix: props.prefix, components: props.components, schema: props.schema, escape: true, }) }) : res; } } else if (OpenApiTypeCheckerBase.isOneOf(props.schema)) { // UNION const elements = props.schema.oneOf.map((s, i) => escapeSchema(Object.assign(Object.assign({}, props), { schema: s, accessor: `${props.accessor}.oneOf[${i}]` }))); if (elements.some((v) => v === null)) return null; const filtered = elements.filter((v) => v !== undefined); if (filtered.length === 0) return undefined; return Object.assign(Object.assign({}, props.schema), { oneOf: filtered .map((v) => flatSchema({ prefix: props.prefix, components: props.components, schema: v, })) .flat() }); } else if (OpenApiTypeCheckerBase.isObject(props.schema)) { // OBJECT const object = props.schema; const properties = Object.entries((_b = object.properties) !== null && _b !== void 0 ? _b : {}).map(([k, s]) => [ k, escapeSchema(Object.assign(Object.assign({}, props), { schema: s, visited: props.visited, accessor: `${props.accessor}.properties[${JSON.stringify(k)}]` })), ]); const additionalProperties = object.additionalProperties ? typeof object.additionalProperties === "object" && object.additionalProperties !== null ? escapeSchema(Object.assign(Object.assign({}, props), { schema: object.additionalProperties, accessor: `${props.accessor}.additionalProperties` })) : object.additionalProperties : false; if (properties.some(([_k, v]) => v === null) || additionalProperties === null) return null; else if (properties.some(([k, v]) => { var _a; return v === undefined && ((_a = object.required) === null || _a === void 0 ? void 0 : _a.includes(k)) === true; }) === true) return undefined; return Object.assign(Object.assign({}, object), { properties: Object.fromEntries(properties.filter(([_k, v]) => v !== undefined)), additionalProperties: additionalProperties !== null && additionalProperties !== void 0 ? additionalProperties : false, required: (_d = (_c = object.required) === null || _c === void 0 ? void 0 : _c.filter((k) => properties.some(([key, value]) => key === k && value !== undefined))) !== null && _d !== void 0 ? _d : [] }); } else if (OpenApiTypeCheckerBase.isTuple(props.schema)) { // TUPLE const elements = props.schema.prefixItems.map((s, i) => escapeSchema(Object.assign(Object.assign({}, props), { schema: s, accessor: `${props.accessor}.prefixItems[${i}]` }))); const additionalItems = props.schema.additionalItems ? typeof props.schema.additionalItems === "object" && props.schema.additionalItems !== null ? escapeSchema(Object.assign(Object.assign({}, props), { schema: props.schema.additionalItems, accessor: `${props.accessor}.additionalItems` })) : props.schema.additionalItems : false; if (elements.some((v) => v === null) || additionalItems === null) return null; else if (elements.some((v) => v === undefined)) return undefined; return Object.assign(Object.assign({}, props.schema), { prefixItems: elements, additionalItems: additionalItems !== null && additionalItems !== void 0 ? additionalItems : false }); } else if (OpenApiTypeCheckerBase.isArray(props.schema)) { // ARRAY const items = escapeSchema(Object.assign(Object.assign({}, props), { schema: props.schema.items, accessor: `${props.accessor}.items` })); if (items === null) return null; else if (items === undefined) return Object.assign(Object.assign({}, props.schema), { minItems: undefined, maxItems: 0, items: {} }); return Object.assign(Object.assign({}, props.schema), { items: items }); } return props.schema; }; const coverStation = (p) => { var _a; const cache = (_a = p.visited.get(p.x)) === null || _a === void 0 ? void 0 : _a.get(p.y); if (cache !== undefined) return cache; // FOR RECURSIVE CASE const nested = MapUtil_1.MapUtil.take(p.visited)(p.x)(() => new Map()); nested.set(p.y, true); // COMPUTE IT const result = coverSchema(p); nested.set(p.y, result); return result; }; const coverSchema = (p) => { // CHECK EQUALITY if (p.x === p.y) return true; else if (OpenApiTypeCheckerBase.isReference(p.x) && OpenApiTypeCheckerBase.isReference(p.y) && p.x.$ref === p.y.$ref) return true; // COMPARE WITH FLATTENING const alpha = flatSchema({ prefix: p.prefix, components: p.components, schema: p.x, }); const beta = flatSchema({ prefix: p.prefix, components: p.components, schema: p.y, }); if (alpha.some((x) => OpenApiTypeCheckerBase.isUnknown(x))) return true; else if (beta.some((x) => OpenApiTypeCheckerBase.isUnknown(x))) return false; return beta.every((b) => alpha.some((a) => coverEscapedSchema({ prefix: p.prefix, components: p.components, visited: p.visited, x: a, y: b, }))); }; const coverEscapedSchema = (p) => { // CHECK EQUALITY if (p.x === p.y) return true; else if (OpenApiTypeCheckerBase.isUnknown(p.x)) return true; else if (OpenApiTypeCheckerBase.isUnknown(p.y)) return false; else if (OpenApiTypeCheckerBase.isNull(p.x)) return OpenApiTypeCheckerBase.isNull(p.y); // ATOMIC CASE else if (OpenApiTypeCheckerBase.isConstant(p.x)) return OpenApiTypeCheckerBase.isConstant(p.y) && p.x.const === p.y.const; else if (OpenApiTypeCheckerBase.isBoolean(p.x)) return (OpenApiTypeCheckerBase.isBoolean(p.y) || (OpenApiTypeCheckerBase.isConstant(p.y) && typeof p.y.const === "boolean")); else if (OpenApiTypeCheckerBase.isInteger(p.x)) return (OpenApiTypeCheckerBase.isInteger(p.y) || OpenApiTypeCheckerBase.isConstant(p.y)) && coverInteger(p.x, p.y); else if (OpenApiTypeCheckerBase.isNumber(p.x)) return ((OpenApiTypeCheckerBase.isConstant(p.y) || OpenApiTypeCheckerBase.isInteger(p.y) || OpenApiTypeCheckerBase.isNumber(p.y)) && coverNumber(p.x, p.y)); else if (OpenApiTypeCheckerBase.isString(p.x)) return (OpenApiTypeCheckerBase.isConstant(p.y) || OpenApiTypeCheckerBase.isString(p.y)) && coverString(p.x, p.y); // INSTANCE CASE else if (OpenApiTypeCheckerBase.isArray(p.x)) return ((OpenApiTypeCheckerBase.isArray(p.y) || OpenApiTypeCheckerBase.isTuple(p.y)) && coverArray({ prefix: p.prefix, components: p.components, visited: p.visited, x: p.x, y: p.y, })); else if (OpenApiTypeCheckerBase.isObject(p.x)) return (OpenApiTypeCheckerBase.isObject(p.y) && coverObject({ prefix: p.prefix, components: p.components, visited: p.visited, x: p.x, y: p.y, })); else if (OpenApiTypeCheckerBase.isReference(p.x)) return OpenApiTypeCheckerBase.isReference(p.y) && p.x.$ref === p.y.$ref; return false; }; const coverArray = (p) => { if (OpenApiTypeCheckerBase.isTuple(p.y)) return (p.y.prefixItems.every((v) => coverStation({ prefix: p.prefix, components: p.components, visited: p.visited, x: p.x.items, y: v, })) && (p.y.additionalItems === undefined || (typeof p.y.additionalItems === "object" && coverStation({ prefix: p.prefix, components: p.components, visited: p.visited, x: p.x.items, y: p.y.additionalItems, })))); else if (!(p.x.minItems === undefined || (p.y.minItems !== undefined && p.x.minItems <= p.y.minItems))) return false; else if (!(p.x.maxItems === undefined || (p.y.maxItems !== undefined && p.x.maxItems >= p.y.maxItems))) return false; return coverStation({ prefix: p.prefix, components: p.components, visited: p.visited, x: p.x.items, y: p.y.items, }); }; const coverObject = (p) => { var _a; if (!p.x.additionalProperties && !!p.y.additionalProperties) return false; else if (!!p.x.additionalProperties && !!p.y.additionalProperties && ((typeof p.x.additionalProperties === "object" && p.y.additionalProperties === true) || (typeof p.x.additionalProperties === "object" && typeof p.y.additionalProperties === "object" && !coverStation({ prefix: p.prefix, components: p.components, visited: p.visited, x: p.x.additionalProperties, y: p.y.additionalProperties, })))) return false; return Object.entries((_a = p.y.properties) !== null && _a !== void 0 ? _a : {}).every(([key, b]) => { var _a, _b, _c, _d; const a = (_a = p.x.properties) === null || _a === void 0 ? void 0 : _a[key]; if (a === undefined) return false; else if (((_b = p.x.required) === null || _b === void 0 ? void 0 : _b.includes(key)) === true && ((_d = (_c = p.y.required) === null || _c === void 0 ? void 0 : _c.includes(key)) !== null && _d !== void 0 ? _d : false) === false) return false; return coverStation({ prefix: p.prefix, components: p.components, visited: p.visited, x: a, y: b, }); }); }; const coverInteger = (x, y) => { if (OpenApiTypeCheckerBase.isConstant(y)) return typeof y.const === "number" && Number.isInteger(y.const); return [ x.type === y.type, x.minimum === undefined || (y.minimum !== undefined && x.minimum <= y.minimum), x.maximum === undefined || (y.maximum !== undefined && x.maximum >= y.maximum), x.exclusiveMinimum !== true || x.minimum === undefined || (y.minimum !== undefined && (y.exclusiveMinimum === true || x.minimum < y.minimum)), x.exclusiveMaximum !== true || x.maximum === undefined || (y.maximum !== undefined && (y.exclusiveMaximum === true || x.maximum > y.maximum)), x.multipleOf === undefined || (y.multipleOf !== undefined && y.multipleOf / x.multipleOf === Math.floor(y.multipleOf / x.multipleOf)), ].every((v) => v); }; const coverNumber = (x, y) => { if (OpenApiTypeCheckerBase.isConstant(y)) return typeof y.const === "number"; return [ x.type === y.type || (x.type === "number" && y.type === "integer"), x.minimum === undefined || (y.minimum !== undefined && x.minimum <= y.minimum), x.maximum === undefined || (y.maximum !== undefined && x.maximum >= y.maximum), x.exclusiveMinimum !== true || x.minimum === undefined || (y.minimum !== undefined && (y.exclusiveMinimum === true || x.minimum < y.minimum)), x.exclusiveMaximum !== true || x.maximum === undefined || (y.maximum !== undefined && (y.exclusiveMaximum === true || x.maximum > y.maximum)), x.multipleOf === undefined || (y.multipleOf !== undefined && y.multipleOf / x.multipleOf === Math.floor(y.multipleOf / x.multipleOf)), ].every((v) => v); }; const coverString = (x, y) => { if (OpenApiTypeCheckerBase.isConstant(y)) return typeof y.const === "string"; return [ x.format === undefined || (y.format !== undefined && coverFormat(x.format, y.format)), x.pattern === undefined || x.pattern === y.pattern, x.minLength === undefined || (y.minLength !== undefined && x.minLength <= y.minLength), x.maxLength === undefined || (y.maxLength !== undefined && x.maxLength >= y.maxLength), ].every((v) => v); }; const coverFormat = (x, y) => x === y || (x === "idn-email" && y === "email") || (x === "idn-hostname" && y === "hostname") || (["uri", "iri"].includes(x) && y === "url") || (x === "iri" && y === "uri") || (x === "iri-reference" && y === "uri-reference"); const flatSchema = (props) => { const schema = escapeReferenceOfFlatSchema(props); if (OpenApiTypeCheckerBase.isOneOf(schema)) return schema.oneOf .map((v) => flatSchema({ prefix: props.prefix, components: props.components, schema: v, })) .flat(); return [schema]; }; const escapeReferenceOfFlatSchema = (props) => { var _a, _b; if (OpenApiTypeCheckerBase.isReference(props.schema) === false) return props.schema; const key = props.schema.$ref.replace(props.prefix, ""); const found = escapeReferenceOfFlatSchema({ prefix: props.prefix, components: props.components, schema: (_b = (_a = props.components.schemas) === null || _a === void 0 ? void 0 : _a[key]) !== null && _b !== void 0 ? _b : {}, }); if (found === undefined) throw new Error(`Reference type not found: ${JSON.stringify(props.schema.$ref)}`); return escapeReferenceOfFlatSchema({ prefix: props.prefix, components: props.components, schema: found, }); }; })(OpenApiTypeCheckerBase || (exports.OpenApiTypeCheckerBase = OpenApiTypeCheckerBase = {}));