UNPKG

tynder

Version:

TypeScript friendly Data validator for JavaScript.

374 lines 15 kB
"use strict"; // Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln Object.defineProperty(exports, "__esModule", { value: true }); exports.reportErrorWithPush = exports.reportError = exports.formatErrorMessage = exports.defaultMessages = exports.errorTypeNames = void 0; const types_1 = require("../types"); const escape_1 = require("./escape"); const util_1 = require("./util"); exports.errorTypeNames = [ '', 'InvalidDefinition', 'Required', 'TypeUnmatched', 'AdditionalPropUnmatched', 'RepeatQtyUnmatched', 'SequenceUnmatched', 'ValueRangeUnmatched', 'ValuePatternUnmatched', 'ValueLengthUnmatched', 'ValueUnmatched', ]; exports.defaultMessages = { invalidDefinition: '"%{name}" of "%{parentType}" type definition is invalid.', required: '"%{name}" of "%{parentType}" is required.', typeUnmatched: '"%{name}" of "%{parentType}" should be type "%{expectedType}".', additionalPropUnmatched: '"%{addtionalProps}" of "%{parentType}" are not matched to additional property patterns.', repeatQtyUnmatched: '"%{name}" of "%{parentType}" should repeat %{repeatQty} times.', sequenceUnmatched: '"%{name}" of "%{parentType}" sequence is not matched', valueRangeUnmatched: '"%{name}" of "%{parentType}" value should be in the range %{minValue} to %{maxValue}.', valuePatternUnmatched: '"%{name}" of "%{parentType}" value should be matched to pattern "%{pattern}"', valueLengthUnmatched: '"%{name}" of "%{parentType}" length should be in the range %{minLength} to %{maxLength}.', valueUnmatched: '"%{name}" of "%{parentType}" value should be "%{expectedValue}".', }; function getErrorMessage(errType, ...messages) { for (const m of messages) { switch (errType) { case types_1.ErrorTypes.InvalidDefinition: if (m.invalidDefinition) { return m.invalidDefinition; } break; case types_1.ErrorTypes.Required: if (m.required) { return m.required; } break; case types_1.ErrorTypes.TypeUnmatched: if (m.typeUnmatched) { return m.typeUnmatched; } break; case types_1.ErrorTypes.AdditionalPropUnmatched: if (m.additionalPropUnmatched) { return m.additionalPropUnmatched; } break; case types_1.ErrorTypes.RepeatQtyUnmatched: if (m.repeatQtyUnmatched) { return m.repeatQtyUnmatched; } break; case types_1.ErrorTypes.SequenceUnmatched: if (m.sequenceUnmatched) { return m.sequenceUnmatched; } break; case types_1.ErrorTypes.ValueRangeUnmatched: if (m.valueRangeUnmatched) { return m.valueRangeUnmatched; } break; case types_1.ErrorTypes.ValuePatternUnmatched: if (m.valuePatternUnmatched) { return m.valuePatternUnmatched; } break; case types_1.ErrorTypes.ValueLengthUnmatched: if (m.valueLengthUnmatched) { return m.valueLengthUnmatched; } break; case types_1.ErrorTypes.ValueUnmatched: if (m.valueUnmatched) { return m.valueUnmatched; } break; } } return ''; } function findTopRepeatableAssertion(ctx) { const ret = ctx.typeStack .slice() .reverse() .map(x => Array.isArray(x) ? x[0] : x) .find(x => x.kind === 'repeated' || x.kind === 'spread' || x.kind === 'optional') || null; return ret; } function getExpectedType(ty) { switch (ty.kind) { case 'repeated': return `(repeated ${getExpectedType(ty.repeated)})`; case 'spread': return getExpectedType(ty.spread); case 'sequence': return '(sequence)'; case 'primitive': return ty.primitiveName; case 'primitive-value': return `(value ${typeof ty.value === 'string' ? `'${String(ty.value)}'` : String(ty.value)})`; case 'optional': return getExpectedType(ty.optional); case 'one-of': return `(one of ${ty.oneOf.map(x => getExpectedType(x)).join(', ')})`; case 'never': case 'any': case 'unknown': return ty.kind; case 'symlink': return ty.symlinkTargetName; default: return ty.typeName ? ty.typeName : '?'; } } function formatErrorMessage(msg, data, ty, args, values) { let ret = msg; // TODO: complex type object members' custom error messages are not displayed? // TODO: escapeString() is needed? const tr = values.topRepeatable; const dict = new Map([ ['expectedType', ty.stereotype ? ty.stereotype : escape_1.escapeString(getExpectedType(ty))], ['type', escape_1.escapeString(typeof data)], ['expectedValue', escape_1.escapeString(ty.kind === 'primitive-value' ? String(ty.value) : ty.kind === 'enum' ? ty.typeName ? `enum member of ${ty.typeName}` : '?' : '?')], ['value', escape_1.escapeString(String(data))], ['repeatQty', escape_1.escapeString(tr ? tr.kind !== 'optional' ? `${util_1.nvl(tr.min, '')}${(tr.min !== null && tr.min !== void 0) || (tr.max !== null && tr.max !== void 0) ? '..' : ''}${util_1.nvl(tr.max, '')}` : '0..1' : '?')], ['minValue', escape_1.escapeString(ty.kind === 'primitive' ? `${util_1.nvl(ty.minValue, util_1.nvl(ty.greaterThanValue, '(smallest)'))}` : '?')], ['maxValue', escape_1.escapeString(ty.kind === 'primitive' ? `${util_1.nvl(ty.maxValue, util_1.nvl(ty.lessThanValue, '(biggest)'))}` : '?')], ['pattern', escape_1.escapeString(ty.kind === 'primitive' ? `${ty.pattern ? `/${ty.pattern.source}/${ty.pattern.flags}` : '(pattern)'}` : '?')], ['minLength', escape_1.escapeString(ty.kind === 'primitive' ? `${util_1.nvl(ty.minLength, '0')}` : '?')], ['maxLength', escape_1.escapeString(ty.kind === 'primitive' ? `${util_1.nvl(ty.maxLength, '(biggest)')}` : '?')], ['name', escape_1.escapeString(`${ty.kind !== 'repeated' && values.dataPath.endsWith('repeated)') ? 'repeated item of ' : ty.kind !== 'sequence' && values.dataPath.endsWith('sequence)') ? 'sequence item of ' : ''}${values.entryName || '?'}`)], ['parentType', escape_1.escapeString(values.parentType || '?')], ['dataPath', values.dataPath], ...(args.substitutions || []), ]); for (const ent of dict.entries()) { ret = ret.replace(new RegExp(`%{${ent[0]}}`), ent[1]); } return ret; } exports.formatErrorMessage = formatErrorMessage; function reportError(errType, data, ty, args) { const messages = []; if (ty.messages) { messages.push(ty.messages); } if (args.ctx.errorMessages) { messages.push(args.ctx.errorMessages); } messages.push(exports.defaultMessages); const dataPathEntryArray = []; for (let i = 0; i < args.ctx.typeStack.length; i++) { const p = args.ctx.typeStack[i]; const next = args.ctx.typeStack[i + 1]; const pt = Array.isArray(p) ? p[0] : p; const pi = Array.isArray(next) ? next[1] : void 0; let isSet = false; if (pt.kind === 'repeated') { if (i !== args.ctx.typeStack.length - 1) { if (pt.name) { dataPathEntryArray.push({ kind: 'key', name: pt.name }); } dataPathEntryArray.push({ kind: 'index', name: `(${pi !== void 0 ? `${pi}:` : ''}repeated)` }); isSet = true; } } else if (pt.kind === 'sequence') { if (i !== args.ctx.typeStack.length - 1) { if (pt.name) { dataPathEntryArray.push({ kind: 'key', name: pt.name }); } dataPathEntryArray.push({ kind: 'index', name: `(${pi !== void 0 ? `${pi}:` : ''}sequence)` }); isSet = true; } } if (!isSet) { if (pt.name) { if (i === 0) { if (pt.typeName) { dataPathEntryArray.push({ kind: 'type', name: pt.typeName }); } else { dataPathEntryArray.push({ kind: 'key', name: pt.name }); } } else { const len = dataPathEntryArray.length; if (len && dataPathEntryArray[len - 1].kind === 'type') { if (pt.kind === 'object' && next && pt.typeName) { dataPathEntryArray.push({ kind: 'type', name: pt.typeName }); } else { dataPathEntryArray.push({ kind: 'key', name: pt.name }); // NOTE: type inference failed } } else { if (pt.typeName) { dataPathEntryArray.push({ kind: 'type', name: pt.typeName }); } else { dataPathEntryArray.push({ kind: 'key', name: pt.name }); } } } } else if (pt.typeName) { dataPathEntryArray.push({ kind: 'type', name: pt.typeName }); } } } let dataPath = ''; for (let i = 0; i < dataPathEntryArray.length; i++) { const p = dataPathEntryArray[i]; dataPath += p.name; if (i + 1 === dataPathEntryArray.length) { break; } dataPath += p.kind === 'type' ? ':' : '.'; } let parentType = ''; let entryName = ''; for (let i = dataPathEntryArray.length - 1; 0 <= i; i--) { const p = dataPathEntryArray[i]; if (p.kind === 'type') { if (i !== 0 && i === dataPathEntryArray.length - 1) { const q = dataPathEntryArray[i - 1]; if (q.kind === 'index') { continue; // e.g.: "File:acl.(0:repeated).ACL" } } // else: "File:acl.(0:repeated).ACL:target" parentType = p.name; for (let j = i + 1; j < dataPathEntryArray.length; j++) { const q = dataPathEntryArray[j]; if (q.kind === 'key') { entryName = q.name; break; } } break; } } if (!parentType) { for (let i = args.ctx.typeStack.length - 1; 0 <= i; i--) { const p = args.ctx.typeStack[i]; const pt = Array.isArray(p) ? p[0] : p; if (pt.typeName) { parentType = pt.typeName; } } } const topRepeatable = findTopRepeatableAssertion(args.ctx); const values = { dataPath, topRepeatable, parentType, entryName }; const constraints = {}; const cSrces = [ty]; if (errType === types_1.ErrorTypes.RepeatQtyUnmatched && topRepeatable) { cSrces.unshift(topRepeatable); } for (const cSrc of cSrces) { if (util_1.nvl(cSrc.minValue, false)) { constraints.minValue = cSrc.minValue; } if (util_1.nvl(cSrc.maxValue, false)) { constraints.maxValue = cSrc.maxValue; } if (util_1.nvl(cSrc.greaterThanValue, false)) { constraints.greaterThanValue = cSrc.greaterThanValue; } if (util_1.nvl(cSrc.lessThanValue, false)) { constraints.lessThanValue = cSrc.lessThanValue; } if (util_1.nvl(cSrc.minLength, false)) { constraints.minLength = cSrc.minLength; } if (util_1.nvl(cSrc.maxLength, false)) { constraints.maxLength = cSrc.maxLength; } if (util_1.nvl(cSrc.pattern, false)) { const pat = cSrc.pattern; constraints.pattern = `/${pat.source}/${pat.flags}`; } if (util_1.nvl(cSrc.min, false)) { constraints.min = cSrc.min; } if (util_1.nvl(cSrc.max, false)) { constraints.max = cSrc.max; } } const val = {}; switch (typeof data) { case 'number': case 'bigint': case 'string': case 'boolean': case 'undefined': val.value = data; break; case 'object': if (data === null) { val.value = data; } } if (ty.messageId) { args.ctx.errors.push(Object.assign({ code: `${ty.messageId}-${exports.errorTypeNames[errType]}`, message: formatErrorMessage(ty.message ? ty.message : getErrorMessage(errType, ...messages), data, ty, args, values), dataPath, constraints }, val)); } else if (ty.message) { args.ctx.errors.push(Object.assign({ code: `${exports.errorTypeNames[errType]}`, message: formatErrorMessage(ty.message, data, ty, args, values), dataPath, constraints }, val)); } else { args.ctx.errors.push(Object.assign({ code: `${exports.errorTypeNames[errType]}`, message: formatErrorMessage(getErrorMessage(errType, ...messages), data, ty, args, values), dataPath, constraints }, val)); } } exports.reportError = reportError; function reportErrorWithPush(errType, data, tyidx, args) { try { args.ctx.typeStack.push(tyidx); reportError(errType, data, tyidx[0], args); } finally { args.ctx.typeStack.pop(); } } exports.reportErrorWithPush = reportErrorWithPush; //# sourceMappingURL=reporter.js.map