Skip to content

Commit

Permalink
Refactoring of validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 30, 2022
1 parent 4356518 commit ff7ceaf
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 32 deletions.
48 changes: 31 additions & 17 deletions src/config/normalize/lib/call.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,15 @@ const handleValueError = function (error, value) {
// Errors add the property `name` as prefix.
export const callUserFunc = async function (
userFunc,
{ validate = false, prefix, ...opts },
{ validate, prefix, ...opts },
) {
try {
return await maybeFunction(userFunc, opts)
} catch (error) {
handleValidateError(error, validate)
throw wrapError(
error,
`Configuration property "${getPrefix(prefix, opts.name)}" `,
)
throw handleValidateError(error, { ...opts, validate, prefix })
}
}

const getPrefix = function (prefix, name) {
if (prefix === '') {
return name
}

return prefix.endsWith('.') ? `${prefix}${name}` : `${prefix}.${name}`
}

// Consumers can distinguish users errors from system bugs by checking
// the `error.validation` boolean property.
// User errors require both:
Expand All @@ -66,12 +54,38 @@ export const callValidateFunc = async function (validate, value, opts) {
await callValueFunc(validate, value, { ...opts, validate: true })
}

const handleValidateError = function (error, validate) {
if (validate && isValidateError(error)) {
error.validation = true
// Throw a user validation error
export const throwValidateError = function (message, opts) {
const error = new Error(message)
setValidationProp(error)
throw addPropPrefix(error, opts)
}

const handleValidateError = function (error, opts) {
if (opts.validate && isValidateError(error)) {
setValidationProp(error)
}

return addPropPrefix(error, opts)
}

const isValidateError = function (error) {
return error instanceof Error && error.message.startsWith('must')
}

const setValidationProp = function (error) {
error.validation = true
}

const addPropPrefix = function (error, opts) {
const propName = getPropName(opts)
return wrapError(error, `Configuration property "${propName}" `)
}

const getPropName = function ({ prefix, name }) {
if (prefix === '') {
return name
}

return prefix.endsWith('.') ? `${prefix}${name}` : `${prefix}.${name}`
}
33 changes: 19 additions & 14 deletions src/config/normalize/lib/definitions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { callValueFunc, callUserFunc, callValidateFunc } from './call.js'
import {
callValueFunc,
callUserFunc,
callValidateFunc,
throwValidateError,
} from './call.js'
import { resolvePath } from './path.js'

export const applyDefinition = async function (
Expand Down Expand Up @@ -76,35 +81,35 @@ const applyValidateTransform = async function ({
transform,
opts,
}) {
if (value === undefined) {
await validateRequired(required, opts)
return value
}

const valueA = await resolvePath(value, { path, cwd, glob, opts })
await validateRequired(valueA, required, opts)
await validateValue(valueA, validate, opts)
const valueB = await transformValue(valueA, transform, opts)
return valueB
}

// Apply `required(value, opts)` which throws if `true` and value is `undefined`
const validateRequired = async function (value, required, opts) {
if (value === undefined && (await callValueFunc(required, value, opts))) {
await callValidateFunc(throwRequired, value, opts)
// Apply `required(opts)` which throws if `true` and value is `undefined`
const validateRequired = async function (required, opts) {
if (await callUserFunc(required, opts)) {
throwValidateError('must be defined.', opts)
}
}

const throwRequired = function () {
throw new Error('must be defined.')
}

// Apply `validate(value, opts)` which throws on validation errors
const validateValue = async function (value, validate, opts) {
if (value !== undefined && validate !== undefined) {
if (validate !== undefined) {
await callValidateFunc(validate, value, opts)
}
}

// Apply `transform(value, opts)` which transforms the value set by the user.
// If can also delete it by returning `undefined`.
const transformValue = async function (value, transform, opts) {
return value !== undefined && transform !== undefined
? await callValueFunc(transform, value, opts)
: value
return transform === undefined
? value
: await callValueFunc(transform, value, opts)
}
2 changes: 1 addition & 1 deletion src/config/normalize/lib/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { callValueFunc, callValidateFunc } from './call.js'
// to ensure it is evaluated at runtime.
// An empty `cwd` is same as `.`
export const resolvePath = async function (value, { path, cwd, glob, opts }) {
if (value === undefined || !(await callValueFunc(path, value, opts))) {
if (!(await callValueFunc(path, value, opts))) {
return value
}

Expand Down

0 comments on commit ff7ceaf

Please sign in to comment.