Skip to content

Commit

Permalink
Refactor normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 16, 2022
1 parent b05433b commit 9d0200a
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 70 deletions.
91 changes: 37 additions & 54 deletions src/config/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,6 @@ import mapObj from 'map-obj'
import { UserError } from '../error/main.js'

// Configuration validation helper functions
export const checkObject = function (value, name) {
if (!isPlainObj(value)) {
throw new UserError(
`'${name}' value must be a plain object: ${inspect(value)}`,
)
}
}

export const checkBoolean = function (value, name) {
if (typeof value !== 'boolean') {
throw new UserError(`'${name}' must be true or false: ${inspect(value)}`)
}
}

export const checkInteger = function (value, name) {
if (!Number.isInteger(value)) {
throw new UserError(`'${name}' must be an integer: ${inspect(value)}`)
}
}

// Many array configuration properties can optionally a single element, for
// simplicity providing it is the most common use case.
// We also allow duplicate values but remove them.
export const normalizeOptionalArray = function (value = []) {
return Array.isArray(value) ? [...new Set(value)] : [value]
}

export const checkArrayLength = function (value, name) {
if (value.length === 0) {
throw new UserError(`At least one '${name}' must be defined.`)
}
}

const checkArray = function (value, name) {
if (!Array.isArray(value)) {
throw new UserError(`'${name}' must be an array: ${inspect(value)}`)
}
}

export const checkArrayItems = function (checkers, value, name) {
checkArray(value, name)
return value.map((item, index) =>
Expand All @@ -59,9 +20,10 @@ const getIndexName = function (name, index, value) {

export const checkObjectProps = function (checkers, value, name) {
checkObject(value, name)
return mapObj(value, (childName, childValue) =>
return mapObj(value, (childName, childValue) => [
childName,
applyCheckers(checkers, childValue, `${name}.${childName}`),
)
])
}

const applyCheckers = function (checkers, value, name) {
Expand All @@ -76,6 +38,18 @@ const applyChecker = function (checker, value, name) {
return newValue === undefined ? value : newValue
}

export const checkBoolean = function (value, name) {
if (typeof value !== 'boolean') {
throw new UserError(`'${name}' must be true or false: ${inspect(value)}`)
}
}

export const checkInteger = function (value, name) {
if (!Number.isInteger(value)) {
throw new UserError(`'${name}' must be an integer: ${inspect(value)}`)
}
}

export const checkString = function (value, name) {
if (typeof value !== 'string') {
throw new UserError(`'${name}' must be a string: ${inspect(value)}`)
Expand All @@ -88,25 +62,34 @@ export const checkDefinedString = function (value, name) {
}
}

export const checkStringsObject = function (value, name) {
Object.entries(value).forEach(([childName, propValue]) => {
checkString(propValue, `${name}.${childName}`)
})
const checkArray = function (value, name) {
if (!Array.isArray(value)) {
throw new UserError(`'${name}' must be an array: ${inspect(value)}`)
}
}

// Many array configuration properties can optionally a single element, for
// simplicity providing it is the most common use case.
// We also allow duplicate values but remove them.
export const normalizeOptionalArray = function (value = []) {
return Array.isArray(value) ? [...new Set(value)] : [value]
}

export const checkJsonDeepObject = function (value, name) {
Object.entries(value).forEach(([childName, propValue]) => {
checkJsonObject(propValue, `${name}.${childName}`)
})
export const checkArrayLength = function (value, name) {
if (value.length === 0) {
throw new UserError(`At least one '${name}' must be defined.`)
}
}

export const checkJsonObject = function (value, name) {
Object.entries(value).forEach(([childName, propValue]) => {
checkJson(propValue, `${name}.${childName}`)
})
export const checkObject = function (value, name) {
if (!isPlainObj(value)) {
throw new UserError(
`'${name}' value must be a plain object: ${inspect(value)}`,
)
}
}

const checkJson = function (value, name) {
export const checkJson = function (value, name) {
if (!isJson(value)) {
throw new UserError(
`'${name}' must only contain strings, numbers, booleans, nulls, arrays or plain objects: ${inspect(
Expand Down
18 changes: 5 additions & 13 deletions src/config/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import {
normalizeOptionalArray,
checkArrayItems,
checkArrayLength,
checkObjectProps,
checkDefinedString,
checkObject,
checkStringsObject,
checkJsonObject,
checkJson,
} from './check.js'
import { validateConfigSelector, isConfigSelector } from './select/normalize.js'

Expand Down Expand Up @@ -65,15 +64,8 @@ const applyNormalizer = function (value, name, normalizer) {
return newValue === undefined ? value : newValue
}

const validateTitles = function (value, name) {
Object.entries(value).forEach(([childName, propValue]) => {
checkString(propValue, `${name}.${childName}`)
checkDefinedString(propValue, `${name}.${childName}`)
})
}

const NORMALIZERS = {
inputs: [checkObject, checkJsonObject],
inputs: [checkObjectProps.bind(undefined, [checkJson])],
limit: [checkInteger, normalizeLimit],
merge: [validateMerge],
outliers: [checkBoolean],
Expand All @@ -94,10 +86,10 @@ const NORMALIZERS = {
showDiff: [checkBoolean],
showPrecision: [checkBoolean],
showTitles: [checkBoolean],
system: [checkObject, checkStringsObject],
system: [checkObjectProps.bind(undefined, [checkString])],
tasks: [
normalizeOptionalArray,
checkArrayItems.bind(undefined, [checkString, checkDefinedString]),
],
titles: [validateTitles],
titles: [checkObjectProps.bind(undefined, [checkString, checkDefinedString])],
}
5 changes: 2 additions & 3 deletions src/config/plugin/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cleanObject } from '../../utils/clean.js'
import { pick } from '../../utils/pick.js'
import { checkObject, checkJsonDeepObject } from '../check.js'
import { checkObjectProps, checkJson } from '../check.js'
import { mergeConfigs } from '../merge/main.js'

// Retrieve plugin configuration object.
Expand Down Expand Up @@ -47,8 +47,7 @@ export const addPluginsConfig = function ({
topProps,
}) {
const pluginsConfig = config[configProp]
checkObject(pluginsConfig, configProp)
checkJsonDeepObject(pluginsConfig, configProp)
checkObjectProps(checkObjectProps([checkJson], pluginsConfig, configProp))
return plugins.map((plugin) =>
addPluginConfig({ plugin, pluginsConfig, config, topProps }),
)
Expand Down

0 comments on commit 9d0200a

Please sign in to comment.