Skip to content

Commit

Permalink
Add modernErrors()
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jun 5, 2022
1 parent 51b502b commit 9091063
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 94 deletions.
11 changes: 5 additions & 6 deletions src/bin/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import { fileURLToPath } from 'url'
import { readPackageUp } from 'read-pkg-up'
import UpdateNotifier from 'update-notifier'

import { onError } from '../error/handler.js'
import { ErrorTypes, ERROR_PROPS } from '../error/main.js'
import { ERROR_PROPS } from '../error/main.js'
import { run, show, remove, dev } from '../main.js'
import { addPadding } from '../report/utils/indent.js'

import { parseCliFlags } from './parse.js'
// eslint-disable-next-line import/max-dependencies
import { defineCli } from './top.js'

// Parse CLI flags then execute commands.
Expand Down Expand Up @@ -56,10 +54,11 @@ const checkUpdate = async function () {
const COMMANDS = { run, show, remove, dev }

// Print CLI errors and exit, depending on the error type
// TODO: error normalization?
// TODO: enforce error types for bugs inside the CLI logic?
const handleCliError = function (error) {
const errorA = onError(error, ErrorTypes)
const { exitCode, printStack, indented } = ERROR_PROPS[errorA.name]
const errorMessage = printStack ? errorA.stack : errorA.message
const { exitCode, printStack, indented } = ERROR_PROPS[error.name]
const errorMessage = printStack ? error.stack : error.message
const errorMessageA = indented ? addPadding(errorMessage) : errorMessage
console.error(errorMessageA)
process.exitCode = exitCode
Expand Down
26 changes: 15 additions & 11 deletions src/config/normalize/lib/error.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { createErrorType } from '../../../error/create.js'
import { modernErrors } from '../../../error/modern.js'

// Invalid `inputs`
export const InputError = createErrorType('InputError')
// Invalid `rules` or `options`
export const DefinitionError = createErrorType('DefinitionError')
// Bug in a keyword|plugin
export const KeywordError = createErrorType('KeywordError')
// Bug in the library itself
export const CoreError = createErrorType('CoreError')
const { InputError, DefinitionError, KeywordError, CoreError, onError } =
modernErrors(['InputError', 'DefinitionError', 'KeywordError', 'CoreError'])

// All error types, with first being default type
export const ErrorTypes = [CoreError, InputError, DefinitionError, KeywordError]
export {
// Invalid `inputs`
InputError,
// Invalid `rules` or `options`
DefinitionError,
// Bug in a keyword|plugin
KeywordError,
// Bug in the library itself
CoreError,
// Top-level error handler
onError,
}
5 changes: 2 additions & 3 deletions src/config/normalize/lib/main.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { list } from 'wild-wild-path'

import { onError } from '../../../error/handler.js'
import { cleanObject } from '../../../utils/clean.js'

import { InputError, ErrorTypes } from './error.js'
import { InputError, onError } from './error.js'
import { getInfo } from './info.js'
import { applyKeywords } from './keywords/main.js'
import { normalizeOpts } from './options.js'
Expand Down Expand Up @@ -116,7 +115,7 @@ const LIST_OPTS = { childFirst: true, sort: true, missing: true, entries: true }
// When in `sort` mode, input errors are returned instead of being thrown.
// Other errors are always propagated.
const handleError = function (error, soft) {
const errorA = onError(error, ErrorTypes)
const errorA = onError(error)

if (soft && errorA instanceof InputError) {
return { error: errorA, warnings: [] }
Expand Down
26 changes: 15 additions & 11 deletions src/config/plugin/lib/error.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { createErrorType } from '../../../error/create.js'
import { modernErrors } from '../../../error/modern.js'

// Error from the library
export const CoreError = createErrorType('CoreError')
// Error from the library's user, who defines available plugin types
export const UserError = createErrorType('UserError')
// Error from a plugin author, who defines a specific plugin
export const PluginError = createErrorType('PluginError')
// Error from a plugin user
export const ConsumerError = createErrorType('ConsumerError')
const { CoreError, UserError, PluginError, ConsumerError, onError } =
modernErrors(['CoreError', 'UserError', 'PluginError', 'ConsumerError'])

// All error types, with first being default type
export const ErrorTypes = [CoreError, UserError, PluginError, ConsumerError]
export {
// Error from the library
CoreError,
// Error from the library's user, who defines available plugin types
UserError,
// Error from a plugin author, who defines a specific plugin
PluginError,
// Error from a plugin user
ConsumerError,
// Top-level error handler
onError,
}
8 changes: 3 additions & 5 deletions src/config/plugin/lib/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { onError } from '../../../error/handler.js'

import { validateDuplicatePlugins } from './duplicates.js'
import { UserError, ErrorTypes } from './error.js'
import { UserError, onError } from './error.js'
import { getPluginInfo } from './info.js'
import { normalizeMultipleOpts, normalizeSingleOpts } from './options.js'

Expand Down Expand Up @@ -32,7 +30,7 @@ export const getPlugins = async function (pluginConfigs, opts) {
validateDuplicatePlugins(pluginInfos, optsA)
return pluginInfos
} catch (error) {
throw onError(error, ErrorTypes)
throw onError(error)
}
}

Expand All @@ -57,7 +55,7 @@ export const getPlugin = async function (pluginConfig, opts) {
const plugin = await getPluginInfo(pluginConfig, optsA)
return plugin
} catch (error) {
throw onError(error, ErrorTypes)
throw onError(error)
}
}

Expand Down
10 changes: 0 additions & 10 deletions src/error/handler.js

This file was deleted.

43 changes: 27 additions & 16 deletions src/error/main.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { createErrorType } from './create.js'
import { modernErrors } from './modern.js'

// Bug in the library itself
export const CoreError = createErrorType('CoreError')
// Bug in a plugin (reporter|runner)
export const PluginError = createErrorType('PluginError')
// Invalid tasks or tasks file
export const UserCodeError = createErrorType('UserCodeError')
// Invalid options
export const UserError = createErrorType('UserError')
// `limit` option threshold was reached
export const LimitError = createErrorType('LimitError')
// User aborting the benchmark
export const StopError = createErrorType('StopError')
const {
CoreError,
PluginError,
UserCodeError,
UserError,
LimitError,
StopError,
onError,
} = modernErrors([
'CoreError',
'PluginError',
'UserCodeError',
'UserError',
'LimitError',
'StopError',
])

// All error types, with first being default type
export const ErrorTypes = [
export {
// Bug in the library itself
CoreError,
// Bug in a plugin (reporter|runner)
PluginError,
// Invalid tasks or tasks file
UserCodeError,
// Invalid options
UserError,
// `limit` option threshold was reached
LimitError,
// User aborting the benchmark
StopError,
]
// Top-level error handler
onError,
}

// Error type-specific behavior
export const ERROR_PROPS = {
Expand Down
31 changes: 31 additions & 0 deletions src/error/modern.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// eslint-disable-next-line n/file-extension-in-import, import/no-unassigned-import
import 'error-cause/auto'

import { createErrorType } from './create.js'
import { mergeErrorCause } from './merge/main.js'
import { allowErrorTypes } from './types.js'

// Create error types by passing an array of error names.
// Also returns an `onError(error) => error` function to use as a top-level
// error handler.
// Custom error `onCreate()` logic can be specified
// - To make it type-specific, an object of functions should be used, then
// `object[error.name]` should be used inside `onCreate()`
export const modernErrors = function (errorNames, onCreate) {
const ErrorTypes = Object.fromEntries(
errorNames.map((errorName) => [
errorName,
createErrorType(errorName, onCreate),
]),
)
const onErrorHandler = onError.bind(undefined, Object.values(ErrorTypes))
return { ...ErrorTypes, onError: onErrorHandler }
}

// Error handler that normalizes an error, merge its `error.cause` and ensure
// its type is among an allowed list of types.
const onError = function (ErrorTypes, error) {
const errorA = mergeErrorCause(error)
const errorB = allowErrorTypes(errorA, ErrorTypes)
return errorB
}
14 changes: 5 additions & 9 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
// eslint-disable-next-line n/file-extension-in-import, import/no-unassigned-import
import 'error-cause/auto'

import { listCombinations } from './combination/list.js'
import { getConfig } from './config/main.js'
import { performDev } from './dev/main.js'
import { onError } from './error/handler.js'
import { ErrorTypes } from './error/main.js'
import { onError } from './error/main.js'
import { checkLimits } from './history/compare/limit.js'
import { getFromHistory, removeFromHistory } from './history/data/main.js'
import { getTargetRawResults } from './history/merge/results.js'
Expand All @@ -31,7 +27,7 @@ export const run = async function (configFlags) {
checkLimits(programmaticResult)
return programmaticResult
} catch (error) {
throw onError(error, ErrorTypes)
throw onError(error)
}
}

Expand All @@ -44,7 +40,7 @@ export const show = async function (configFlags) {
checkLimits(programmaticResult)
return programmaticResult
} catch (error) {
throw onError(error, ErrorTypes)
throw onError(error)
}
}

Expand All @@ -58,7 +54,7 @@ export const remove = async function (configFlags) {
await removeFromHistory(targetRawResults, config)
return programmaticResult
} catch (error) {
throw onError(error, ErrorTypes)
throw onError(error)
}
}

