bajo
Version:
The ultimate framework for whipping up massive apps in no time
139 lines (128 loc) • 4.13 kB
JavaScript
import lodash from 'lodash'
import Tools from './tools.js'
const { isPlainObject, each, isArray, get, isEmpty, merge } = lodash
Error.stackTraceLimit = 15
/**
* Bajo error class, a thin wrapper of node's Error object.
*
* Every Bajo {@link Plugin|plugin} has a built-in method called ```error``` which basically the shortcut to create a new Err instance.
* It helps you create this instance anywhere in your code quickly without the hassle of importing & instantiating:
*
* ```javascript
* ... anywhere inside your code
* if (notfound) throw this.error('Sorry, item is nowhere to be found!')
* ```
*/
class Err extends Tools {
/**
* @param {Plugin} plugin - Plugin instance
* @param {string} msg - Error message
* @param {...any} [args] - Variables to interpolate with error message. Payload object can be pushed at the very last argument
*/
constructor (plugin, msg, ...args) {
super(plugin)
/**
* Error payload extracted from the last arguments
* @type {Object}
*/
this.payload = args.length > 0 && isPlainObject(args[args.length - 1]) ? args[args.length - 1] : {}
/**
* Original message before translation
* @type {string}
*/
this.orgMessage = msg
/**
* Translated message
* @type {string}
*/
this.message = this.payload.noTrans ? msg : this.plugin.t(msg, ...args)
}
/**
* Write message to the console
*
* @method
* @returns {Err} Error object, usefull for chaining
*/
write = () => {
let err
if (this.payload.class) err = this.payload.class(this.message)
else err = Error(this.message)
delete this.payload.class
const stacks = err.stack.split('\n')
stacks.splice(1, 1)
stacks.splice(1, 1)
err.stack = stacks.join('\n')
const values = {}
let detailsMessage
for (const key in this.payload) {
const value = this.payload[key]
if (key === 'details' && isArray(value)) {
const { result, detailsMessage: dm } = this.formatErrorDetails(value)
if (!isEmpty(dm)) detailsMessage = dm
if (result) merge(values, result)
}
err[key] = value
}
if (!isEmpty(values)) err.values = values
err.ns = this.plugin.ns
err.orgMessage = this.orgMessage
if (detailsMessage) err.detailsMessage = detailsMessage
return err
}
/**
* Print instance on console and terminate process
*
* @method
*/
fatal = () => {
const err = this.write()
console.error(err)
this.app.exit(true)
}
/**
* Pretty format error details
*
* @method
* @param {Object} value - Value to format
* @returns {Object}
*/
formatErrorDetails = (value) => {
const { isString, last } = this.app.lib._
const { without } = this.app.lib.aneka
const result = {}
const me = this
const detailsMessage = []
each(value, (v, i) => {
if (isString(v)) v = { error: v }
if (!v.context) {
v.error = me.plugin.t(v.error)
return undefined
}
v.context.message = v.message
if (v.type === 'any.only') {
const items = without(get(v, 'context.valids', []))
v.context.ref = items.join(', ')
}
const field = get(v, 'context.key')
const val = get(v, 'context.value')
let error = me.plugin.t(v.message[0] === '~' ? v.message.slice(1) : `validation.${v.type}`, v.context ?? {}, {})
if (error.includes(' ref:')) {
const item = last(error.split(' '))
const [, rfield] = item.split(':')
error = error.replace(item, `'${me.plugin.t('field.' + rfield)}'`)
}
value[i] = {
field,
error,
value: val,
ext: { type: v.type, context: v.context }
}
detailsMessage.push(me.plugin.t('fieldError%s%s', field, value[i].error))
})
return {
result,
detailsMessage: detailsMessage.length > 0 ? (me.plugin.t('error') + ': ' + detailsMessage.join(', ')) : ''
}
}
}
export default Err