const SchemaVersionHelper = require('./lib/schema-version-helper');
const PostgresCreator = require('./lib/postgres-creator');
const SQLiteCreator = require('./lib/sqlite-creator');
+const { validate } = require('./lib/validation');
module.exports = {
Abstract,
SchemaVersionHelper,
SQLiteCreator,
PostgresCreator,
+ validate,
};
const { fileScope } = require('@squeep/log-helper');
const DatabaseErrors = require('./errors');
const { schemaVersionObjectToNumber } = require('./schema-version-helper');
-const uuid = require('uuid');
const _fileScope = fileScope(__filename);
-const supportedValidationTypes = [
- 'array',
- 'bigint',
- 'boolean',
- 'buffer',
- 'date',
- 'function',
- 'infinites',
- 'null',
- 'number',
- 'object',
- 'string',
- 'symbol',
- 'undefined',
- 'uuid',
-];
-
/**
* An abstract database class skeleton.
*/
this._isProduction = process.env['NODE_ENV'] === 'production';
// istanbul ignore next
if (!this._isProduction) {
- // List of methods an engine needs to implement, used for engine test coverage.
+ // Configure metadata used by tests and extra validation when not in production.
+
+ // List of generic methods an engine needs to implement to reify this model.
this._abstractMethods = [
'schemaVersionsSupported',
'_currentSchema',
'context',
'transaction',
];
+
+ // List of methods specific to database interface.
+ this._interfaceMethods = [];
+
// List of tables for tests to purge during integration tests.
this._tableNames = [];
}
}
- /** @typedef {[String[], String[]]} ValidPropertiesTypes */
- /**
- * Check property types on an object.
- * @param {Object} object
- * @param {ValidPropertiesTypes[]} validations
- */
- _validate(object, validations) {
- validations.forEach(([properties, types]) => this._ensureTypes(object, properties, types));
- }
-
-
- /**
- * Sanity check for validation args.
- * @param {Object} object
- * @param {String[]} properties
- * @param {String[]} types
- */
- _ensureTypesArgsCheck(object, properties, types) {
- const _scope = _fileScope('_ensureTypesArgsCheck');
- const problemArguments = [];
- if (!object || typeof object !== 'object') {
- problemArguments.push('object');
- }
- if (!properties || !Array.isArray(properties)) {
- problemArguments.push('properties');
- }
- if (!types || !Array.isArray(types)) {
- problemArguments.push('types');
- }
- if (problemArguments.length) {
- throw new TypeError(`invalid argument ${problemArguments}`);
- }
- types.forEach((t) => {
- if (!this.constructor._supportedValidationTypes.includes(t)) {
- this.logger.error(_scope, 'unsupported type', { object, properties, types, unsupportedType: t });
- throw new TypeError(`unsupported type '${t}'`);
- }
- });
- }
-
-
- static get _supportedValidationTypes() {
- return supportedValidationTypes;
- }
-
- /**
- * Basic type checking of object properties.
- *
- * Types may be any of the built-in types:
- * - boolean
- * - bigint (also allowed with 'number')
- * - function
- * - number (this will also allow 'bigint')
- * - object
- * - string
- * - symbol
- * - undefined
- *
- * Types may also be any of the following:
- * - array
- * - buffer
- * - date
- * - infinites
- * - null
- * - uuid
- * @param {Object} object
- * @param {String[]} properties
- * @param {String[]} types
- */
- _ensureTypes(object, properties, types) {
- const _scope = _fileScope('_ensureTypes');
-
- // Only sanity-check args outside of prod
- this._isProduction || this._ensureTypesArgsCheck(object, properties, types);
-
- properties.forEach((p) => {
- // eslint-disable-next-line security/detect-object-injection
- const pObj = object[p];
- const pType = typeof pObj;
- if (!types.includes(pType)
- && !(types.includes('array') && Array.isArray(pObj))
- && !(types.includes('buffer') && pObj instanceof Buffer)
- && !(types.includes('date') && pObj instanceof Date)
- && !(types.includes('infinites') && Math.abs(pObj) === Infinity)
- && !(types.includes('null') && pObj === null)
- && !(types.includes('number') && pType === 'bigint')
- && !(types.includes('uuid') && uuid.validate(pObj))
- ) {
- const reason = `'${p}' is '${pType}', but must be ${types.length > 1 ? 'one of ' : ''}'${types}'`;
- this.logger.error(_scope, reason, {});
- throw new DatabaseErrors.DataValidation(reason);
- }
- });
- }
-
-
/**
* Interface methods need implementations. Ensure the db-interaction
* methods on the base class call this, so they may be overridden by
--- /dev/null
+'use strict';
+
+const { DataValidation } = require('./errors');
+const uuid = require('uuid');
+
+const supportedValidationTypes = [
+ 'array',
+ 'bigint',
+ 'boolean',
+ 'buffer',
+ 'date',
+ 'function',
+ 'infinites',
+ 'null',
+ 'number',
+ 'object',
+ 'string',
+ 'symbol',
+ 'undefined',
+ 'uuid',
+];
+
+/** @typedef {[String[], String[]]} ValidPropertiesTypes */
+/**
+ * Check property types on an object.
+ * @param {Object} object
+ * @param {ValidPropertiesTypes[]} validations
+ */
+function validate(object, validations) {
+ validations.forEach(([properties, types]) => ensureTypes(object, properties, types));
+}
+
+
+/**
+ * Sanity check for validation args.
+ * @param {Object} object
+ * @param {String[]} properties
+ * @param {String[]} types
+ */
+function _ensureTypesArgsCheck(object, properties, types) {
+ const problemArguments = [];
+ if (!object || typeof object !== 'object') {
+ problemArguments.push('object');
+ }
+ if (!properties || !Array.isArray(properties)) {
+ problemArguments.push('properties');
+ }
+ if (!types || !Array.isArray(types)) {
+ problemArguments.push('types');
+ }
+ if (problemArguments.length) {
+ throw new TypeError(`invalid argument ${problemArguments}`);
+ }
+ types.forEach((t) => {
+ if (!supportedValidationTypes.includes(t)) {
+ throw new TypeError(`unsupported type '${t}'`);
+ }
+ });
+}
+
+
+/**
+ * Basic type checking of object properties.
+ *
+ * Types may be any of the built-in types:
+ * - boolean
+ * - bigint (also allowed with 'number')
+ * - function
+ * - number (this will also allow 'bigint')
+ * - object
+ * - string
+ * - symbol
+ * - undefined
+ *
+ * Types may also be any of the following:
+ * - array
+ * - buffer
+ * - date
+ * - infinites
+ * - null
+ * - uuid
+ * @param {Object} object
+ * @param {String[]} properties
+ * @param {String[]} types
+ */
+function _ensureTypes(object, properties, types) {
+ properties.forEach((p) => {
+ const pObj = object[p]; // eslint-disable-line security/detect-object-injection
+ const pType = typeof pObj;
+ if (!types.includes(pType)
+ && !(types.includes('array') && Array.isArray(pObj))
+ && !(types.includes('buffer') && pObj instanceof Buffer)
+ && !(types.includes('date') && pObj instanceof Date)
+ && !(types.includes('infinites') && Math.abs(pObj) === Infinity)
+ && !(types.includes('null') && pObj === null)
+ && !(types.includes('number') && pType === 'bigint')
+ && !(types.includes('uuid') && uuid.validate(pObj))
+ ) {
+ const reason = `'${p}' is '${pType}', but must be ${types.length > 1 ? 'one of ' : ''}'${types}'`;
+ throw new DataValidation(reason);
+ }
+ });
+}
+
+// istanbul ignore next
+const ensureTypes = (process.env['NODE_ENV'] === 'production') ? _ensureTypes : (...args) => {
+ _ensureTypesArgsCheck(...args);
+ _ensureTypes(...args);
+};
+
+module.exports = {
+ supportedValidationTypes,
+ ensureTypes,
+ _ensureTypes,
+ _ensureTypesArgsCheck,
+ validate,
+};
\ No newline at end of file
}); // _camelfy
- describe('_ensureTypesArgsCheck', function () {
- it('fails for missing fields', function () {
- assert.throws(() => db._ensureTypesArgsCheck(), TypeError);
- });
- it('fails for invalid type', function () {
- const object = {};
- const properties = ['prop'];
- const types = ['bad type'];
- assert.throws(() => db._ensureTypesArgsCheck(object, properties, types), TypeError);
- });
- it('passes valid args', function () {
- const object = {};
- const properties = ['prop'];
- const types = ['string'];
- db._ensureTypesArgsCheck(object, properties, types);
- });
- }); // _ensureTypesArgsCheck
-
- describe('_ensureTypes / _validate', function () {
- let object;
- beforeEach(function () {
- object = {
- array: ['foo', 'bar'],
- bignum: BigInt(456),
- buf: Buffer.from('foop'),
- date: new Date(),
- infP: Infinity,
- infN: -Infinity,
- num: 123,
- obj: {},
- str: 'some words',
- uuid: 'a4dd5106-2d64-11ed-a2ba-0025905f714a',
- veryNull: null,
- };
- });
- it('succeeds', function () {
- db._ensureTypes(object, ['array'], ['array']);
- db._ensureTypes(object, ['bignum'], ['bigint']);
- db._ensureTypes(object, ['bignum', 'num'], ['number']);
- db._ensureTypes(object, ['buf'], ['buffer']);
- db._ensureTypes(object, ['date'], ['date']);
- db._ensureTypes(object, ['infP', 'infN'], ['infinites']);
- db._ensureTypes(object, ['str', 'veryNull'], ['string', 'null']);
- db._ensureTypes(object, ['uuid'], ['uuid']);
- });
- it('data failure', function () {
- assert.throws(() => db._ensureTypes(object, ['missingField'], ['string', 'null']), DataValidation);
- });
- it('failure covers singular', function () {
- try {
- db._ensureTypes(object, ['missingField'], ['string']);
- assert.fail('validation should have failed');
- } catch (e) {
- assert(e instanceof DataValidation);
- }
- });
- it('parameter failure', function () {
- try {
- db._ensureTypes(object, ['missingField'], undefined);
- assert.fail('validation should have failed');
- } catch (e) {
- assert(e instanceof TypeError);
- }
- });
- it('_validate succeeds', function () {
- db._validate(object, [
- [['array'], ['array']],
- [['bignum'], ['bigint']],
- [['bignum', 'num'], ['number']],
- [['buf'], ['buffer']],
- [['date'], ['date']],
- [['infP', 'infN'], ['infinites']],
- [['str', 'veryNull'], ['string', 'null']],
- [['uuid'], ['uuid']],
- ]);
- });
- it('_validate rejects', function () {
- assert.throws(() => db._validate({}, [
- [['array'], ['array']],
- [['bignum'], ['bigint']],
- [['bignum', 'num'], ['number']],
- [['buf'], ['buffer']],
- [['date'], ['date']],
- [['infP', 'infN'], ['infinites']],
- [['str', 'veryNull'], ['string', 'null']],
- [['uuid'], ['uuid']],
- ]), DataValidation);
- });
- }); // _ensureTypes / _validate
-
describe('_notImplemented', function () {
it('does what it says', function () {
assert.throws(() => db._notImplemented('method', []), NotImplemented);
--- /dev/null
+/* eslint-env mocha */
+/* eslint-disable sonarjs/no-duplicate-string */
+'use strict';
+
+const assert = require('node:assert');
+const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const { StubLogger } = require('../helpers');
+const Validation = require('../../lib/validation');
+const { DataValidation } = require('../../lib/errors');
+
+describe('Validation', function () {
+
+ describe('_ensureTypesArgsCheck', function () {
+ it('fails for missing fields', function () {
+ assert.throws(() => Validation._ensureTypesArgsCheck(), TypeError);
+ });
+ it('fails for invalid type', function () {
+ const object = {};
+ const properties = ['prop'];
+ const types = ['bad type'];
+ assert.throws(() => Validation._ensureTypesArgsCheck(object, properties, types), TypeError);
+ });
+ it('fails for wrong args', function () {
+ const object = 'foop';
+ const properties = 'prop';
+ const types = 'bad type';
+ assert.throws(() => Validation._ensureTypesArgsCheck(object, properties, types), TypeError);
+ });
+ it('passes valid args', function () {
+ const object = {};
+ const properties = ['prop'];
+ const types = ['string'];
+ Validation._ensureTypesArgsCheck(object, properties, types);
+ });
+ }); // _ensureTypesArgsCheck
+
+ describe('_ensureTypes / validate', function () {
+ let object;
+ beforeEach(function () {
+ object = {
+ array: ['foo', 'bar'],
+ bignum: BigInt(456),
+ buf: Buffer.from('foop'),
+ date: new Date(),
+ infP: Infinity,
+ infN: -Infinity,
+ num: 123,
+ obj: {},
+ str: 'some words',
+ uuid: 'a4dd5106-2d64-11ed-a2ba-0025905f714a',
+ veryNull: null,
+ };
+ });
+ it('succeeds', function () {
+ Validation._ensureTypes(object, ['array'], ['array']);
+ Validation._ensureTypes(object, ['bignum'], ['bigint']);
+ Validation._ensureTypes(object, ['bignum', 'num'], ['number']);
+ Validation._ensureTypes(object, ['buf'], ['buffer']);
+ Validation._ensureTypes(object, ['date'], ['date']);
+ Validation._ensureTypes(object, ['infP', 'infN'], ['infinites']);
+ Validation._ensureTypes(object, ['str', 'veryNull'], ['string', 'null']);
+ Validation._ensureTypes(object, ['uuid'], ['uuid']);
+ });
+ it('data failure', function () {
+ assert.throws(() => Validation._ensureTypes(object, ['missingField'], ['string', 'null']), DataValidation);
+ });
+ it('failure covers singular', function () {
+ assert.throws(() => Validation._ensureTypes(object, ['missingField'], ['string']), DataValidation);
+ });
+ it('parameter failure', function () {
+ assert.throws(() => Validation._ensureTypes(object, ['missingField'], undefined), TypeError);
+ });
+ it('validate succeeds', function () {
+ Validation.validate(object, [
+ [['array'], ['array']],
+ [['bignum'], ['bigint']],
+ [['bignum', 'num'], ['number']],
+ [['buf'], ['buffer']],
+ [['date'], ['date']],
+ [['infP', 'infN'], ['infinites']],
+ [['str', 'veryNull'], ['string', 'null']],
+ [['uuid'], ['uuid']],
+ ]);
+ });
+ it('validate rejects', function () {
+ assert.throws(() => Validation.validate({}, [
+ [['array'], ['array']],
+ [['bignum'], ['bigint']],
+ [['bignum', 'num'], ['number']],
+ [['buf'], ['buffer']],
+ [['date'], ['date']],
+ [['infP', 'infN'], ['infinites']],
+ [['str', 'veryNull'], ['string', 'null']],
+ [['uuid'], ['uuid']],
+ ]), DataValidation);
+ });
+ }); // _ensureTypes / validate
+
+}); // Validation