UNPKG

tynder

Version:

TypeScript friendly Data validator for JavaScript.

200 lines 10.7 kB
// 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