tynder
Version:
TypeScript friendly Data validator for JavaScript.
374 lines • 15 kB
JavaScript
;
// 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