Expand All @@ -69,6 +65,6 @@ export const dev = async function (configFlags) {
const combinations = await listCombinations(config)
await performDev(combinations, config)
} catch (error) {
throw onError(error, ErrorTypes)
throw onError(error)
}
}
48 changes: 26 additions & 22 deletions src/runners/common/error.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import { createErrorType } from '../../error/create.js'
import { onError } from '../../error/handler.js'
import { modernErrors } from '../../error/modern.js'

// Error from the library itself
export const CoreError = createErrorType('CoreError')
// Could not JSON-stringify IPC payload
export const IpcSerializationError = createErrorType('IpcSerializationError')
// Tasks file throws when loading
export const TasksLoadError = createErrorType('TasksLoadError')
// Tasks file has invalid syntax, e.g. exports invalid fields
export const TasksSyntaxError = createErrorType('TasksSyntaxError')
// Tasks throws when running
export const TasksRunError = createErrorType('TasksRunError')
// Invalid runner config
export const ConfigError = createErrorType('ConfigError')

// All error types, with first being default type
const ErrorTypes = [
const {
CoreError,
IpcSerializationError,
TasksLoadError,
TasksSyntaxError,
TasksRunError,
ConfigError,
]
onError,
} = modernErrors([
'CoreError',
'IpcSerializationError',
'TasksLoadError',
'TasksSyntaxError',
'TasksRunError',
'ConfigError',
])

// Serialize an error to send to parent
export const serializeError = function (error) {
const { name, message, stack } = onError(error, ErrorTypes)
return { name, message, stack }
export {
// Error from the library itself
CoreError,
// Could not JSON-stringify IPC payload
IpcSerializationError,
// Tasks file throws when loading
TasksLoadError,
// Tasks file has invalid syntax, e.g. exports invalid fields
TasksSyntaxError,
// Tasks throws when running
TasksRunError,
// Invalid runner config
ConfigError,
// Top-level error handler
onError,
}
8 changes: 7 additions & 1 deletion src/runners/common/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { argv } from 'process'

import got from 'got'

import { serializeError, IpcSerializationError } from './error.js'
import { IpcSerializationError, onError } from './error.js'

// Handles IPC communication with the parent process
export const handleEvents = async function (handlers) {
Expand Down Expand Up @@ -54,3 +54,9 @@ const safeSerializeBody = function (returnValue = {}) {
const serializeBody = function (returnValue) {
return JSON.stringify(returnValue)
}

// Serialize an error to send to parent
const serializeError = function (error) {
const { name, message, stack } = onError(error)
return { name, message, stack }
}

0 comments on commit 9091063

Please sign in to comment.