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.

150 lines (143 loc) 6.86 kB
import { MapUtil } from "../MapUtil.mjs"; import { OpenApiTypeChecker } from "../OpenApiTypeChecker.mjs"; import { OpenApiStationValidator } from "./OpenApiStationValidator.mjs"; var OpenApiOneOfValidator; (function(OpenApiOneOfValidator) { OpenApiOneOfValidator.validate = ctx => { const discriminator = getDiscriminator(ctx); for (const item of discriminator.branches) if (item.predicator(ctx.value)) return OpenApiStationValidator.validate({ ...ctx, schema: item.schema }); if (discriminator.branches.length !== 0) return OpenApiOneOfValidator.validate({ ...ctx, schema: { oneOf: discriminator.remainders } }); const matched = discriminator.remainders.find(schema => OpenApiStationValidator.validate({ ...ctx, schema, exceptionable: false, equals: false }) === true); if (matched === undefined) return ctx.report(ctx); return ctx.equals === true ? OpenApiStationValidator.validate({ ...ctx, schema: matched }) : true; }; const getDiscriminator = ctx => { const resolvedList = ctx.schema.oneOf.map(schema => getFlattened({ components: ctx.components, schema, visited: new Set })); const anything = resolvedList.find(resolved => OpenApiTypeChecker.isUnknown(resolved.escaped)); if (anything) return { branches: [], remainders: [ anything.schema ] }; const nullables = resolvedList.filter(resolved => OpenApiTypeChecker.isNull(resolved.schema)); const significant = resolvedList.filter(resolved => false === OpenApiTypeChecker.isNull(resolved.escaped)); if (significant.length === 1) return { branches: [ { schema: significant[0].schema, predicator: value => value !== null } ], remainders: nullables.map(nullable => nullable.schema) }; const tuples = significant.filter(flat => OpenApiTypeChecker.isTuple(flat.escaped)); const arrays = significant.filter(flat => OpenApiTypeChecker.isArray(flat.escaped)); const branches = [ ...tuples.length === 0 && arrays.length !== 0 ? discriminateArrays(ctx, significant.filter(flat => OpenApiTypeChecker.isArray(flat.schema))) : [], ...discriminateObjects(ctx, significant.filter(flat => 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.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.validate({ ...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 } ]; objectSchemas = objectSchemas.filter(flat => flat.escaped.properties !== undefined && flat.escaped.required !== undefined).map(flat => ({ ...flat, escaped: { ...flat.escaped, properties: Object.fromEntries(Object.entries(flat.escaped.properties ?? {}).map(([key, value]) => [ key, getFlattened({ components: ctx.components, schema: value, visited: new Set }).escaped ])) } })); const matrix = new Map; objectSchemas.forEach((obj, i) => { for (const [key, value] of Object.entries(obj.escaped.properties ?? {})) { if (!!obj.escaped.required?.includes(key) === false) continue; MapUtil.take(matrix)(key)(() => new Array(objectSchemas.length).fill(null))[i] = value; } }); return objectSchemas.map((obj, i) => { const candidates = []; for (const [key, value] of Object.entries(obj.escaped.properties ?? {})) { if (!!obj.escaped.required?.includes(key) === false) continue; const neighbors = matrix.get(key).filter((_oppo, j) => i !== j).filter(oppo => oppo !== null); const unique = OpenApiTypeChecker.isConstant(value) ? neighbors.every(oppo => OpenApiTypeChecker.isConstant(oppo) && value.const !== oppo.const) : neighbors.length === 0; if (unique) candidates.push(key); } if (candidates.length === 0) return null; const top = candidates.find(key => OpenApiTypeChecker.isConstant(obj.escaped.properties[key])) ?? candidates[0]; const target = obj.escaped.properties[top]; return { schema: obj.schema, predicator: 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 || (OpenApiOneOfValidator = {})); const getFlattened = props => { if (OpenApiTypeChecker.isReference(props.schema)) { const key = props.schema.$ref.split("/").pop() ?? ""; if (props.visited.has(key)) return { schema: props.schema, escaped: {} }; props.visited.add(key); return { ...getFlattened({ components: props.components, schema: props.components.schemas?.[key] ?? {}, visited: props.visited }), schema: props.schema }; } return { schema: props.schema, escaped: props.schema }; }; export { OpenApiOneOfValidator }; //# sourceMappingURL=OpenApiOneOfValidator.mjs.map