// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import { RecursivePartial, TypeAssertion, ValidationContext } from './types'; import { ValidationError } from './lib/errors'; import { isUnsafeVarNames } from './lib/protection'; import { validate } from './validator'; function pickMapper(value: any, ty: TypeAssertion) { switch (ty.kind) { case 'object': { const ret = Array.isArray(value) ? [] : {}; const dataMembers = new Set(); if (! Array.isArray(value)) { for (const m in value) { if (Object.prototype.hasOwnProperty.call(value, m)) { dataMembers.add(m); } } } for (const x of ty.members) { if (Object.hasOwnProperty.call(value, x[0])) { dataMembers.delete(x[0]); ret[x[0]] = value[x[0]]; } } if (ty.additionalProps && 0 < ty.additionalProps.length) { function* getAdditionalMembers() { for (const m of dataMembers.values()) { yield m; } if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { yield String(i); } } } for (const m of getAdditionalMembers()) { ret[m] = value[m]; } } return ret; } default: return value; } } export function pickRoot(data: T, ty: TypeAssertion, ctx: ValidationContext): T { switch (ty.kind) { case 'never': throw new ValidationError(`Type unmatched: ${(ty as any).kind}`, ty, ctx); case 'any': // FALL_THRU case 'unknown': // FALL_THRU case 'primitive': // FALL_THRU case 'primitive-value': // FALL_THRU case 'repeated': // FALL_THRU case 'sequence': // FALL_THRU case 'one-of': // FALL_THRU case 'enum': // FALL_THRU case 'object': { const r = validate(data, ty, ctx); if (r) { return r.value; } else { throw new ValidationError('Validation failed.', ty, ctx); } } case 'spread': case 'optional': case 'symlink': case 'operator': throw new ValidationError(`Unexpected type assertion: ${(ty as any).kind}`, ty, ctx); default: throw new ValidationError(`Unknown type assertion: ${(ty as any).kind}`, ty, ctx); } } export function pick(data: T, ty: TypeAssertion, ctx?: Partial): RecursivePartial { const ctx2: ValidationContext = { ...{errors: [], typeStack: []}, ...(ctx || {}), mapper: pickMapper, }; try { return pickRoot(data, ty, ctx2); } finally { if (ctx) { ctx.errors = ctx2.errors; } } } function merge(data: any, needle: any) { if (data === null || data === void 0) { return needle; } switch (typeof data) { case 'object': if (Array.isArray(data)) { return [...needle]; } else { const r: any = {...data}; for (const k in needle) { if (Object.prototype.hasOwnProperty.call(needle, k)) { if (isUnsafeVarNames(r, k)) { continue; } r[k] = merge(r[k], needle[k]); } } return r; } default: return needle; } } export function patch(data: T, needle: any, ty: TypeAssertion, ctx?: Partial): T { const ctx2: ValidationContext = { ...{errors: [], typeStack: []}, ...(ctx || {}), }; const validated = pick(needle, ty, ctx2); return merge(data, validated); }