UNPKG

@samchon/openapi

Version:

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

142 lines (135 loc) 6.64 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 }); return discriminator.branches.length === 0 ? discriminator.remainders.map((schema => OpenApiStationValidator.validate({ ...ctx, schema, exceptionable: false }))).some((v => v)) || ctx.report(ctx) : OpenApiOneOfValidator.validate({ ...ctx, schema: { oneOf: discriminator.remainders } }); }; 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 }))))).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 })) }))); }; 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