@samchon/openapi
Version:
OpenAPI definitions and converters for 'typia' and 'nestia'.
532 lines (531 loc) • 25.8 kB
JavaScript
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 = {}));
;