Skip to content

Commit

Permalink
Improve error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jun 5, 2022
1 parent 961b88b commit 4ce5967
Show file tree
Hide file tree
Showing 11 changed files with 52 additions and 57 deletions.
7 changes: 3 additions & 4 deletions src/config/normalize/lib/call/error.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { normalizeError } from '../../../../error/normalize/main.js'
import { wrapError } from '../../../../error/wrap.js'
import { InputError, DefinitionError, KeywordError } from '../error.js'

import {
Expand Down Expand Up @@ -46,7 +45,7 @@ const handleInputError = function ({
const errorA = addInputPrefix(error, prefix, originalName)
const errorB = addCurrentInput(errorA, input, hasInput)
const errorC = addExampleInput({ error: errorB, example, hasInput, test })
return wrapError(errorC, '', InputError)
return new InputError('', { cause: errorC })
}

const handleDefinitionError = function ({
Expand All @@ -67,7 +66,7 @@ const handleDefinitionError = function ({
})
const errorB = addCurrentDefinition(errorA, definition)
const errorC = addExampleDefinition(errorB, exampleDefinition)
return wrapError(errorC, '', DefinitionError)
return new DefinitionError('', { cause: errorC })
}

const handleKeywordError = function ({
Expand All @@ -82,7 +81,7 @@ const handleKeywordError = function ({
const errorA = addKeywordPrefix({ error, prefix, originalName, keyword })
const errorB = addCurrentDefinition(errorA, definition)
const errorC = addCurrentInput(errorB, input, hasInput)
return wrapError(errorC, '', KeywordError)
return new KeywordError('', { cause: errorC })
}

const ERROR_HANDLERS = {
Expand Down
17 changes: 7 additions & 10 deletions src/config/normalize/lib/call/prefix.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { wrapError } from '../../../../error/wrap.js'

// The `prefix` is the name of the type of property to show in error
// message and warnings such as "Option".
export const addInputPrefix = function (error, prefix, originalName) {
return wrapError(error, `${prefix} "${originalName}"`)
return new Error(`${prefix} "${originalName}":`, { cause: error })
}

export const addDefinitionPrefix = function ({
Expand All @@ -13,10 +11,10 @@ export const addDefinitionPrefix = function ({
keyword,
isValidation,
}) {
const suffix = isValidation ? 'definition' : 'must have a valid definition:\n'
return wrapError(
error,
const suffix = isValidation ? 'definition:' : 'must have a valid definition.'
return new Error(
`${prefix} "${originalName}"'s keyword "${keyword}" ${suffix}`,
{ cause: error },
)
}

Expand All @@ -26,8 +24,7 @@ export const addKeywordPrefix = function ({
originalName,
keyword,
}) {
return wrapError(
error,
`${prefix} "${originalName}"'s keyword "${keyword}" bug:`,
)
return new Error(`${prefix} "${originalName}"'s keyword "${keyword}" bug.`, {
cause: error,
})
}
14 changes: 6 additions & 8 deletions src/config/normalize/lib/call/suffix.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import { inspect } from 'util'

import { wrapError } from '../../../../error/wrap.js'

// Add the current definition as error suffix
export const addCurrentDefinition = function (error, definition) {
return definition === undefined
? error
: wrapErrorValue(error, 'Current definition', definition)
: addSuffix(error, 'Current definition', definition)
}

// Add the example definition as error suffix
export const addExampleDefinition = function (error, exampleDefinition) {
return exampleDefinition === undefined
? error
: wrapErrorValue(error, 'Example definition', exampleDefinition)
: addSuffix(error, 'Example definition', exampleDefinition)
}

// Add the current input as error suffix
export const addCurrentInput = function (error, input, hasInput) {
return hasInput ? wrapErrorValue(error, 'Current input', input) : error
return hasInput ? addSuffix(error, 'Current input', input) : error
}

// Add the example input as error suffix
export const addExampleInput = function ({ error, example, hasInput, test }) {
return example !== undefined && (hasInput || test !== undefined)
? wrapErrorValue(error, 'Example input', example)
? addSuffix(error, 'Example input', example)
: error
}

const wrapErrorValue = function (error, name, value) {
return wrapError(error, `\n${name}:${serializeValue(value)}`)
const addSuffix = function (error, name, value) {
return new Error(`${name}:${serializeValue(value)}`, { cause: error })
}

const serializeValue = function (value) {
Expand Down
13 changes: 6 additions & 7 deletions src/config/normalize/lib/keywords/list/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import ajvKeywords from 'ajv-keywords'
import Ajv from 'ajv/dist/2020.js'
import { decodePointer } from 'json-ptr'

import { wrapError } from '../../../../../error/wrap.js'

const normalize = function (definition) {
try {
return AJV.compile(definition)
} catch (error) {
throw wrapError(error, 'must be a valid JSON schema (version 2020).\n')
} catch {
const cause = getValidationError(AJV.errors)
throw new Error('must be a valid JSON schema (version 2020):\n', { cause })
}
}

Expand All @@ -34,17 +33,17 @@ const AJV = getAjv()

const main = function (validate, input) {
if (!validate(input)) {
throwValidationError(validate.errors)
throw getValidationError(validate.errors)
}
}

const throwValidationError = function (errors) {
const getValidationError = function (errors) {
// eslint-disable-next-line fp/no-mutating-methods
const message = [...errors].reverse().map(serializeAjvError).join(', ')
const messageA = message.startsWith('must')
? `${message}.`
: `must be valid: ${message}.`
throw new Error(messageA)
return new Error(messageA)
}

const serializeAjvError = function ({ instancePath, message }) {
Expand Down
10 changes: 4 additions & 6 deletions src/config/normalize/lib/keywords/list/transform.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import isPlainObj from 'is-plain-obj'
import { normalizePath } from 'wild-wild-parser'

import { wrapError } from '../../../../../error/wrap.js'

const normalize = function (definition) {
if (!isTransformMove(definition)) {
return { value: definition }
Expand All @@ -12,8 +10,8 @@ const normalize = function (definition) {

try {
return { value, newProp: normalizePath(newProp) }
} catch (error) {
throw wrapError(error, 'must return a valid "newProp":')
} catch (cause) {
throw new Error('must return a valid "newProp":\n', { cause })
}
}

Expand All @@ -30,8 +28,8 @@ const isTransformMove = function (definition) {

const main = function (definition, input) {
const { value } = definition
const { path = findCommonMove(value, input) } = definition
return { input: value, path }
const { newProp = findCommonMove(value, input) } = definition
return { input: value, path: newProp }
}

// Automatically detect some common type of moves
Expand Down
6 changes: 2 additions & 4 deletions src/config/normalize/lib/keywords/normalize/common.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { normalizePath } from 'wild-wild-parser'

import { wrapError } from '../../../../../error/wrap.js'

// Normalize string definition
export const normalizeString = function (definition) {
if (typeof definition !== 'string') {
Expand All @@ -24,8 +22,8 @@ export const normalizeBoolean = function (definition) {
export const normalizePropertyPath = function (definition) {
try {
return normalizePath(definition)
} catch (error) {
throw wrapError(error, 'must be a valid path:')
} catch (cause) {
throw new Error('must be a valid path:\n', { cause })
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/config/normalize/lib/keywords/normalize/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { inspect } from 'util'

import isPlainObj from 'is-plain-obj'

import { wrapError } from '../../../../../error/wrap.js'
import { DefinitionError } from '../../error.js'

import { validateName } from './name.js'
Expand All @@ -24,7 +23,7 @@ const validateKeyword = function (keyword, index, keywords) {

try {
validateKeywordProps(keyword, index, keywords)
} catch (error) {
throw wrapError(error, `Invalid keyword "${keyword.name}":`)
} catch (cause) {
throw new Error(`Invalid keyword "${keyword.name}":`, { cause })
}
}
5 changes: 2 additions & 3 deletions src/config/normalize/lib/keywords/path.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { normalizePath } from 'wild-wild-parser'

import { wrapError } from '../../../../error/wrap.js'
import { KeywordError } from '../error.js'
import { addMove } from '../move.js'

Expand All @@ -20,7 +19,7 @@ export const applyPath = function ({ path }, moves, { path: oldPath }) {
const safeNormalizePath = function (path) {
try {
return normalizePath(path)
} catch (error) {
throw wrapError(error, 'The "path" is invalid:', KeywordError)
} catch (cause) {
throw new KeywordError('The "path" is invalid:\n', { cause })
}
}
5 changes: 2 additions & 3 deletions src/config/normalize/lib/keywords/rename.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { serializePath, normalizePath } from 'wild-wild-parser'
import { remove } from 'wild-wild-path'

import { wrapError } from '../../../../error/wrap.js'
import { KeywordError } from '../error.js'
import { addMove } from '../move.js'

Expand Down Expand Up @@ -39,7 +38,7 @@ export const applyRename = function ({
const safeNormalizePath = function (rename) {
try {
return normalizePath(rename)
} catch (error) {
throw wrapError(error, 'The "rename" path is invalid:', KeywordError)
} catch (cause) {
throw new KeywordError('The "rename" path is invalid:\n', { cause })
}
}
22 changes: 16 additions & 6 deletions src/config/normalize/lib/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { list } from 'wild-wild-path'

import { mergeErrorCause } from '../../../error/merge/main.js'
import { allowErrorTypes } from '../../../error/types.js'
import { cleanObject } from '../../../utils/clean.js'

Expand All @@ -22,10 +23,10 @@ import { logWarnings } from './warn.js'
// - Makes it clear to users what the order is
// TODO: abstract this function to its own library
export const normalizeInputs = async function (inputs, rules, opts) {
const { soft, all, keywords, ruleProps, sync } = normalizeOpts(opts)
const rulesA = normalizeRules({ rules, all, ruleProps, sync })
const { soft, all, keywords, ruleProps, sync } = safeNormalizeOpts(opts)

try {
const rulesA = normalizeRules({ rules, all, ruleProps, sync })
const state = { inputs, moves: [], warnings: [] }
await applyRules({ rules: rulesA, state, keywords, sync })
const { inputs: inputsA, warnings } = state
Expand All @@ -37,6 +38,14 @@ export const normalizeInputs = async function (inputs, rules, opts) {
}
}

const safeNormalizeOpts = function (opts) {
try {
return normalizeOpts(opts)
} catch (error) {
return handleError(error, false)
}
}

const applyRules = async function ({
rules: { items, parallel },
state,
Expand Down Expand Up @@ -111,11 +120,12 @@ const applyRule = async function ({
// When in `sort` mode, input errors are returned instead of being thrown.
// Other errors are always propagated.
const handleError = function (error, soft) {
const errorA = allowErrorTypes(error, ErrorTypes)
const errorA = mergeErrorCause(error)
const errorB = allowErrorTypes(errorA, ErrorTypes)

if (soft && errorA instanceof InputError) {
return { error: errorA, warnings: [] }
if (soft && errorB instanceof InputError) {
return { error: errorB, warnings: [] }
}

throw errorA
throw errorB
}
5 changes: 2 additions & 3 deletions src/config/normalize/lib/rule/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { inspect } from 'util'
import isPlainObj from 'is-plain-obj'
import { normalizeQuery } from 'wild-wild-parser'

import { wrapError } from '../../../../error/wrap.js'
import { DefinitionError } from '../error.js'

import { validateRuleProps } from './validate.js'
Expand All @@ -30,7 +29,7 @@ const normalizeName = function (rule) {

try {
return normalizeQuery(rule.name)
} catch (error) {
throw wrapError(error, 'Invalid "name":', DefinitionError)
} catch (cause) {
throw new DefinitionError('Invalid "name":\n', { cause })
}
}

0 comments on commit 4ce5967

Please sign in to comment.