@samchon/openapi
Version:
OpenAPI definitions and converters for 'typia' and 'nestia'.
142 lines (135 loc) • 6.64 kB
JavaScript
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