tynder
Version:
TypeScript friendly Data validator for JavaScript.
200 lines • 10.7 kB
JavaScript
// Copyright (c) 2019 Shellyl_N and Authors
// license: ISC
// https://github.com/shellyln
import * as operators from '../operators';
import { NumberPattern } from '../lib/util';
function mergeTypeAndSymlink(ty, link) {
const link2 = Object.assign({}, link);
delete link2.kind; // NOTE: (TS>=4.0) TS2790: The operand of a 'delete' operator must be optional.
delete link2.symlinkTargetName; // NOTE: (TS>=4.0) TS2790: The operand of a 'delete' operator must be optional.
delete link2.memberTree; // NOTE: (TS>=4.0) TS2790: The operand of a 'delete' operator must be optional.
return Object.assign(Object.assign({}, ty), link2);
}
function updateSchema(original, schema, ty, typeName) {
if (typeName && schema.has(typeName)) {
const z = schema.get(typeName);
if (z.ty === original) {
schema.set(typeName, Object.assign(Object.assign({}, z), { ty, resolved: true }));
}
}
return ty;
}
export function resolveMemberNames(ty, rootSym, memberTreeSymbols, memberPos) {
const addTypeName = (mt, typeName, memberSym) => {
if (typeName) {
return (Object.assign(Object.assign({}, mt), { typeName: memberPos === 0 ?
`${rootSym}.${memberTreeSymbols.join('.')}` :
`${typeName}.${memberSym}` }));
}
else {
return mt;
}
};
for (let i = memberPos; i < memberTreeSymbols.length; i++) {
const memberSym = memberTreeSymbols[i];
switch (ty.kind) {
case 'optional':
return resolveMemberNames(ty.optional, rootSym, memberTreeSymbols, i + 1);
case 'object':
for (const m of ty.members) {
if (memberSym === m[0]) {
return addTypeName(resolveMemberNames(m[1], rootSym, memberTreeSymbols, i + 1), ty.typeName, memberSym);
}
}
if (ty.additionalProps) {
for (const m of ty.additionalProps) {
for (const k of m[0]) {
switch (k) {
case 'number':
if (NumberPattern.test(memberSym)) {
return resolveMemberNames(m[1], rootSym, memberTreeSymbols, i + 1);
}
break;
case 'string':
return resolveMemberNames(m[1], rootSym, memberTreeSymbols, i + 1);
default:
if (k.test(memberSym)) {
return resolveMemberNames(m[1], rootSym, memberTreeSymbols, i + 1);
}
break;
}
}
}
}
throw new Error(`Undefined member name is appeared: ${memberSym}`);
case 'symlink':
if (!ty.typeName) {
throw new Error(`Reference of anonymous type is appeared: ${memberSym}`);
}
return (Object.assign({
kind: 'symlink',
symlinkTargetName: rootSym,
name: memberSym,
typeName: rootSym,
}, (0 < memberTreeSymbols.length ? {
memberTree: memberTreeSymbols,
} : {})));
default:
// TODO: kind === 'operator'
throw new Error(`Unsupported type kind is appeared: (kind:${ty.kind}).${memberSym}`);
}
}
return ty;
}
export function resolveSymbols(schema, ty, ctx) {
var _a;
const ctx2 = Object.assign(Object.assign({}, ctx), { nestLevel: ctx.nestLevel + 1 });
switch (ty.kind) {
case 'symlink':
{
const x = schema.get(ty.symlinkTargetName);
if (!x) {
throw new Error(`Undefined symbol '${ty.symlinkTargetName}' is referred.`);
}
if (0 <= ctx.symlinkStack.findIndex(s => s === ty.symlinkTargetName)) {
return ty;
}
const ty2 = Object.assign({}, ty);
let xTy = x.ty;
if (ty.memberTree && 0 < ty.memberTree.length) {
xTy = Object.assign({}, resolveMemberNames(xTy, ty.symlinkTargetName, ty.memberTree, 0));
ty2.typeName = xTy.typeName;
}
return (resolveSymbols(schema, mergeTypeAndSymlink(xTy, ty2), Object.assign(Object.assign({}, ctx2), { symlinkStack: [...ctx2.symlinkStack, ty2.symlinkTargetName] })));
}
case 'repeated':
return updateSchema(ty, schema, Object.assign(Object.assign({}, ty), { repeated: resolveSymbols(schema, ty.repeated, ctx2) }), ty.typeName);
case 'spread':
return updateSchema(ty, schema, Object.assign(Object.assign({}, ty), { spread: resolveSymbols(schema, ty.spread, ctx2) }), ty.typeName);
case 'sequence':
return updateSchema(ty, schema, Object.assign(Object.assign({}, ty), { sequence: ty.sequence.map(x => resolveSymbols(schema, x, ctx2)) }), ty.typeName);
case 'one-of':
return updateSchema(ty, schema, Object.assign(Object.assign({}, ty), { oneOf: ty.oneOf.map(x => resolveSymbols(schema, x, ctx2)) }), ty.typeName);
case 'optional':
return updateSchema(ty, schema, Object.assign(Object.assign({}, ty), { optional: resolveSymbols(schema, ty.optional, ctx2) }), ty.typeName);
case 'object':
{
if (0 < ctx.nestLevel && ty.typeName && 0 <= ctx.symlinkStack.findIndex(s => s === ty.typeName)) {
if (schema.has(ty.typeName)) {
const z = schema.get(ty.typeName);
if (z.resolved) {
return z.ty;
}
}
}
const baseSymlinks = (_a = ty.baseTypes) === null || _a === void 0 ? void 0 : _a.filter(x => x.kind === 'symlink');
if (baseSymlinks && baseSymlinks.length > 0 && !ctx.isDeserialization) {
const exts = baseSymlinks
.map(x => resolveSymbols(schema, x, ctx2))
.filter(x => x.kind === 'object');
// TODO: if x.kind !== 'object' items exist -> error?
const d2 = resolveSymbols(schema, operators.derived(Object.assign(Object.assign({}, ty), (ty.baseTypes ? {
baseTypes: ty.baseTypes.filter(x => x.kind !== 'symlink'),
} : {})), ...exts), ty.typeName ? Object.assign(Object.assign({}, ctx2), { symlinkStack: [...ctx2.symlinkStack, ty.typeName] }) : ctx2);
return updateSchema(ty, schema, Object.assign(Object.assign({}, ty), d2), ty.typeName);
}
else {
return updateSchema(ty, schema, Object.assign(Object.assign(Object.assign({}, Object.assign(Object.assign({}, ty), { members: ty.members
.map(x => [
x[0],
resolveSymbols(schema, x[1], ty.typeName ? Object.assign(Object.assign({}, ctx2), { symlinkStack: [...ctx2.symlinkStack, ty.typeName] }) : ctx2),
...x.slice(2),
]) })), (ty.additionalProps && 0 < ty.additionalProps.length ? {
additionalProps: ty.additionalProps
.map(x => [
x[0],
resolveSymbols(schema, x[1], ty.typeName ? Object.assign(Object.assign({}, ctx2), { symlinkStack: [...ctx2.symlinkStack, ty.typeName] }) : ctx2),
...x.slice(2),
]),
} : {})), (ty.baseTypes && 0 < ty.baseTypes.length ? {
baseTypes: ctx.isDeserialization ?
ty.baseTypes
.map(x => x.kind === 'symlink' ? resolveSymbols(schema, x, ctx2) : x)
.filter(x => x.kind === 'object') :
ty.baseTypes,
} : {})), ty.typeName);
}
}
case 'operator':
if (ctx2.operators) {
const ctx3 = ty.typeName ? Object.assign(Object.assign({}, ctx2), { symlinkStack: [...ctx2.symlinkStack, ty.typeName] }) : ctx2;
const operands = ty.operands.map(x => {
if (typeof x === 'object' && x.kind) {
return resolveSymbols(schema, x, ctx3);
}
return x;
});
if (0 < operands.filter(x => x && typeof x === 'object' &&
(x.kind === 'symlink' || x.kind === 'operator')).length) {
throw new Error(`Unresolved type operator is found: ${ty.operator}`);
}
if (!ctx2.operators[ty.operator]) {
throw new Error(`Undefined type operator is found: ${ty.operator}`);
}
const ty2 = Object.assign({}, ty);
delete ty2.operator; // NOTE: (TS>=4.0) TS2790: The operand of a 'delete' operator must be optional.
delete ty2.operands; // NOTE: (TS>=4.0) TS2790: The operand of a 'delete' operator must be optional.
return updateSchema(ty, schema, Object.assign(Object.assign({}, ty2), resolveSymbols(schema, ctx2.operators[ty.operator](...operands), ctx3)), ty.typeName);
}
else {
return ty;
}
default:
return ty;
}
}
const resolverOps = {
picked: operators.picked,
omit: operators.omit,
partial: operators.partial,
intersect: operators.intersect,
subtract: operators.subtract,
};
export function resolveSchema(schema, opts) {
for (const ent of schema.entries()) {
const ty = resolveSymbols(schema, ent[1].ty, Object.assign(Object.assign({}, opts), { nestLevel: 0, symlinkStack: [ent[0]], operators: resolverOps }));
ent[1].ty = ty;
}
return schema;
}
//# sourceMappingURL=resolver.js.map