UNPKG

tynder

Version:

TypeScript friendly Data validator for JavaScript.

896 lines 32.8 kB
"use strict"; // Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln Object.defineProperty(exports, "__esModule", { value: true }); exports.withMsgId = exports.withMsg = exports.withMeta = exports.withRecordType = exports.withForceCast = exports.withConstraint = exports.withStereotype = exports.withMatch = exports.withMaxLength = exports.withMinLength = exports.withLessThan = exports.withGreaterThan = exports.withMaxValue = exports.withMinValue = exports.withRange = exports.withDocComment = exports.withOriginalTypeName = exports.withTypeName = exports.withName = exports.symlinkType = exports.derived = exports.objectType = exports.enumType = exports.spread = exports.sequenceOf = exports.repeated = exports.optional = exports.primitiveValue = exports.regexpPatternStringType = exports.primitive = exports.subtract = exports.oneOf = exports.intersect = exports.partial = exports.omit = exports.picked = void 0; const protection_1 = require("./lib/protection"); // emulate Pick<T> // ex. Pick<Foo, 'a' | 'b'> function picked(ty, ...names) { switch (ty.kind) { case 'object': { const members = []; for (const name of names) { const member = ty.members.find(x => x[0] === name); if (member) { if (member[2]) { const m2 = [...member]; if (3 < m2.length) { m2[2] = false; } else { m2.length = 2; } members.push(m2); } else { members.push(member); } } } return ({ kind: 'object', members, }); } case 'symlink': case 'operator': { return ({ kind: 'operator', operator: 'picked', operands: [ty, ...names], }); } default: return ({ kind: 'object', members: [], }); } } exports.picked = picked; // emulate Omit<T> // ex. Omit<Foo, 'a' | 'b'> function omit(ty, ...names) { switch (ty.kind) { case 'object': { const members = []; for (const member of ty.members) { if (!names.find(name => member[0] === name)) { if (member[2]) { const m2 = [...member]; if (3 < m2.length) { m2[2] = false; } else { m2.length = 2; } members.push(m2); } else { members.push(member); } } } return ({ kind: 'object', members, }); } case 'symlink': case 'operator': { return ({ kind: 'operator', operator: 'omit', operands: [ty, ...names], }); } default: return ({ kind: 'object', members: [], }); } } exports.omit = omit; // emulate Partial<T> function partial(ty) { switch (ty.kind) { case 'object': { const members = []; for (const member of ty.members) { let m = member[1].kind === 'optional' ? member : [member[0], optional(member[1]), ...member.slice(2)]; if (m[2]) { m = [...m]; if (3 < m.length) { m[2] = false; } else { m.length = 2; } } m[1].name = m[0]; const optTy = Object.assign({}, m[1].optional); m[1].optional = optTy; if (optTy.name && optTy.name !== optTy.typeName) { delete optTy.name; } if (!optTy.name && optTy.typeName) { optTy.name = optTy.typeName; } members.push(m); } return ({ kind: 'object', members, }); } case 'symlink': case 'operator': { return ({ kind: 'operator', operator: 'partial', operands: [ty], }); } default: return ty; } } exports.partial = partial; // intersection (a & b) function intersect(...types) { if (types.length === 0) { throw new Error(`Empty intersection type is not allowed.`); } if (0 < types.filter(x => x && typeof x === 'object' && (x.kind === 'symlink' || x.kind === 'operator')).length) { return ({ kind: 'operator', operator: 'intersect', operands: types.slice(), }); } let lastTy = null; const members = new Map(); for (const ty of types) { if (ty && typeof ty === 'object') { if (lastTy && lastTy.kind !== ty.kind) { return ({ kind: 'never', }); } lastTy = ty; if (ty.kind === 'object') { for (const m of ty.members) { if (m[2]) { const m2 = [...m]; if (3 < m2.length) { m2[2] = false; } else { m2.length = 2; } members.set(m[0], m2); // Overwrite if exists } else { members.set(m[0], m); // Overwrite if exists } } } } else { return ({ kind: 'never', }); } } if (lastTy && lastTy.kind !== 'object') { return lastTy; } else { return ({ kind: 'object', members: Array.from(members.values()), }); } } exports.intersect = intersect; // union (a | b) function oneOf(...types) { if (types.length === 0) { throw new Error(`Empty union type is not allowed.`); } if (types.length === 1) { const ty = types[0]; if (ty && typeof ty === 'object') { return ty; } else { return primitiveValue(ty); } } const ret = { kind: 'one-of', oneOf: [], }; for (const ty of types) { // TODO: remove same type if (ty && typeof ty === 'object') { if (ty.kind === 'one-of') { ret.oneOf = ret.oneOf.concat(ty.oneOf); } else { ret.oneOf.push(ty); } } else { ret.oneOf.push(primitiveValue(ty)); } } return ret; } exports.oneOf = oneOf; // subtraction (a - b) function subtract(...types) { if (types.length === 0) { throw new Error(`Empty subtraction type is not allowed.`); } if (0 < types.filter(x => x && typeof x === 'object' && (x.kind === 'symlink' || x.kind === 'operator')).length) { return ({ kind: 'operator', operator: 'subtract', operands: types.slice(), }); } let ret = types[0]; if (!ret || typeof ret !== 'object' || ret.kind !== 'object') { throw new Error(`First parameter of subtraction type should be 'object'.`); } for (const ty of types.slice(1)) { if (ty && typeof ty === 'object' && ty.kind === 'object') { ret = omit(ret, ...ty.members.map(m => m[0])); } } return ret; } exports.subtract = subtract; function primitive(typeName) { switch (typeName) { case 'never': return ({ kind: 'never', }); case 'any': return ({ kind: 'any', }); case 'unknown': return ({ kind: 'unknown', }); case 'number': // FALL_THRU case 'integer': // FALL_THRU case 'bigint': // FALL_THRU case 'string': // FALL_THRU case 'boolean': // FALL_THRU case 'null': // FALL_THRU case 'undefined': return ({ kind: 'primitive', primitiveName: typeName, }); case 'never?': return (optional({ kind: 'never', })); case 'any?': return (optional({ kind: 'any', })); case 'unknown?': return (optional({ kind: 'unknown', })); case 'number?': // FALL_THRU case 'integer?': // FALL_THRU case 'bigint?': // FALL_THRU case 'string?': // FALL_THRU case 'boolean?': // FALL_THRU case 'null?': // FALL_THRU case 'undefined?': return (optional({ kind: 'primitive', primitiveName: typeName.substring(0, typeName.length - 1), })); default: throw new Error(`Unknown primitive type assertion: ${typeName}`); } // TODO: Function, DateStr, DateTimeStr, Funtion?, DateStr?, DateTimeStr? } exports.primitive = primitive; function regexpPatternStringType(pattern) { return ({ kind: 'primitive', primitiveName: 'string', pattern, }); } exports.regexpPatternStringType = regexpPatternStringType; function primitiveValue(value) { if (value === null || value === void 0) { return ({ kind: 'primitive-value', value, }); } else switch (typeof value) { case 'number': // FALL_THRU case 'bigint': // FALL_THRU case 'string': // FALL_THRU case 'boolean': return ({ kind: 'primitive-value', value, }); default: throw new Error(`Unknown primitive value assertion: ${value}`); } } exports.primitiveValue = primitiveValue; function optional(ty) { if (ty && typeof ty === 'object' && ty.kind) { if (ty.kind === 'optional') { return ty; } else { return (Object.assign({ kind: 'optional', optional: ty }, (ty.typeName ? { typeName: ty.typeName } : {}))); } } else { return ({ kind: 'optional', optional: primitiveValue(ty), }); } } exports.optional = optional; function repeated(ty, option) { if (ty && typeof ty === 'object' && ty.kind) { return ({ kind: 'repeated', min: option && typeof option.min === 'number' ? option.min : null, max: option && typeof option.max === 'number' ? option.max : null, repeated: ty, }); } else { return ({ kind: 'repeated', min: option && typeof option.min === 'number' ? option.min : null, max: option && typeof option.max === 'number' ? option.max : null, repeated: primitive(ty), }); } } exports.repeated = repeated; function sequenceOf(...seq) { return ({ kind: 'sequence', sequence: seq.map(ty => ty && typeof ty === 'object' && ty.kind ? ty : primitiveValue(ty)), }); } exports.sequenceOf = sequenceOf; function spread(ty, option) { if (ty && typeof ty === 'object' && ty.kind) { return ({ kind: 'spread', min: option && typeof option.min === 'number' ? option.min : null, max: option && typeof option.max === 'number' ? option.max : null, spread: ty, }); } else { return ({ kind: 'spread', min: option && typeof option.min === 'number' ? option.min : null, max: option && typeof option.max === 'number' ? option.max : null, spread: primitiveValue(ty), }); } } exports.spread = spread; function enumType(...values) { const ar = values.slice(); let value = 0; for (let i = 0; i < ar.length; i++) { if (protection_1.isUnsafeVarNames(protection_1.dummyTargetObject, ar[i][0])) { throw new Error(`Unsafe symbol name is appeared in enum assertion: ${ar[i][0]}`); } if (ar[i][1] === null || ar[i][1] === void 0) { ar[i][1] = value++; } else if (typeof ar[i][1] === 'number') { value = ar[i][1] + 1; } if (!ar[i][2]) { ar[i].length = 2; } } return ({ kind: 'enum', values: ar, }); } exports.enumType = enumType; function objectType(...members) { const revMembers = members.slice().reverse(); for (const x of members) { if (typeof x[0] === 'string') { if (protection_1.isUnsafeVarNames(protection_1.dummyTargetObject, x[0])) { throw new Error(`Unsafe symbol name is appeared in object assertion: ${x[0]}`); } if (members.find(m => m[0] === x[0]) !== revMembers.find(m => m[0] === x[0])) { throw new Error(`Duplicated member is found: ${x[0]}`); } } } const membersProps = members .filter(x => typeof x[0] === 'string') .map(x => x[1] && typeof x[1] === 'object' && x[1].kind ? [x[0], withName(x[1], x[0]), x[2]] : [x[0], withName(primitiveValue(x[1]), x[0]), x[2]]) .map(x => (x[2] ? [x[0], x[1], false, ...x.slice(2)] : [x[0], x[1]])); const additionalProps = members .filter(x => typeof x[0] !== 'string') .map(x => x[1] && typeof x[1] === 'object' && x[1].kind ? x : [x[0], primitiveValue(x[1]), x[2]]) .map(x => (x[2] ? [x[0], x[1], false, ...x.slice(2)] : [x[0], x[1]])); return (Object.assign({ kind: 'object', members: membersProps, }, (0 < additionalProps.length ? { additionalProps, } : {}))); } exports.objectType = objectType; function checkRecursiveExtends(ty, base) { if (ty === base) { return false; } if (ty.typeName && (ty.typeName === base.typeName || (base.kind === 'symlink' && ty.typeName === base.symlinkTargetName))) { return false; } if (base.kind === 'object' && base.baseTypes) { for (const z of base.baseTypes) { if (!checkRecursiveExtends(ty, z)) { return false; } } } return true; } function derived(ty, ...exts) { const ret = { kind: 'object', members: [], baseTypes: [], }; for (const ext of exts) { switch (ext.kind) { case 'object': if (!checkRecursiveExtends(ty, ext)) { throw new Error(`Recursive extend is found: ${ty.name || '(unnamed)'}`); } for (const m of ext.members) { if (!ret.members.find(x => x[0] === m[0])) { ret.members.push([m[0], m[1], true, ...m.slice(3)]); } // TODO: Check for different types with the same name. } // FALL_THRU case 'symlink': ret.baseTypes.push(ext); break; case 'operator': { throw new Error(`Unresolved type operator is found: ${ext.operator}`); } } // NOTE: 'symlink' base types will resolved by calling `resolveSymbols()`. // `resolveSymbols()` will call `derived()` after resolve symlink exts. } ret.members = ty.members.concat(ret.members); if (ty.baseTypes) { ret.baseTypes = ty.baseTypes .filter(x => x.kind !== 'symlink') .concat(ret.baseTypes); } if (ret.baseTypes.length === 0) { delete ret.baseTypes; } const revMembers = ret.members.slice().reverse(); for (const x of ret.members) { if (ret.members.find(m => m[0] === x[0]) !== revMembers.find(m => m[0] === x[0])) { throw new Error(`Duplicated member is found: ${x[0]} in ${ty.name || '(unnamed)'}`); } } let additionalProps = []; if (ret.baseTypes) { for (const base of ret.baseTypes) { if (base.kind === 'object') { if (base.additionalProps && 0 < base.additionalProps.length) { additionalProps = additionalProps.concat(base.additionalProps.map(x => [x[0], x[1], true, ...x.slice(3)])); } } // NOTE: 'symlink' base types will resolved by calling `resolveSymbols()`. // `resolveSymbols()` will call `derived()` after resolve symlink exts. } } if (ty.additionalProps && 0 < ty.additionalProps.length) { additionalProps = additionalProps.concat(ty.additionalProps); // TODO: concat order } if (0 < additionalProps.length) { ret.additionalProps = additionalProps; } return ret; } exports.derived = derived; function symlinkType(name) { return ({ kind: 'symlink', symlinkTargetName: name, }); } exports.symlinkType = symlinkType; function withName(ty, name) { if (!name) { return ty; } return (Object.assign(Object.assign({}, ty), { name })); } exports.withName = withName; function withTypeName(ty, typeName) { if (!typeName) { return ty; } return (Object.assign(Object.assign({}, ty), { typeName })); } exports.withTypeName = withTypeName; function withOriginalTypeName(ty, originalTypeName) { if (!originalTypeName) { return ty; } return (Object.assign(Object.assign({}, ty), { originalTypeName })); } exports.withOriginalTypeName = withOriginalTypeName; function withDocComment(ty, docComment) { if (!docComment) { return ty; } return (Object.assign(Object.assign({}, ty), { docComment })); } exports.withDocComment = withDocComment; function withRange(minValue, maxValue) { return (ty) => { if (typeof minValue !== 'number' && typeof minValue !== 'string') { throw new Error(`Decorator '@range' parameter 'minValue' should be number or string.`); } if (typeof maxValue !== 'number' && typeof maxValue !== 'string') { throw new Error(`Decorator '@range' parameter 'maxValue' should be number or string.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@range' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { minValue, maxValue }) })); } else { if (!ty || ty.kind !== 'primitive') { throw new Error(`Decorator '@range' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { minValue, maxValue })); } }; } exports.withRange = withRange; function withMinValue(minValue) { return (ty) => { if (typeof minValue !== 'number' && typeof minValue !== 'string') { throw new Error(`Decorator '@minValue' parameter 'minValue' should be number or string.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@minValue' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { minValue }) })); } else { if (!ty || ty.kind !== 'primitive') { throw new Error(`Decorator '@minValue' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { minValue })); } }; } exports.withMinValue = withMinValue; function withMaxValue(maxValue) { return (ty) => { if (typeof maxValue !== 'number' && typeof maxValue !== 'string') { throw new Error(`Decorator '@maxValue' parameter 'maxValue' should be number or string.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@maxValue' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { maxValue }) })); } else { if (!ty || ty.kind !== 'primitive') { throw new Error(`Decorator '@maxValue' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { maxValue })); } }; } exports.withMaxValue = withMaxValue; function withGreaterThan(greaterThanValue) { return (ty) => { if (typeof greaterThanValue !== 'number' && typeof greaterThanValue !== 'string') { throw new Error(`Decorator '@greaterThan' parameter 'greaterThan' should be number or string.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@greaterThan' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { greaterThanValue }) })); } else { if (!ty || ty.kind !== 'primitive') { throw new Error(`Decorator '@greaterThan' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { greaterThanValue })); } }; } exports.withGreaterThan = withGreaterThan; function withLessThan(lessThanValue) { return (ty) => { if (typeof lessThanValue !== 'number' && typeof lessThanValue !== 'string') { throw new Error(`Decorator '@lessThan' parameter 'lessThan' should be number or string.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@lessThan' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { lessThanValue }) })); } else { if (!ty || ty.kind !== 'primitive') { throw new Error(`Decorator '@lessThan' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { lessThanValue })); } }; } exports.withLessThan = withLessThan; function withMinLength(minLength) { return (ty) => { if (typeof minLength !== 'number') { throw new Error(`Decorator '@minLength' parameter 'minLength' should be number.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@minLength' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { minLength }) })); } else { if (!ty || ty.kind !== 'primitive') { throw new Error(`Decorator '@minLength' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { minLength })); } }; } exports.withMinLength = withMinLength; function withMaxLength(maxLength) { return (ty) => { if (typeof maxLength !== 'number') { throw new Error(`Decorator '@maxLength' parameter 'maxLength' should be number.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@maxLength' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { maxLength }) })); } else { if (!ty || ty.kind !== 'primitive') { throw new Error(`Decorator '@maxLength' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { maxLength })); } }; } exports.withMaxLength = withMaxLength; function withMatch(pattern) { return (ty) => { if (typeof pattern !== 'object') { throw new Error(`Decorator '@match' parameter 'pattern' should be RegExp.`); } if (ty.kind === 'optional') { const opt = ty.optional; if (opt.kind !== 'primitive') { throw new Error(`Decorator '@match' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { pattern }) })); } else { if (!ty || ty.kind !== 'primitive' || ty.primitiveName !== 'string') { throw new Error(`Decorator '@match' cannot be applied to anything other than 'primitive'.`); } return (Object.assign(Object.assign({}, ty), { pattern })); } }; } exports.withMatch = withMatch; function withStereotype(stereotype) { if (typeof stereotype !== 'string') { throw new Error(`Decorator '@stereotype' parameter 'stereotype' should be string.`); } if (protection_1.isUnsafeVarNames(protection_1.dummyTargetObject, stereotype)) { throw new Error(`Unsafe symbol name is appeared in stereotype assertion: ${stereotype}`); } return (ty) => { if (ty.kind === 'optional') { const ret = (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, ty.optional), { stereotype }) })); return ret; } else { const ret = (Object.assign(Object.assign({}, ty), { stereotype })); return ret; } }; } exports.withStereotype = withStereotype; function withConstraint(name, args) { if (typeof name !== 'string') { throw new Error(`Decorator '@constraint' parameter 'name' should be string.`); } if (protection_1.isUnsafeVarNames(protection_1.dummyTargetObject, name)) { throw new Error(`Unsafe symbol name is appeared in constraint assertion: ${name}`); } return (ty) => { if (ty.kind === 'optional') { const opt = ty.optional; const ret = (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, opt), { customConstraints: opt.customConstraints ? opt.customConstraints.slice().push(name) : [name], customConstraintsArgs: opt.customConstraintsArgs ? Object.assign(Object.assign({}, opt.customConstraintsArgs), { [name]: args }) : { [name]: args } }) })); return ret; } else { const ret = (Object.assign(Object.assign({}, ty), { customConstraints: ty.customConstraints ? ty.customConstraints.slice().push(name) : [name], customConstraintsArgs: ty.customConstraintsArgs ? Object.assign(Object.assign({}, ty.customConstraintsArgs), { [name]: args }) : { [name]: args } })); return ret; } }; } exports.withConstraint = withConstraint; function withForceCast() { return (ty) => { if (ty.kind === 'optional') { const ret = (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, ty.optional), { forceCast: true }) })); return ret; } else { const ret = (Object.assign(Object.assign({}, ty), { forceCast: true })); return ret; } }; } exports.withForceCast = withForceCast; function withRecordType() { return (ty) => { if (ty.kind === 'optional') { const ret = (Object.assign(Object.assign({}, ty), { optional: Object.assign(Object.assign({}, ty.optional), { isRecordTypeField: true }) })); return ret; } else { const ret = (Object.assign(Object.assign({}, ty), { isRecordTypeField: true })); return ret; } }; } exports.withRecordType = withRecordType; function withMeta(meta) { return (ty) => { const ret = (Object.assign(Object.assign({}, ty), { meta })); return ret; }; } exports.withMeta = withMeta; function withMsg(messages) { return (ty) => { if (ty.kind === 'optional') { if (typeof messages === 'string') { const ret = (Object.assign(Object.assign({}, ty), { message: messages, optional: Object.assign(Object.assign({}, ty.optional), { message: messages }) })); delete ret.messages; delete ret.optional.messages; return ret; } else { const ret = (Object.assign(Object.assign({}, ty), { messages, optional: Object.assign(Object.assign({}, ty.optional), { messages }) })); delete ret.message; delete ret.optional.message; return ret; } } else { if (typeof messages === 'string') { const ret = (Object.assign(Object.assign({}, ty), { message: messages })); delete ret.messages; return ret; } else { const ret = (Object.assign(Object.assign({}, ty), { messages })); delete ret.message; return ret; } } }; } exports.withMsg = withMsg; function withMsgId(messageId) { return (ty) => { if (ty.kind === 'optional') { return (Object.assign(Object.assign({}, ty), { messageId, optional: Object.assign(Object.assign({}, ty.optional), { messageId }) })); } else { return (Object.assign(Object.assign({}, ty), { messageId })); } }; } exports.withMsgId = withMsgId; //# sourceMappingURL=operators.js.map