UNPKG

@samchon/openapi

Version:

Universal OpenAPI to LLM function calling schemas. Transform any Swagger/OpenAPI document into type-safe schemas for OpenAI, Claude, Qwen, and more.

182 lines (181 loc) 9.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenApiOneOfValidator = void 0; const MapUtil_1 = require("../MapUtil"); const OpenApiTypeChecker_1 = require("../OpenApiTypeChecker"); const OpenApiStationValidator_1 = require("./OpenApiStationValidator"); var OpenApiOneOfValidator; (function (OpenApiOneOfValidator) { OpenApiOneOfValidator.validate = (ctx) => { const discriminator = getDiscriminator(ctx); for (const item of discriminator.branches) if (item.predicator(ctx.value)) return OpenApiStationValidator_1.OpenApiStationValidator.validate(Object.assign(Object.assign({}, ctx), { schema: item.schema })); if (discriminator.branches.length !== 0) return OpenApiOneOfValidator.validate(Object.assign(Object.assign({}, ctx), { schema: { oneOf: discriminator.remainders, } })); const matched = discriminator.remainders.find((schema) => OpenApiStationValidator_1.OpenApiStationValidator.validate(Object.assign(Object.assign({}, ctx), { schema, exceptionable: false, equals: false })) === true); if (matched === undefined) return ctx.report(ctx); return ctx.equals === true ? OpenApiStationValidator_1.OpenApiStationValidator.validate(Object.assign(Object.assign({}, ctx), { schema: matched })) : true; }; const getDiscriminator = (ctx) => { const resolvedList = ctx.schema.oneOf.map((schema) => getFlattened({ components: ctx.components, schema, visited: new Set(), })); // FIND ANY TYPE const anything = resolvedList.find((resolved) => OpenApiTypeChecker_1.OpenApiTypeChecker.isUnknown(resolved.escaped)); if (anything) return { branches: [], remainders: [anything.schema], }; // CHECK NULLABLES const nullables = resolvedList.filter((resolved) => OpenApiTypeChecker_1.OpenApiTypeChecker.isNull(resolved.schema)); const significant = resolvedList.filter((resolved) => false === OpenApiTypeChecker_1.OpenApiTypeChecker.isNull(resolved.escaped)); if (significant.length === 1) return { branches: [ { schema: significant[0].schema, predicator: (value) => value !== null, }, ], remainders: nullables.map((nullable) => nullable.schema), }; // DISCRIMINATIONS const tuples = significant.filter((flat) => OpenApiTypeChecker_1.OpenApiTypeChecker.isTuple(flat.escaped)); const arrays = significant.filter((flat) => OpenApiTypeChecker_1.OpenApiTypeChecker.isArray(flat.escaped)); const branches = [ ...(tuples.length === 0 && arrays.length !== 0 ? discriminateArrays(ctx, significant.filter((flat) => OpenApiTypeChecker_1.OpenApiTypeChecker.isArray(flat.schema))) : []), ...discriminateObjects(ctx, significant.filter((flat) => OpenApiTypeChecker_1.OpenApiTypeChecker.isObject(flat.escaped)), tuples.length + arrays.length === 0), ]; return { branches, remainders: ctx.schema.oneOf.filter((x) => branches.some((y) => y.schema === x) === false), }; }; const discriminateArrays = (ctx, arraySchemas) => { if (arraySchemas.length === 1) return [ { schema: arraySchemas[0].schema, predicator: (value) => Array.isArray(value), }, ]; return arraySchemas .filter((flat, i, array) => array.every((item, j) => i === j || OpenApiTypeChecker_1.OpenApiTypeChecker.covers({ components: ctx.components, x: item.escaped.items, y: flat.escaped.items, }) === false)) .map((flat) => ({ schema: flat.schema, predicator: (value) => Array.isArray(value) && (value.length === 0 || OpenApiStationValidator_1.OpenApiStationValidator.validate(Object.assign(Object.assign({}, ctx), { schema: flat.escaped.items, value: value[0], path: `${ctx.path}[0]`, exceptionable: false, equals: false }))), })); }; const discriminateObjects = (ctx, objectSchemas, noArray) => { if (objectSchemas.length === 1) return [ { schema: objectSchemas[0].schema, predicator: noArray ? (value) => typeof value === "object" && value !== null : (value) => typeof value === "object" && value !== null && Array.isArray(value) === false, }, ]; // KEEP ONLY REQUIRED PROPERTIES objectSchemas = objectSchemas .filter((flat) => flat.escaped.properties !== undefined && flat.escaped.required !== undefined) .map((flat) => { var _a; return (Object.assign(Object.assign({}, flat), { escaped: Object.assign(Object.assign({}, flat.escaped), { properties: Object.fromEntries(Object.entries((_a = flat.escaped.properties) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => [ key, getFlattened({ components: ctx.components, schema: value, visited: new Set(), }).escaped, ])) }) })); }); // PROPERTY MATRIX const matrix = new Map(); objectSchemas.forEach((obj, i) => { var _a, _b; for (const [key, value] of Object.entries((_a = obj.escaped.properties) !== null && _a !== void 0 ? _a : {})) { if (!!((_b = obj.escaped.required) === null || _b === void 0 ? void 0 : _b.includes(key)) === false) continue; MapUtil_1.MapUtil.take(matrix)(key)(() => new Array(objectSchemas.length).fill(null))[i] = value; } }); // THE BRANCHES return objectSchemas .map((obj, i) => { var _a, _b, _c; const candidates = []; for (const [key, value] of Object.entries((_a = obj.escaped.properties) !== null && _a !== void 0 ? _a : {})) { if (!!((_b = obj.escaped.required) === null || _b === void 0 ? void 0 : _b.includes(key)) === false) continue; const neighbors = matrix .get(key) .filter((_oppo, j) => i !== j) .filter((oppo) => oppo !== null); const unique = OpenApiTypeChecker_1.OpenApiTypeChecker.isConstant(value) ? neighbors.every((oppo) => OpenApiTypeChecker_1.OpenApiTypeChecker.isConstant(oppo) && value.const !== oppo.const) : neighbors.length === 0; if (unique) candidates.push(key); } if (candidates.length === 0) return null; const top = (_c = candidates.find((key) => OpenApiTypeChecker_1.OpenApiTypeChecker.isConstant(obj.escaped.properties[key]))) !== null && _c !== void 0 ? _c : candidates[0]; const target = obj.escaped.properties[top]; return { schema: obj.schema, predicator: OpenApiTypeChecker_1.OpenApiTypeChecker.isConstant(target) ? (value) => typeof value === "object" && value !== null && value[top] === target.const : (value) => typeof value === "object" && value !== null && value[top] !== undefined, }; }) .filter((b) => b !== null); }; })(OpenApiOneOfValidator || (exports.OpenApiOneOfValidator = OpenApiOneOfValidator = {})); const getFlattened = (props) => { var _a, _b, _c; if (OpenApiTypeChecker_1.OpenApiTypeChecker.isReference(props.schema)) { const key = (_a = props.schema.$ref.split("/").pop()) !== null && _a !== void 0 ? _a : ""; if (props.visited.has(key)) return { schema: props.schema, escaped: {}, }; props.visited.add(key); return Object.assign(Object.assign({}, getFlattened({ components: props.components, schema: (_c = (_b = props.components.schemas) === null || _b === void 0 ? void 0 : _b[key]) !== null && _c !== void 0 ? _c : {}, visited: props.visited, })), { schema: props.schema }); } return { schema: props.schema, escaped: props.schema, }; };