@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
JavaScript
;
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,
};
};