pg-safe-numbers
Version:
Safe number parsing for pg and Sequelize.
245 lines (200 loc) • 6.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.safeParseInt = safeParseInt;
exports.floatTrim = floatTrim;
exports.floatCompare = floatCompare;
exports.safeParseFloat = safeParseFloat;
exports.pgSetTypeParsers = pgSetTypeParsers;
exports.pgUnsetTypeParsers = pgUnsetTypeParsers;
var _util = require('util');
var _util2 = _interopRequireDefault(_util);
var _postgresArray = require('postgres-array');
var _postgresArray2 = _interopRequireDefault(_postgresArray);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
let DefaultPg = null;
let DefaultSequelize = null;
try {
DefaultPg = require('pg');
} catch (err) {} // eslint-disable-line no-empty
try {
DefaultSequelize = require('sequelize');
} catch (err) {} // eslint-disable-line no-empty
/**
* Default handler for unsafe int parser.
* @param {Number} parsed Parsed value.
* @param {String} text Input string.
* @return Optional value to be returned by the parser.
*/
function defaultUnsafeInt(parsed, text) {
throw new TypeError(`Unsafe int parse ${ _util2.default.inspect(text) } to ${ _util2.default.inspect(parsed) }.`);
}
/**
* Default handler for unsafe float parser.
* @param {Number} parsed Parsed value.
* @param {String} text Input string.
* @return Optional value to be returned by the parser.
*/
function defaultUnsafeFloat(parsed, text) {
throw new TypeError(`Unsafe float parse ${ _util2.default.inspect(text) } to ${ _util2.default.inspect(parsed) }.`);
}
/**
* Parse integer delegating unsafe input to the handler.
* @param {String} text
* @param {Function} unsafeHandler = defaultUnsafeInt
* @return {Number|NaN} Parsed value or unsafe handler's result.
*/
function safeParseInt(text) {
let unsafeHandler = arguments.length <= 1 || arguments[1] === undefined ? defaultUnsafeInt : arguments[1];
const parsed = parseInt(text, 10);
if (Number.isSafeInteger(parsed) || isNaN(parsed)) {
return parsed;
}
return unsafeHandler(parsed, text);
}
/**
* Trim float (currently on the right only).
* @param {string} text Float string.
* @return {string}
*/
function floatTrim(text) {
if (text.indexOf('.') !== -1) {
return text.replace(/[0\s\uFEFF\xA0]+$/g, '');
}
return text;
}
/**
* Compare number with it's floating point string represenation.
* @param {Number} parsed
* @param {String} text
* @return {Boolean} True if the parsed number is the same as precision text representation.
*/
function floatCompare(parsed, text) {
let precision = (text.match(/[0-9]/g) || []).length;
return floatTrim(parsed.toPrecision(precision)) === floatTrim(text);
}
/**
* Parse float delegating unsafe input to the handler.
* @param {String} text
* @param {Function} unsafeHandler = defaultUnsafeFloat
* @return {Number|NaN|+/-Infinity} Parsed value or unsafe handler's result.
*/
function safeParseFloat(text) {
let unsafeHandler = arguments.length <= 1 || arguments[1] === undefined ? defaultUnsafeFloat : arguments[1];
let parsed = parseFloat(text);
if (floatCompare(parsed, text) || isNaN(parsed) || !isFinite(parsed)) {
return parsed;
}
return unsafeHandler(parsed, text);
}
/**
* Remember old parsers so they can be unset.
* @type {Array}
*/
let pgSetTypeParsersStack = [];
/**
* Replace default pg parsers for Int8 and Numberic data types.
* @param {pg?} .pg
* @param {function(parsed, text)?} unsafeInt Function handler to deal with unsafe input, default throws TypeError.
* @param {function(parsed, text)?} unsafeFloat Function handler to deal with unsafe input, default throws TypeError.
* @return {array} Previous parsers.
*/
function pgSetTypeParsers() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref$pg = _ref.pg;
let pg = _ref$pg === undefined ? DefaultPg : _ref$pg;
var _ref$Sequelize = _ref.Sequelize;
let Sequelize = _ref$Sequelize === undefined ? DefaultSequelize : _ref$Sequelize;
var _ref$unsafeInt = _ref.unsafeInt;
let unsafeInt = _ref$unsafeInt === undefined ? defaultUnsafeInt : _ref$unsafeInt;
var _ref$unsafeFloat = _ref.unsafeFloat;
let unsafeFloat = _ref$unsafeFloat === undefined ? defaultUnsafeFloat : _ref$unsafeFloat;
let result = [];
if (pg) {
const PgTypes = {
Int8: 20,
Int8Array: 1016,
Numeric: 1700,
NumericArray: 1231
};
// Remember old parsers.
['Int8', 'Int8Array', 'Numeric', 'NumericArray'].forEach(type => {
result.push({
kind: 'pg',
info: {
oid: PgTypes[type],
format: 'text',
func: pg.types.getTypeParser(PgTypes[type], 'text')
}
});
});
const safeParseIntFunction = function (text) {
return safeParseInt(text, unsafeInt);
};
const safeParseFloatFunction = function (text) {
return safeParseFloat(text, unsafeFloat);
};
pg.types.setTypeParser(PgTypes.Int8, 'text', safeParseIntFunction);
pg.types.setTypeParser(PgTypes.Numeric, 'text', safeParseFloatFunction);
pg.types.setTypeParser(PgTypes.Int8Array, 'text', function (text) {
return _postgresArray2.default.parse(text, safeParseIntFunction);
});
pg.types.setTypeParser(PgTypes.Int8Numeric, 'text', function (text) {
return _postgresArray2.default.parse(text, safeParseFloatFunction);
});
}
if (Sequelize) {
result.push({
kind: 'sequelize',
info: {
type: 'DECIMAL',
parse: Sequelize.postgres.DECIMAL.parse
}
});
Sequelize.postgres.DECIMAL.parse = function (value) {
return safeParseFloat(value, unsafeFloat);
};
}
pgSetTypeParsersStack.push(result);
return result;
}
/**
* Reverts to previously used type parsers.
* TODO: Do it in context of pg.
*/
function pgUnsetTypeParsers() {
var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref2$pg = _ref2.pg;
let pg = _ref2$pg === undefined ? DefaultPg : _ref2$pg;
var _ref2$Sequelize = _ref2.Sequelize;
let Sequelize = _ref2$Sequelize === undefined ? DefaultSequelize : _ref2$Sequelize;
const previous = pgSetTypeParsersStack.pop();
for (const _ref3 of previous) {
const kind = _ref3.kind;
const info = _ref3.info;
switch (kind) {
case 'pg':
if (pg) {
const oid = info.oid;
const format = info.format;
const func = info.func;
pg.types.setTypeParser(oid, format, func);
}
break;
case 'sequelize':
if (Sequelize) {
const type = info.type;
const parse = info.parse;
Sequelize.postgres[type].parse = parse;
}
break;
default:
throw new TypeError(`Unknown kind ${ kind }, expected "pg" or "sequelize".`);
}
}
}
/**
* @see pgSetTypeParsers
*/
exports.default = pgSetTypeParsers;