UNPKG

config-srv

Version:

API and REST interface for editing a structured set of parameters

279 lines (253 loc) 7.78 kB
/* eslint-disable class-methods-use-this, max-len */ const path = require('path'); const __ = require('./lib.js'); const standardTypes = require('./types.js'); const { DEFAULT_SCHEMA_DIR, DEFAULT_CONFIG_DIR } = require('./constants.js'); const ConfigServiceError = require('./ConfigServiceError.js'); module.exports = class Utils { constructor (serviceOptions = {}) { const { logger, userTypes, debug } = serviceOptions; /** * @param {LoggerEx} [logger] - An object that provides the 'mErr' method, * which prints an error message to the console */ this.debug = debug; this.logger = logger; this.registerTypes(userTypes); this.lib = __; } // eslint-disable-next-line no-empty-function async init () { } /** * Deep equality function for non-primitive arguments * * @param value1 * @param value2 * @returns {boolean} */ deepEqual (value1, value2) { return __.deepEqual(value1, value2); } /** * Returns the full path to the directory from where the service will take the Schema file * @return {String} */ static getSchemaDir () { let schemaDir = __.getCmdOrEnv( 'NODE_CONFIG_SERVICE_SCHEMA_DIR', path.join(process.cwd(), DEFAULT_SCHEMA_DIR), ); if (schemaDir.indexOf('.') === 0) { schemaDir = path.join(process.cwd(), schemaDir); } return schemaDir; } /** * Returns the full path to the directory from where the service will take named configuration files * @return {string} */ static getConfigDir () { const configDir = __.getCmdOrEnv('NODE_CONFIG_SERVICE_DIR', DEFAULT_CONFIG_DIR); if (/[\\/]/.test(configDir) && path.isAbsolute(configDir)) { return path.resolve(configDir); } return path.resolve(Utils.getSchemaDir() + path.sep + configDir); } // =============================== UTILS ================================= _expectedPath (path_) { return path_.replace(process.cwd(), '<proj_root>').replace(/\\/g, '/'); } /** * Return an 'ConfigServiceError' error object * writes an error message to the console and to the log (if there is an error logger object) * * @param {String} msg * @param {Error} [err] * @return {ConfigServiceError} */ _error (msg, err = null) { return new ConfigServiceError(msg, err, this); } /** * Clearing cache require */ _deleteRequireCacheFor (src) { if (src.indexOf('.') === 0) { src = path.resolve(path.join(process.cwd(), src)); } let resolved = src; try { resolved = require.resolve(src); } catch (e) { // } delete require.cache[resolved]; } /** * Normalizes json by removing undefined properties * * @param {*} json * @param {String} propPath * @return {*} - normalized value */ _normalizeJSON (json, propPath = null) { if (!json) { return json; } try { return JSON.parse(JSON.stringify(json)); } catch (err) { const msg = propPath ? ` for parameter «${propPath}»` : ''; throw this._error(`Cannot normalize JSON value${msg}. Error: ${err.message}`, err); } } // =============================== SCHEMA ITEM TYPES ================================= registerTypes (userTypes) { const types = __.cloneDeep(standardTypes); if (__.isNonEmptyObject(userTypes)) { __.each(userTypes, ({ jsTypes = ['any'], validator = () => {} }, userTypeName) => { if (jsTypes && Array.isArray(jsTypes)) { jsTypes = jsTypes.filter((v) => ['any', 'null', 'object', 'array', 'string', 'number', 'boolean'].includes(v)); if (jsTypes.length) { if (!__.hasProp(types, userTypeName)) { types[userTypeName] = {}; } types[userTypeName].jsTypes = jsTypes; } } if (validator && typeof validator === 'function') { if (!__.hasProp(types, userTypeName)) { types[userTypeName] = {}; } types[userTypeName].validator = validator; } if (types[userTypeName] && !types[userTypeName].validator) { types[userTypeName].validator = ( newValue, defaultValue = null, ) => (newValue === undefined ? defaultValue : newValue); } }); } this.types = types; } /** * Checking the correspondence between the type of real data and the type from the Schema * * @param {String} realType * @param {String} schemaDataType * @return {boolean} */ _validateType (realType, schemaDataType) { if (realType === 'undefined') { return true; } if (!realType || !schemaDataType) { return false; } const { jsTypes } = this.types[schemaDataType] || {}; if (!jsTypes) { return false; } return !!jsTypes && (jsTypes.includes('any') || jsTypes.includes(realType)); } /** * Checks if the schema data type is registered * * @param {String} schemaDataType * @return {boolean} */ _schemaDataTypeExists (schemaDataType) { return __.hasProp(this.types, schemaDataType); } /** * Detects the actual data type. * * @param {*} value * @return {String} */ _detectRealType (value) { if (value === null) { return 'null'; } let type = typeof value; if (type === 'object' && Array.isArray(value)) { type = 'array'; } return type; } /** * Checking the correspondence between the type of real data and the type from the Schema * * @private * @param {*} realValue * @param {String} schemaDataType * @return {boolean} */ _validateValueByType (realValue, schemaDataType) { return this._validateType(this._detectRealType(realValue), schemaDataType); } /** * Returns a list of expected types for the passed schema data type * Used in error messages * * @param {schemaDataType} schemaDataType * @return {String} */ _getExpectedRealTypesString (schemaDataType) { const { jsTypes } = this.types[schemaDataType] || {}; if (!jsTypes) { throw this._error(`Invalid type «${schemaDataType}» passed in function «_getExpectedRealTypesString»`); } return `Expected: «${jsTypes.join(',')}»`; } /** * Parses the path to the parameter, checking and normalizing it. * Returns the path as a string representation and as an array. * * @param {propPathType} paramPath * @param {Object} options * @return {Object} */ _parseParamPathFragment (paramPath, options = {}) { options.callFrom = options.callFrom || '_parseParamPathFragment'; paramPath = paramPath || ''; const isValid = typeof paramPath === 'string' || (Array.isArray(paramPath) && !paramPath.some((v) => typeof v !== 'string')); if (!isValid) { throw this._error(`Parameter path is not a string or array of strings: «${String(paramPath)}». Function «${options.callFrom}»`); } let pathArr; if (typeof paramPath === 'string') { pathArr = paramPath.split('.').filter((v) => `${v}`.trim()); } else { pathArr = paramPath; paramPath = pathArr.filter((v) => `${v}`.trim()).join('.'); } const configName = pathArr.length ? pathArr[0] : ''; const pathParent = [...pathArr]; const lastParamName = pathParent.length ? pathParent.pop() : ''; return { paramPath, pathArr, configName, pathParent, lastParamName, }; } /** * Expose method cloneDeep from lib. */ cloneDeep (obj, options) { return __.cloneDeep(obj, options); } /** * Expose method cloneDeep from lib. */ log (msg) { // eslint-disable-next-line no-console console.log(`\x1b[94m[config-service]:\x1b[0m${msg}`); } };