vnopts
Version:
validate and normalize options
538 lines (406 loc) • 12 kB
Markdown
# vnopts
[](https://www.npmjs.com/package/vnopts)
[](https://github.com/ikatyang/vnopts/actions?query=branch%3Amain)
validate and normalize options
[Changelog](https://github.com/ikatyang/vnopts/blob/main/CHANGELOG.md)
## Install
```sh
npm install vnopts
```
## Usage
```js
import * as vnopts from 'vnopts'
const schemas = [
vnopts.ChoiceSchema.create({
name: 'parser',
choices: ['babylon', 'flow', 'typescript'],
}),
vnopts.BooleanSchema.create({
name: 'useFlowParser',
deprecated: true,
redirect: value =>
!value ? undefined : { to: { key: 'parser', value: 'flow' } },
}),
]
vnopts.normalize({ useFlowParser: true }, schemas)
//=> { parser: "flow" }
// warning: `useFlowParser` is deprecated, we now treat it as `{ parser: "flow" }`.
vnopts.normalize({ parser: 'none' }, schemas)
//=> error: Invalid `parser` value. Expected `"babylon", "flow" or "typescript"`, but received `"none"`.
vnopts.normalize({ parserr: 'typescript' }, schemas)
//=> {}
// warning: Ignored unknown option `{ parserr: "typescript" }`. Did you mean `parser`?
```
## API
### Normalizer
```ts
/** a shortcut for `new Normalizer(...).normalize(...)` */
function normalize(
options: object,
schemas: Schema[],
opts?: NormalizerOptions,
): object
```
```ts
class Normalizer {
constructor(schemas: Schema[], opts?: NormalizerOptions)
/** normalize the options based on schemas */
normalize(options: object): object
/** clear the deprecation warning history so as to show the same warning again */
cleanHistory(): void
}
```
```ts
interface NormalizerOptions {
logger?: Logger | false
loggerPrintWidth?: number
descriptor?: Descriptor
unknown?: UnknownHandler
invalid?: InvalidHandler
deprecated?: DeprecatedHandler
missing?: IdentifyMissing
required?: IdentifyRequired
preprocess?: (options: Options, utils: Utils) => Options
postprocess?: (
options: Options,
utils: Utils,
) => typeof VALUE_UNCHANGED | { delete?: string[]; override?: Options }
}
```
#### Logger
Defaults to `console`.
```ts
interface Logger {
warn(message: string): void
}
```
#### Descriptor
Defaults to [`apiDescriptor`](https://github.com/ikatyang/vnopts/blob/main/src/descriptors/api.ts).
```ts
interface Descriptor {
key: (key: string) => string
value: (value: any) => string
pair: (pair: { key: string; value: any }) => string
}
```
#### UnknownHandler
Defaults to [`levenUnknownHandler`](https://github.com/ikatyang/vnopts/blob/main/src/handlers/unknown/leven.ts).
```ts
type UnknownHandler = (key: string, value: any, utils: Utils) => void | object
```
The returned object will be merged into the output object (and validate its value if the key is known).
#### InvalidHandler
Defaults to [`commonInvalidHandler`](https://github.com/ikatyang/vnopts/blob/main/src/handlers/invalid/common.ts).
```ts
type InvalidHandler = (
key: string,
value: OptionValue,
utils: Utils,
) => string | Error
```
Returns an error message or the error itself.
#### DeprecatedHandler
Defaults to [`commonDeprecatedHandler`](https://github.com/ikatyang/vnopts/blob/main/src/handlers/deprecated/common.ts).
```ts
type DeprecatedHandler = (
keyOrPair: string | { key: string; value: any },
redirectToKeyOrPair: undefined | string | { key: string; value: any },
utils: Utils,
) => string
```
Returns a deprecation warning.
#### IdentifyMissing
Defaults to `() => false`.
```ts
type IdentifyMissing = (key: string, options: Options) => boolean
```
Returns a boolean to indicate if `key` is _missing_ in `options`.
(`!(key in options)` is always considered missing.)
#### IdentifyRequired
Defaults to `() => false`.
```ts
type IdentifyRequired = (key: string) => boolean
```
Returns a boolean to indicate if `key` is required in the output.
### Schemas
#### AnySchema
```ts
interface AnySchemaParameters extends SchemaHandlers {
name: string
}
```
```js
const schemas = [vnopts.AnySchema.create({ name: 'any' })]
vnopts.normalize({ any: 'hello world' }, schemas)
//=> { any: "hello world" }
vnopts.normalize({ unknown: 'hello world' }, schemas)
//=> {}
// warning: Ignored unknown option `{ unknown: "hello world" }`.
```
#### BooleanSchema
```ts
interface BooleanSchemaParameters extends SchemaHandlers {
name: string
}
```
```js
const schemas = [vnopts.BooleanSchema.create({ name: 'bool' })]
vnopts.normalize({ bool: true }, schemas)
//=> { bool: true }
vnopts.normalize({ bool: 'hello world' }, schemas)
// error: Invalid `bool` value. Expected `true or false`, but received `"hello world"`.
```
#### NumberSchema
```ts
interface NumberSchemaParameters extends SchemaHandlers {
name: string
}
```
```js
const schemas = [vnopts.NumberSchema.create({ name: 'num' })]
vnopts.normalize({ num: 1 }, schemas)
//=> { num: 1 }
vnopts.normalize({ num: null }, schemas)
// error: Invalid `num` value. Expected `a number`, but received `null`.
```
#### IntegerSchema
```ts
interface IntegerSchemaParameters extends SchemaHandlers {
name: string
}
```
```js
const schemas = [vnopts.IntegerSchema.create({ name: 'int' })]
vnopts.normalize({ int: 1 }, schemas)
//=> { int: 1 }
vnopts.normalize({ int: 1.5 }, schemas)
// error: Invalid `int` value. Expected `an integer`, but received `1.5`.
```
#### StringSchema
```ts
interface StringSchemaParameters extends SchemaHandlers {
name: string
}
```
```js
const schemas = [vnopts.StringSchema.create({ name: 'str' })]
vnopts.normalize({ str: 'hi' }, schemas)
//=> { str: "hi" }
vnopts.normalize({ str: true }, schemas)
// error: Invalid `str` value. Expected `a string`, but received `true`.
```
#### ChoiceSchema
```ts
interface ChoiceSchemaParameters extends SchemaHandlers {
name: string
choices: Array<
| undefined
| null
| boolean
| number
| string
| {
value: undefined | null | boolean | number | string
deprecated?: boolean
hidden?: boolean // do not show this value in `expected`
redirect?: /* key */ string | { key: string; value: any }
forward?: /* key */ string | { key: string; value: any }
}
>
}
```
```js
const schemas = [
vnopts.ChoiceSchema.create({ name: 'choice', choices: [2, false, 'hey'] }),
]
vnopts.normalize({ choice: 2 }, schemas)
//=> { choice: 2 }
vnopts.normalize({ choice: true }, schemas)
// error: Invalid `choice` value. Expected `false, 2 or "hey"`, but received `true`.
```
#### AliasSchema
`AliasSchema` validates values using the validator from the source schema and redirects all the value to the source key.
```ts
interface AliasSchemaParameters extends SchemaHandlers {
name: string
/** the name of the source schema */
sourceName: string
}
```
```js
const schemas = [
vnopts.BooleanSchema.create({ name: 'source' }),
vnopts.AliasSchema.create({ name: 'alias', sourceName: 'source' }),
]
vnopts.normalize({ alias: true }, schemas)
//=> { source: true }
vnopts.normalize({ alias: 'invalid' }, schemas)
//=> error: Invalid `alias` value. Expected `true or false`, but received `"invalid"`.
```
#### ArraySchema
`AliasSchema` validates values using the validator from the source schema and redirects all the value to the source key.
```ts
interface ArraySchemaParameters extends SchemaHandlers {
/** defaults to valueSchema's name */
name?: string
valueSchema: Schema
}
```
```js
const schemas = [
vnopts.ArraySchema.create({
valueSchema: vnopts.ChoiceSchema.create({
name: 'choices',
choices: [1, true, 'foo'],
}),
}),
]
vnopts.normalize({ choices: [1, 'foo'] }, schemas)
//=> { choices: [1, "foo"] }
vnopts.normalize({ choices: 1 }, schemas)
//=> error: Invalid `choices` value. Expected `an array of true, 1 or "foo"`, but received `1`.
```
### Handlers
Every schema has its own handlers but you can still override/extend them.
```ts
interface SchemaHandlers {
default?: SchemaDefaultHandler
expected?: SchemaExpectedHandler
validate?: SchemaValidateHandler
deprecated?: SchemaDeprecateHandler
forward?: SchemaForwardHandler
redirect?: SchemaRedirectHandler
overlap?: SchemaOverlapHandler
preprocess?: SchemaPreprocessHandler
postprocess?: SchemaPostprocessHandler
}
```
#### default
```ts
type SchemaDefaultHandler =
| DefaultResult
| ((schema: Schema, utils: Utils) => DefaultResult)
type DefaultResult = undefined | { value?: any }
```
`undefined` represents no default value,
default values are wrapped in an object's `value` field
to avoid the ambiguity between missing and `undefined`.
#### expected
```ts
type SchemaExpectedHandler =
| ExpectedResult
| ((schema: Schema, utils: Utils) => ExpectedResult)
type ExpectedResult =
| string
| { text: string }
| {
text?: string
list: {
title: string
values: ExpectedResult[]
}
}
```
Returns the description for the expected value in the form of text and/or list.
For example the following `ExpectedResult`:
```json
{
"list": {
"title": "one of the following values",
"values": ["foo", "bar", "baz"]
}
}
```
will produce the following message in `commonInvalidHandler`:
```
Invalid `<key>` value. Expected `one of the following values`, but received `<value>`.
- `"foo"`
- `"bar"`
- `"baz"`
```
If both `text` and `list` are returned,
`text` will be chosen if its width is the minimum one or its width is less than `loggerPrintWidth`,
otherwise `list`.
#### validate
```ts
type SchemaValidateHandler =
| ValidateResult
| ((value: unknown, schema: Schema, utils: Utils) => ValidateResult)
type ValidateResult = boolean | { value: unknown }
```
Returns a boolean represents if the entire value is valid,
or put the invalid value in an object's `value` field if only part of the value is invalid,
this is useful for collection schema like `ArraySchema`.
#### deprecated
```ts
type SchemaDeprecatedHandler =
| DeprecatedResult
| ((value: unknown, schema: Schema, utils: Utils) => DeprecatedResult)
type DeprecatedResult = boolean | { value: any } | Array<{ value: any }>
```
Returns `true` if the entire key is deprecated, `false` if it's not deprecated,
or (an array of) an object with `value` field if only part of the value is deprecated,
one object corresponds to one deprecation warning.
#### forward
```ts
type SchemaForwardHandler =
| ForwardResult
| ((value: any, schema: Schema, utils: Utils) => ForwardResult)
type TransferTo = /* key */ string | { key: string; value: any }
type TransferResult = TransferTo | { from?: /* value */ any; to: TransferTo }
type ForwardResult = undefined | TransferResult | Array<TransferResult>
```
Returns a key or a key-value pair if the entire value needs to be forwarded there,
or (an array of) an object with `from`/`to` field if only part of the value needs to be forwarded.
#### redirect
```ts
type SchemaRedirectHandler =
| RedirectResult
| ((value: any, schema: Schema, utils: Utils) => RedirectResult)
type RedirectResult =
| ForwardResult
| {
remain?: any
redirect: ForwardResult
}
```
Similar to `forward` but returns an object with `remain`/`redirect` field if not the entire value needs to be redirected.
#### overlap
```ts
type SchemaOverlapHandler = (
currentValue: any,
newValue: any,
schema: Schema,
utils: Utils,
) => any
```
Describes what should the normalizer do if there're multiple values assigned to the same key.
#### preprocess
```ts
type SchemaPreprocessHandler = (
value: unknown,
schema: Schema,
utils: Utils,
) => unknown
```
The preprocess before passing into the validator.
#### postprocess
```ts
type SchemaPostprocessHandler = (
value: unknown,
schema: Schema,
utils: Utils,
) => unknown
```
The postprocess after normalization.
## Development
```sh
# lint
pnpm run lint
# build
pnpm run build
# test
pnpm run test
```
## License
MIT © [Ika](https://github.com/ikatyang)