/** * Roboliq: Automation for liquid-handling robots * @copyright 2017, ETH Zurich, Ellis Whitehead * @license GPL-3.0 */ const _ = require('lodash'); const assert = require('assert'); // NOTE: with Babel 6 set to transpile to ES6, classes which extend Error are // not properly recognized as subclasses -- so "instanceof RoboliqError" won't // work. We need a number of work-arounds because of that. class RoboliqError extends Error { constructor(context = {}, errors, fnIgnore, stack) { super(getErrors(context, errors).join("; ")); this.isRoboliqError = true; this.name = "RoboliqError"; this.context = _.cloneDeep(context); this.errors = _.isArray(errors) ? errors : [errors]; if (_.isUndefined(stack)) { Error.captureStackTrace(this, fnIgnore || this.constructor.name); //Error.captureStackTrace(this); } else { this.stack = stack; } // console.log("new RoboliqError: "+this.__proto__) // console.log(this); // console.log(this.stack) } getPrefix() { return getPrefix(this.context); } toString() { return getErrors(context, errors).join("; "); } static getErrors(e) { return getErrors(e.context, e.errors); } } function getPrefix(context) { const prefixes = []; if (_.isPlainObject(context)) { if (_.isString(context.stepName)) prefixes.push(`steps.${context.stepName}`); if (_.isString(context.objectName)) prefixes.push(`objects.${context.objectName}`); if (_.isString(context.paramName)) prefixes.push(`parameter "${context.paramName}"`); else if (_.isArray(context.paramName)) prefixes.push(`parameters "${context.paramName.join('`, `')}"`); } return (prefixes.length > 0) ? prefixes.join(", ")+": " : ""; } function addContext(e, context) { _.mergeWith(e.context, context || {}, (a, b) => { if (_.isArray(a) && _.isArray(b)) { return a.concat(b); } }); } function getErrors(context, errors) { errors = _.isArray(errors) ? errors : [errors]; const prefix = getPrefix(context); return _.map(errors || [], s => prefix+s); } function _context(context, fn) { try { fn(); } catch (e) { // console.log("_context: "+e.__proto__) // console.log(JSON.stringify(e, null, '\t')) rethrow(e, context); } } function rethrow(e, context, fnIgnore = rethrow) { if (e instanceof Error) { if (e.isRoboliqError) { // console.log("rethrow1") addContext(e, context); throw e; } else { // console.log("rethrow2") const error = new RoboliqError(context, [e.message], undefined, e.stack); throw error; } } else if (typeof e === "string") { throw new RoboliqError({errors: [e]}, fnIgnore); } else { // console.log("rethrow4") throw new RoboliqError(context, [e.toString()], fnIgnore); } } /*function getContextPrefix(context) { const prefixes = []; if (!_.isEmpty(context)) { if (_.isString(context.paramName)) prefixes.push(`parameter "${context.paramName}"`); else if (_.isArray(context.paramName)) prefixes.push(`parameters "${context.paramName.join('`, `')}"`); if (_.isString(context.objectName)) prefixes.push(`object "${context.objectName}"`); } return (prefixes.length > 0) ? prefixes.join(", ")+": " : ""; }*/ /*function handleError(context, e) { var prefix = getContextPrefix(context); if (!e.trace) { if (e.stack) console.log(e.stack); e.trace = e.stack; } if (e.errors) { e.errors = _.map(e.errors, message => prefix+message); } else { e.name = "ProcessingError"; e.errors = _.compact([prefix+e.message]); } //console.log({epath: e.path, cpath: context.path}) if (!e.path && context.path) e.path = context.path; throw e; }*/ function truthy(context, result, message) { assert(message, "you must provide a `message` value"); if (!result) { if (_.isFunction(message)) message = message(); throw new RoboliqError(context, [message], truthy); } } function _try(context, fn) { try { return fn(); } catch (e) { rethrow(e, context, _try); } } function _throw(context, errors) { throw new RoboliqError(context, errors, _throw); } // TODO: get rid of this function after refactoring parameter processing in roboliqDirectives function paramsRequired(params, names) { assert(_.isPlainObject(params)); assert(_.isArray(names)); _.forEach(names, function(name) { truthy({paramName: name}, params.hasOwnProperty(name), "missing required value [CODE 135]"); }); } module.exports = { RoboliqError, context: _context, getPrefix, paramsRequired: paramsRequired, rethrow, throw: _throw, truthy: truthy, try: _try }