profile
viewpoint
Jed Fox j-f1 undefined America/New_York https://j-f1.github.io 🗣 he/him

issue closedprettier/prettier

[Typescript] Inconsistent behaviour with return type of callback parameter

Prettier 1.16.4 Playground link

Input:

context('App', () => {
    it('should work', (): void => {
      cy.visit('/');
      cy.contains('Home');
    });
  },
);

context('App', (): void => {
    it('should work', (): void => {
      cy.visit('/');
      cy.contains('Home');
    });
  },
);

Output:

context("App", () => {
  it("should work", (): void => {
    cy.visit("/");
    cy.contains("Home");
  });
});

context(
  "App",
  (): void => {
    it("should work", (): void => {
      cy.visit("/");
      cy.contains("Home");
    });
  }
);

Expected behavior:

context("App", () => {
  it("should work", (): void => {
    cy.visit("/");
    cy.contains("Home");
  });
});

context("App", (): void => {
    it("should work", (): void => {
      cy.visit("/");
      cy.contains("Home");
    });
  }
);

Why prettier format

context(
  "App",
  (): void => {
... 

But allow one liner context("App", () => { or it("should work", (): void => { ?

closed time in 2 hours

VincentLanglet

startedjprichardson/node-fs-extra

started time in 3 hours

issue commentprettier/prettier

Respect/Enforce line break after class declaration

What if I just want to use Prettier to add all my semi-colons and manage my quotes? It seems that should be an option.

If you just want that, then you probably want to use ESLint. It’s highly configurable and you can set it up to mostly leave your code alone.

pleerock

comment created time in 11 hours

issue commentdenoland/deno_std

suggestion: code style about async try catch

You could always use the native promise methods:

const mode = await stat(fn).catch(() => null);
const items = await Deno.readDir(dir).catch(() => Deno.mkdir(dir, true).then(() => []));
export async function ensureDir(dir: string): Promise<void> {
  let pathExists = false;
  const stat = await Deno.stat(dir).catch(() => await Deno.mkdir(dir, true));
  if (stat && !stat.isDirectory()) {
    throw new Error(
      `Ensure path exists, expected 'dir', got '${getFileInfoType(stat)}'`
    );
  }
}

Dreamacro

comment created time in 12 hours

issue commentunpkg/unpkg.com

Server error 500 on several modules

Seems like it might be fixed?

vasturiano

comment created time in 12 hours

pull request commentdenoland/deno_std

WIP: add type_decoders module

Good point about the custom decoders 👍 Maybe the is prefix is the way to go, after all.

thefliik

comment created time in a day

issue commentprettier/prettier

Idea "Non Breaking Line Builder"

Assuming the contents of the block always break onto multiple lines, this should work:

group(
  concat([
    ifBreak('{', ''),
    hardline,
    indent(concat([hardline, path.call(print, 'body')])),
    hardline,
    ifBreak(concat(['}  ']), '')
  ])
)
Janther

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.++### isChainOf()++```ts+interface IChainOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): Decoder<R, I>;++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> | PromiseDecoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): PromiseDecoder<R, I>;+```++`isChainOf()` accepts an array of decoders and attempts to decode a provided value using all of them, in order. The successful output of one decoder is provided as input to the next decoder. `isChainOf()` returns the `DecoderSuccess` value of the last decoder in the chain or `DecoderError` on the first failure.++### isObject()++```ts+interface IObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IObjectDecoderOptions<T>): Decoder<T> ;+function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isObject()` accepts a `key: Decoder` argument object and returns a new decoder that will verify that an input is a non-null object, that the input has the keys specified in the argument object, and that the key values pass the relevant decoder provided by the argument object. On `DecoderSuccess`, the a new object is returned and the key-values of that object are provided by output from the argument decoders.++### isExactObject()++```ts+interface IExactObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): Decoder<T> ;+function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isExactObject()` is the same as `isObject()`, except the input object cannot have any excess properties.++### isDictionary()++```ts+interface IDictionaryDecoderOptions<V> {+  msg?: DecoderErrorMsgArg;+}++function isDictionary<R, V = unknown>(decoder: Decoder<R, V>, options?: IDictionaryDecoderOptions<V>): Decoder<R[], V>;+function isDictionary<R, V = unknown>(decoder: PromiseDecoder<R, V>, options?: IDictionaryDecoderOptions<V>): PromiseDecoder<R[], V>;+```++`isDictionary()` receives a decoder argument and uses that decoder to process all values (regardless of key) of an input object.++### isArray()

IMO isArray is more abstract, whereas isTuple is the more concrete version.

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.

It’s also a little difficult to convey since you’d normally expect at least some of the decoders to fail even if the anyOf succeeds.

thefliik

comment created time in a day

pull request commentdenoland/deno_std

WIP: add type_decoders module

One option would be to get rid of the isUndefined/isNull decoders and instead use is(undefined)/is(null), especially since they’d be much more frequently used<sup>[citation needed]</sup> in the form of isOptional/isNullable. This would allow the use of unprefixed function names.

thefliik

comment created time in a day

issue commentprettier/prettier

Support for option - breaking simple template literals

It seems like there isn’t really a good solution for breaking template expressions since they’re whitespace-sensitive. IMO in these cases, it’s better to have soft-wrapping in the IDE handle things, since the IDE can break the line wherever it wants to without altering the actual content of the file, a luxury Prettier doesn’t have.

MrCheater

comment created time in a day

pull request commentprettier/prettier

[Glimmer/Handlebars] Forces multilines print when almost at max length

We do it the latter way in JS, but if Handlebars has a different convention, then that’s fine too. Mostly the rationale is that by putting the closing delimiter on its own line, the parameters can be safely moved around/out of the block.

jjaffeux

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.++### isChainOf()++```ts+interface IChainOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): Decoder<R, I>;++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> | PromiseDecoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): PromiseDecoder<R, I>;+```++`isChainOf()` accepts an array of decoders and attempts to decode a provided value using all of them, in order. The successful output of one decoder is provided as input to the next decoder. `isChainOf()` returns the `DecoderSuccess` value of the last decoder in the chain or `DecoderError` on the first failure.++### isObject()++```ts+interface IObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IObjectDecoderOptions<T>): Decoder<T> ;+function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isObject()` accepts a `key: Decoder` argument object and returns a new decoder that will verify that an input is a non-null object, that the input has the keys specified in the argument object, and that the key values pass the relevant decoder provided by the argument object. On `DecoderSuccess`, the a new object is returned and the key-values of that object are provided by output from the argument decoders.++### isExactObject()++```ts+interface IExactObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): Decoder<T> ;+function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isExactObject()` is the same as `isObject()`, except the input object cannot have any excess properties.++### isDictionary()++```ts+interface IDictionaryDecoderOptions<V> {+  msg?: DecoderErrorMsgArg;+}++function isDictionary<R, V = unknown>(decoder: Decoder<R, V>, options?: IDictionaryDecoderOptions<V>): Decoder<R[], V>;+function isDictionary<R, V = unknown>(decoder: PromiseDecoder<R, V>, options?: IDictionaryDecoderOptions<V>): PromiseDecoder<R[], V>;+```++`isDictionary()` receives a decoder argument and uses that decoder to process all values (regardless of key) of an input object.++### isArray()

isArrayOf?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.++### isChainOf()++```ts+interface IChainOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): Decoder<R, I>;++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> | PromiseDecoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): PromiseDecoder<R, I>;+```++`isChainOf()` accepts an array of decoders and attempts to decode a provided value using all of them, in order. The successful output of one decoder is provided as input to the next decoder. `isChainOf()` returns the `DecoderSuccess` value of the last decoder in the chain or `DecoderError` on the first failure.++### isObject()++```ts+interface IObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IObjectDecoderOptions<T>): Decoder<T> ;+function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isObject()` accepts a `key: Decoder` argument object and returns a new decoder that will verify that an input is a non-null object, that the input has the keys specified in the argument object, and that the key values pass the relevant decoder provided by the argument object. On `DecoderSuccess`, the a new object is returned and the key-values of that object are provided by output from the argument decoders.++### isExactObject()++```ts+interface IExactObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): Decoder<T> ;+function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isExactObject()` is the same as `isObject()`, except the input object cannot have any excess properties.++### isDictionary()++```ts+interface IDictionaryDecoderOptions<V> {+  msg?: DecoderErrorMsgArg;+}++function isDictionary<R, V = unknown>(decoder: Decoder<R, V>, options?: IDictionaryDecoderOptions<V>): Decoder<R[], V>;+function isDictionary<R, V = unknown>(decoder: PromiseDecoder<R, V>, options?: IDictionaryDecoderOptions<V>): PromiseDecoder<R[], V>;+```++`isDictionary()` receives a decoder argument and uses that decoder to process all values (regardless of key) of an input object.++### isArray()++```ts+interface IArrayDecoderOptions<V> {+  msg?: DecoderErrorMsgArg;+}++function isArray<R = unknown, V = unknown>(options?: IArrayDecoderOptions<V>): Decoder<R[], V>;+function isArray<R, V = unknown>(decoder: Decoder<R, V>, options?: IArrayDecoderOptions<V>): Decoder<R[], V>;+function isArray<R, V = unknown>(decoder: PromiseDecoder<R, V>, options?: IArrayDecoderOptions<V>): PromiseDecoder<R[], V>;+```++`isArray()` can be used to make sure an input is an array. If an optional decoder argument is provided, that decoder will be used to process all of the input's elements.++### isTuple()++```ts+interface ITupleDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isTuple<Tuple extends [unknown, ...unknown[]]>(decoders: { [I in keyof Tuple]: Decoder<Tuple[I]> }, options?: ITupleDecoderOptions): Decoder<Tuple>;+function isTuple<Tuple extends [unknown, ...unknown[]]>(decoders: { [I in keyof Tuple]: Decoder<Tuple[I]> | PromiseDecoder<Tuple[I]> }, options?: ITupleDecoderOptions): PromiseDecoder<Tuple>;+```++`isTuple()` receives an array of decoders and creates a decoder which can be used to verify that an input is:++1. An array of the same length as the decoder argument array.+2. The first decoder argument will be used the process the first element of an input array.+3. The second decoder argument will be used the process the second element of an input array.+4. etc...++### isLazy()

When would this be used? Would it be when the Decoder tree is expensive to generate?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.++### isChainOf()++```ts+interface IChainOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): Decoder<R, I>;++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> | PromiseDecoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): PromiseDecoder<R, I>;+```++`isChainOf()` accepts an array of decoders and attempts to decode a provided value using all of them, in order. The successful output of one decoder is provided as input to the next decoder. `isChainOf()` returns the `DecoderSuccess` value of the last decoder in the chain or `DecoderError` on the first failure.++### isObject()++```ts+interface IObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IObjectDecoderOptions<T>): Decoder<T> ;+function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isObject()` accepts a `key: Decoder` argument object and returns a new decoder that will verify that an input is a non-null object, that the input has the keys specified in the argument object, and that the key values pass the relevant decoder provided by the argument object. On `DecoderSuccess`, the a new object is returned and the key-values of that object are provided by output from the argument decoders.++### isExactObject()++```ts+interface IExactObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): Decoder<T> ;+function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isExactObject()` is the same as `isObject()`, except the input object cannot have any excess properties.++### isDictionary()

isDictionaryOf?

Also, would it make sense to have an optional second decoder parameter to check the keys?

isDictionaryOf(isNumber(), isRegex(/^[a-z]+$/) // enforce camelCase keys
thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.++### isChainOf()++```ts+interface IChainOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): Decoder<R, I>;++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> | PromiseDecoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): PromiseDecoder<R, I>;+```++`isChainOf()` accepts an array of decoders and attempts to decode a provided value using all of them, in order. The successful output of one decoder is provided as input to the next decoder. `isChainOf()` returns the `DecoderSuccess` value of the last decoder in the chain or `DecoderError` on the first failure.++### isObject()++```ts+interface IObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IObjectDecoderOptions<T>): Decoder<T> ;+function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isObject()` accepts a `key: Decoder` argument object and returns a new decoder that will verify that an input is a non-null object, that the input has the keys specified in the argument object, and that the key values pass the relevant decoder provided by the argument object. On `DecoderSuccess`, the a new object is returned and the key-values of that object are provided by output from the argument decoders.++### isExactObject()++```ts+interface IExactObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): Decoder<T> ;+function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isExactObject()` is the same as `isObject()`, except the input object cannot have any excess properties.++### isDictionary()++```ts+interface IDictionaryDecoderOptions<V> {+  msg?: DecoderErrorMsgArg;+}++function isDictionary<R, V = unknown>(decoder: Decoder<R, V>, options?: IDictionaryDecoderOptions<V>): Decoder<R[], V>;+function isDictionary<R, V = unknown>(decoder: PromiseDecoder<R, V>, options?: IDictionaryDecoderOptions<V>): PromiseDecoder<R[], V>;+```++`isDictionary()` receives a decoder argument and uses that decoder to process all values (regardless of key) of an input object.++### isArray()++```ts+interface IArrayDecoderOptions<V> {+  msg?: DecoderErrorMsgArg;+}++function isArray<R = unknown, V = unknown>(options?: IArrayDecoderOptions<V>): Decoder<R[], V>;+function isArray<R, V = unknown>(decoder: Decoder<R, V>, options?: IArrayDecoderOptions<V>): Decoder<R[], V>;+function isArray<R, V = unknown>(decoder: PromiseDecoder<R, V>, options?: IArrayDecoderOptions<V>): PromiseDecoder<R[], V>;+```++`isArray()` can be used to make sure an input is an array. If an optional decoder argument is provided, that decoder will be used to process all of the input's elements.++### isTuple()

isTupleOf?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.++### isChainOf()++```ts+interface IChainOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): Decoder<R, I>;++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> | PromiseDecoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): PromiseDecoder<R, I>;+```++`isChainOf()` accepts an array of decoders and attempts to decode a provided value using all of them, in order. The successful output of one decoder is provided as input to the next decoder. `isChainOf()` returns the `DecoderSuccess` value of the last decoder in the chain or `DecoderError` on the first failure.++### isObject()++```ts+interface IObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IObjectDecoderOptions<T>): Decoder<T> ;+function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isObject()` accepts a `key: Decoder` argument object and returns a new decoder that will verify that an input is a non-null object, that the input has the keys specified in the argument object, and that the key values pass the relevant decoder provided by the argument object. On `DecoderSuccess`, the a new object is returned and the key-values of that object are provided by output from the argument decoders.++### isExactObject()

Would it make sense to make this an additional option to isObject, i.e. isObject({ ... }, { exact: true })?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.++### isChainOf()++```ts+interface IChainOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): Decoder<R, I>;++function isChainOf<T extends [unknown, ...unknown[]], R = ChainOfDecoderReturn<T>, I = DecoderInputType<T[0]>>(+  decoders: { [I in keyof T]: Decoder<T[I], DecoderInputType<T[SubtractOne<I>]>> | PromiseDecoder<T[I], DecoderInputType<T[SubtractOne<I>]>> },+  options?: IChainOfDecoderOptions+): PromiseDecoder<R, I>;+```++`isChainOf()` accepts an array of decoders and attempts to decode a provided value using all of them, in order. The successful output of one decoder is provided as input to the next decoder. `isChainOf()` returns the `DecoderSuccess` value of the last decoder in the chain or `DecoderError` on the first failure.++### isObject()++```ts+interface IObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IObjectDecoderOptions<T>): Decoder<T> ;+function isObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isObject()` accepts a `key: Decoder` argument object and returns a new decoder that will verify that an input is a non-null object, that the input has the keys specified in the argument object, and that the key values pass the relevant decoder provided by the argument object. On `DecoderSuccess`, the a new object is returned and the key-values of that object are provided by output from the argument decoders.++### isExactObject()++```ts+interface IExactObjectDecoderOptions<T> {+  msg?: DecoderErrorMsgArg;+  keyMap?: { [P in keyof T]?: string | number };+}++function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): Decoder<T> ;+function isExactObject<T>(decoderObject: { [P in keyof T]: Decoder<T[P]> | PromiseDecoder<T[P]> }, options?: IExactObjectDecoderOptions<T>): PromiseDecoder<T> ;+```++`isExactObject()` is the same as `isObject()`, except the input object cannot have any excess properties.++### isDictionary()++```ts+interface IDictionaryDecoderOptions<V> {+  msg?: DecoderErrorMsgArg;+}++function isDictionary<R, V = unknown>(decoder: Decoder<R, V>, options?: IDictionaryDecoderOptions<V>): Decoder<R[], V>;+function isDictionary<R, V = unknown>(decoder: PromiseDecoder<R, V>, options?: IDictionaryDecoderOptions<V>): PromiseDecoder<R[], V>;

It looks like the return types here have a typo — should they be { [key: string]: R } instead of R[]?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()++```ts+interface ICheckedWithDecoderOptions {+  msg?: DecoderErrorMsgArg;+  promise?: boolean;+}++function isCheckedWith<T>(fn: (value: T) => boolean, options?: ICheckedWithDecoderOptions): Decoder<T, T>;+function isCheckedWith<T>(fn: (value: T) => Promise<boolean>, options: ICheckedWithDecoderOptions & { promise: true }): PromiseDecoder<T, T>;+```++`isCheckedWith()` accepts a predicate function argument and creates a decoder which verifies that inputs pass the function check.++**Async**: to pass a predicate function which returns a promise resolving to a boolean, pass the `promise: true` option to `isCheckedWith()`.++### isOptional()++```ts+interface IOptionalDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isOptional<T>(decoder: Decoder<T>, options?: IOptionalDecoderOptions): Decoder<T | undefined>;+function isOptional<T>(decoder: PromiseDecoder<T>, options?: IOptionalDecoderOptions): PromiseDecoder<T | undefined>;+```++`isOptional()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `undefined`. Convenience function for `isAnyOf([decoder, isUndefined()])`.++### isNullable()++```ts+interface INullableDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNullable<T>(decoder: Decoder<T>, options?: INullableDecoderOptions): Decoder<T | null>;+function isNullable<T>(decoder: PromiseDecoder<T>, options?: INullableDecoderOptions): PromiseDecoder<T | null>;+```++`isNullable()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null`. Convenience function for `isAnyOf([decoder, isNull()])`.++### isMaybe()++```ts+interface IMaybeDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isMaybe<T>(decoder: Decoder<T>, options?: IMaybeDecoderOptions): Decoder<T | null | undefined>;+function isMaybe<T>(decoder: PromiseDecoder<T>, options?: IMaybeDecoderOptions): PromiseDecoder<T | null | undefined>;+```++`isMaybe()` accepts a decoder and returns a new decoder which accepts either the original decoder's value or `null` or `undefined`. Convenience function for `isAnyOf([decoder, isNull(), isUndefined()])`.++### isAnyOf()++```ts+interface IAnyOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isAnyOf<T extends Decoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): Decoder<DecoderReturnType<T>>;+function isAnyOf<T extends Decoder<unknown> | PromiseDecoder<unknown>>(decoders: T[], options?: IAnyOfDecoderOptions): PromiseDecoder<DecoderReturnType<T>>;+```++`isAnyOf()` accepts an array of decoders and attempts to decode a provided value using each of them, in order, returning the first successful result or `DecoderError` if all fail.

Which DecoderError would be returned here? Would it create its own or somehow combine the errors of the passed decoders?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()++```ts+function isConstant<T>(value: T): Decoder<T, unknown>;+```++`isConstant()` accepts a `value: T` argument and creates a decoder which always returns the `value: T` argument, ignoring its input value.++### isInstanceOf()++```ts+interface IInstanceOfDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInstanceOf<T extends new (...args: any) => any>(clazz: T, options?: IInstanceOfDecoderOptions): Decoder<InstanceType<T>, unknown>;+```++`isInstanceOf()` accepts a javascript constructor argument and creates a decoder which verifies that its input is `instanceof clazz`.++### isCheckedWith()

isMatchForPredicate?

If we got rid of the is prefix, this could be matchesPredicate.

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()++```ts+interface IExactlyDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isExactly<T>(value: T, options?: IExactlyDecoderOptions): Decoder<T, unknown>;+```++`isExactly()` accepts a `value: T` argument and can be used to verify that an unknown input is `=== value`.++### isConstant()

Maybe isOverriddenToBe or something more concise with the same meaning?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.++### isAny()++```ts+function isAny<T = unknown>(): Decoder<T, unknown>;+```++`isAny()` creates a decoder which always returns `DecoderSuccess` with whatever input value is provided to it.++### isNever()++```ts+interface INeverDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNever(options?: INeverDecoderOptions): Decoder<never, unknown>;+```++`isNever()` creates a decoder which always returns `DecoderError` with whatever input value is provided to it. One use case is using it in combination with `isObject` and `isOptional` to assert that an input object doesn't contain a given key.++Example:++```ts+const validator = assert(isObject({a: isString() b: isOptional(isNever()) }));++validator({ a: 'one' }) // returns { a: 'one' }+validator({ a: 'one', b: 'two' }) // throws DecoderError+```++### isExactly()

Maybe call this is?

thefliik

comment created time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()++```ts+interface IRegexDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isRegex(regex: RegExp, options?: IRegexDecoderOptions): Decoder<string, unknown>;+```++`isRegex()` can be used to verify that an unknown value is `string` which conforms to the given `RegExp`.

I’m not exactly sure how much you’re allowing matchers to overlap, but would it make sense to re-type this as taking in a string and outputting a string, leaving the actual check to see if the input is a string to isString? Or would that be too much DRY?

thefliik

comment created time in a day

push eventj-f1/read262

Jed Fox

commit sha 0c54145a80acfcc4ff876cac82208ea522983007

Upgrade Prettier

view details

push time in a day

Pull request review commentdenoland/deno_std

WIP: add type_decoders module

+# type_decoders++This module facilitates validating `unknown` (or other) values and casting them to the proper typescript types. It provides an assortment of useful decoder primatives which are usable as-is, easily customized, and composable with other decoders (including any custom decoders you may make).++Users and library authors should be able to create custom decoders which can be composed with the decoders provided in this module, as well as composed with any other third-party decoders which are compatible with this module.++```ts+import { assert, isNumber } from "https://deno.land/std/type_decoders/mod.ts";++// assert() is a convenience function which wraps a decoder+const numberValidator = assert(isNumber());++const value = numberValidator(1) // returns 1+const value = numberValidator('1') // throws `DecoderError`+```++alternatively ++```ts+const decoder = isNumber();++const result = decoder.decode(1); // returns `DecoderSuccess<number>`++const value = result.value; // 1++const result = decoder.decode('1'); // returns (not throws) `DecoderError`+```++# Usage++- [Basic usage](#Basic-usage)+- [Interfaces](#Interfaces)+- [Working with promises](#Working-with-promises)+- [Working with errors](#Working-with-errors) +- [Creating custom decoders](#Creating-custom-decoders)+- [Tips and tricks](#Tips-and-tricks)+- [Decoder API](#Decoder-API)+  - [assert()](#assert)+  - [isBoolean()](#isBoolean)+  - [isString()](#isString)+  - [isNumber()](#isNumber)+  - [isInteger()](#isInteger)+  - [isUndefined()](#isUndefined)+  - [isNull()](#isNull)+  - [isRegex()](#isRegex)+  - [isAny()](#isAny)+  - [isNever()](#isNever)+  - [isExactly()](#isExactly)+  - [isConstant()](#isConstant)+  - [isInstanceOf()](#isInstanceOf)+  - [isCheckedWith()](#isCheckedWith)+  - [isOptional()](#isOptional)+  - [isNullable()](#isNullable)+  - [isMaybe()](#isMaybe)+  - [isAnyOf()](#isAnyOf)+  - [isChainOf()](#isChainOf)+  - [isObject()](#isObject)+  - [isExactObject()](#isExactObject)+  - [isDictionary()](#isDictionary)+  - [isArray()](#isArray)+  - [isTuple()](#isTuple)+  - [isLazy()](#isLazy)++## Basic usage++This module exports an assortment of primative decoder functions which each return a decoder. For consistancy, all of the exported decoder functions begin with the prefix `is`. For example, the `isNumber()` function returns a `Decoder<number, unknown>` suitable for decoding an `unknown` value to a `number`.++```ts+const myNumberDecoder = isNumber();++const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number>+const result = myNumberDecoder.decode('1') // returns (not throws) a DecoderError++if (result instanceof DecoderError) {+  // do stuff...+  return;+}++// result is typed as `DecoderSuccess<number>+// value receives the type `number`+const value = result.value;+```++For your convenience, you can wrap any decoder with the exported `assert` function which will return a valid value directly or throw a `DecoderError`.++```ts+const myNumberDecoder = assert(isNumber());+const value = myNumberDecoder(1); // returns 1+const value = myNumberDecoder('1'); // will throw (not return) a DecoderError+```++Some decoder functions aid with composing decoders. For example, the `isOptional()` decoder accepts another decoder as an argument and returns a decoder that accepts either `undefined` or the value decoded by its argument.++```ts+const myNumberDecoder = isOptional(isNumber());+const result = myNumberDecoder.decode(1) // returns a DecoderSuccess<number | undefined>+const result = myNumberDecoder.decode(undefined) // returns a DecoderSuccess<number | undefined>+```++A more complex example of decoder composition is the `isObject()` decoder function, which receives a `{[key: string]: Decoder<unknown, unknown>}` object argument. This argument is used to process a provided value: it verifies that the provided value is a non-null object, that the object has the specified keys, and that the values of the object's keys pass the provided decoder checks.++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( isNumber() ) )+  })+})++const goodInput = { payload: { values: [0, null, 2] } } as unknown;++const success = myObjectDecoder.decode(goodInput); // will return `DecoderSuccess`++// Notice that success.value is properly typed+const value: { payload: string; { values: Array<number | null> } } = success.value;++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" value > invalid key \"values\" value > invalid array element [2] > must be a string"+error.location // "payload.values[2]"+error.value // { payload: { values: [0, null, '1'] } }+error.path() // ["payload", "values", 2]+error.child // nested DecoderError+error.child.message // "invalid key \"values\" value > invalid array element [2] > must be a string"+error.child.location // "values[2]"+error.child.value // { values: [0, null, '1'] }+error.child.child.value // [0, null, '1']+// etc+```++## Interfaces++This module exports two base decoder classes `Decoder<R, V>` and `PromiseDecoder<R, V>`. It also exports a base `DecoderSuccess<T>` class and `DecoderError` class.++### Decoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class Decoder<R, I = unknown> {+  new(decodeFn: (value: I) => DecoderResult<R>): Decoder<R, I>;++  /**+   * Decodes a value of type `I` and returns a `DecoderResult<R>`.+   */+  decode(value: I): DecoderResult<R>;+  /**+   * Decodes a value of type `Promise<I>` and returns+   * a `Promise<DecoderResult<R>>`.+   */+  decode(value: Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   */+  map<K>(fn: (value: R) => K): Decoder<K, I>;+}+```++### PromiseDecoder<R, I>++The first type argument, `R`, contains the successful return type of the decoder. The second type argument, `I`, contains the type of input arguments passed to the decoder. By default, the input type is `unknown`.++```ts+class PromiseDecoder<R, I = unknown> {+  new(decodeFn: (value: I) => Promise<DecoderResult<R>>): PromiseDecoder<R, I>;++  /**+   * Decodes a value (or promise returning a value) of type `I`+   * and returns a `Promise<DecoderResult<R>>`+   */+  decode(value: I | Promise<I>): Promise<DecoderResult<R>>;++  /**+   * On decode success, transform a value using a provided transformation function.+   * Unlike `Decoder#map`, the tranformation function provided to `PromiseDecoder#map`+   * can return a promise.+   */+  map<K>(fn: (value: R) => K | Promise<K>): PromiseDecoder<K, I>;+}+```++### DecoderSuccess<T>++```ts+class DecoderSuccess<T> {+  new(value: T): DecoderSuccess<T>;++  value: T;+}+```++### DecoderError++```ts+class DecoderError {+  new(+    value: unknown,+    message: string,+    options?: {+      decoderName?: string,+      location?: string;+      child?: DecoderError;+      key?: unknown+    }+  ): DecoderError;++  /** default = 'DecoderError' */+  name: string;++  /** The value that failed validation. */+  value: unknown;+  +  /** A human readable error message. */+  message: string;++  /** An optional name to describe the decoder which triggered the error. */+  decoderName?: string;+  +  /** +   * A human readable string showing the nested location of the error.+   * If the validation error is not nested, location will equal a blank string.+   */+  location: string;+  +  /** The child `DecoderError` which triggered this `DecoderError`, if any */+  child?: DecoderError;+  +  /** +   * The key associated with this `DecoderError` if any.+   * +   * - E.g. this might be the object key which failed validation for an `isObject()`+   *   decoder.+   */+  key?: unknown;++  /**+   * Starting with this error, an array of the keys associated with+   * this error as well as all child errors.+   */+  path(): unknown[];+}+```++### DecoderResult<T>++```ts+type DecoderResult<T> = DecoderSuccess<T> | DecoderError;+```++### DecoderErrorMsgArg++```ts+type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError);+```++## Working with promises++Every decoder supports calling its `decode()` method with a promise which returns the value to be decoded. In this scenerio, `decode()` will return a `Promise<DecoderResult<T>>`. Internally, the decoder will wait for the promise to resolve before passing the value to its `decodeFn`. As such, the internal `decodeFn` will never be passed a promise value.++If you wish to create a custom decoder with a `decodeFn` which returns a promise, then you must use the `PromiseDecoder` class (the `Decoder` class does not support being constructed with a `decodeFn` which returns a promise).++`PromiseDecoder` is largely identical to `Decoder`, except its `decode()` method always returns `Promise<DecoderResult<T>>` (not just when called with a promise value) and it's `decodeFn` returns a promise. Additionally, if you pass a `PromiseDecoder` as an argument to any of the decoder constructor functions in this module (i.e. `isObject()`, `isArray()`), that function will return a `PromiseDecoder` instead of a `Decoder`.++Example: you can pass a custom `PromiseDecoder` to `isObject()` as an argument, but this will change the return of `isObject()` from a `Decoder` to a `PromiseDecoder`.++```ts+const myCustomDecoder = new PromiseDecoder(async value => +  typeof value === 'boolean'+    ? Promise.resolve(new DecoderSuccess(value))+    : Promise.resolve(new DecoderError(value, 'Must be a boolean'))+);++const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray( isNullable( myCustomDecoder ) )+  })+})++myObjectDecoder instanceof PromiseDecoder === true+```++## Working with errors++[_see the `DecoderError` interface_](#DecoderError)++One of the most useful aspects of this module is its support for providing human and machine readable error messages, as well as customizing those messages for your domain. ++Where appropriate, the decoder functions exported by this module accept an optional options object with `{ msg?: DecoderErrorMsgArg }` where `type DecoderErrorMsgArg = string | ((error: DecoderError) => DecoderError)`. If you pass a string to this message property, that string will be used as the error message for that decoder. This is option is easy but will potentially suppress more deeply nested error messages.++Example:++```ts+const myObjectDecoder = isObject({+  payload: isObject({+    values: isArray(+      isNullable( isNumber() ),+      { msg: "invalid array" }+    )+  })+})++const badInput = { payload: { values: [0, null, '1'] } } as unknown;++const error = myObjectDecoder.decode(badInput); // will return `DecoderError`++error.message // "invalid key \"payload\" > invalid key \"values\" > invalid array+error.child.message // "invalid key \"values\" > invalid array"+error.child.child.message // "invalid array"+error.child.child.child.message // "must be a string"+error.child.child.child.child // undefined+```++For more control over your error messages, you can provide a `(error: DecoderError) => DecoderError` function as the `msg` argument.++If a DecoderError occurs, the error will be passed to the provided `msg` function where you can either manipulate the error or return a new error.++Example:++```ts+const errorMsgFn = (error: DecoderError) => {+  const { decoderName } = error.child;++  error.message = decoderName !== 'isArray' ? 'array must have a length of 2' :+    error.child.child ? 'must be an array of numbers' :+    'must be an array';++  error.decoderName = 'myLatLongDecoder';++  return error;+};++const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+], { msg: errorMsgFn })++const badInput0 = {} as unknown;+const badInput1 = [1, '2'] as unknown;+const badInput2 = [1] as unknown;++const error0 = myLatLongDecoder.decode(badInput0);+const error1 = myLatLongDecoder.decode(badInput1);+const error2 = myLatLongDecoder.decode(badInput2);++error0.message // "must be an array"+error1.message // "must be an array of numbers"+error2.message // "array must have a length of 2"+```++In the above example, our `errorMsgFn` made use of the `DecoderError#decoderName` property. DecoderErrors can be constructed with an optional `decoderName` value to easily identify the decoder which created them. All decoder functions exported by this module provide `DecoderError#decoderName` values.++## Creating custom decoders++There are a few ways of creating custom decoders. This simplest way is to simply compose multiple decoders together. For example, the following latitude and longitude decoder is created by composing `isArray(isNumber())` and `isCheckedWith()` using `isChainOf()`;++```ts+const myLatLongDecoder = isChainOf([+  isArray(isNumber()),+  isCheckedWith(input => input.length === 2),+])+```++For more flexibility, you can create a new decoder from scratch using either the `Decoder` or `PromiseDecoder` constructors (see the [working with promises](#Working-with-promises) section for a description of the differences between `Decoder` and `PromiseDecoder`). To make a new decoder from scratch, simply pass a custom decode function to the `Decoder` constructor. A decode function is a function which receives a value and returns a `DecodeSuccess` object on success and a `DecodeError` object on failure.++Example:++```ts+const myCustomDecoder = new Decoder(value => +  typeof value === 'boolean'+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must be a boolean')+)++// You can then compose this decoder with others normally++isObject({ likesDeno: myCustomDecoder })++// Or use it directly++myCustomDecoder.decode(true)+```++#### Specifying an input value type++While the vast majority of decoders expect an input value of `unknown`, it is possible to create a decoder which requires an already typed input value. In fact, the `I` type arg in `Decoder<R, I>` is the input variable type (the default is `unknown`). To create a decoder which requires an input value to already be of a known type, simply type the input of the decoder's decode function.++Example:++```ts+const arrayLengthDecoder = new Decoder((value: unknown[]) => +  value.length < 100+    ? new DecoderSuccess(value)+    : new DecoderError(value, 'must have length less than 100')+)++arrayLengthDecoder.decode(1) // type error! decode() expects an array+```++This decoder only works on array values. One use case for a decoder like this is inside the `isChainOf()` decoder, after we have already verified that a value is an array.++Example:++```ts+isChainOf([+  isArray(),+  arrayLengthDecoder, // <-- this will only be called when the value is an array+])+```++#### Creating custom decoder composition functions++Like this module, you may wish to create custom decoder composition functions (e.g. `isObject()`) to dynamically compose decoders together. It's recommended that, before doing so, you take a look at some of the composition functions contained in this module.++One important thing to consider: if your function takes one or more decoders as an argument, you need to manually handle the possibility of being passed a `PromiseDecoder`. If you receive one ore more `PromiseDecoders` and an argument, your composition function should return a `PromiseDecoder`. Typescript overloads can be used to properly type the different returns.++## Tips and tricks++### The assert() function++It may be the case that you simply want to return the validated value from a decoder directly, rather than a `DecoderResult`. In this case, wrap a decoder with `assert()` to get a callable function which will return a valid value on success, or throw a `DecoderError` on failure.++Example:++```ts+const validator = assert(isNumber());++const value = validator(1); // returns 1++const value = validator('1'); // will throw a `DecoderError`+```++### The decoder map() method++Decoders have a `map` method which can be used to transform valid values. For example, say you are receiving a date param in the form of a string, and you want to convert it to a javascript `Date` object.++```ts+const stringDateDecoder =+  // this regex verifies that a string is of the form `YYYY-MM-DD`+  isRegex(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/)+    .map(value => new Date(value));++const result = stringDateDecoder.decode('2000-01-01'); // returns `DecoderSuccess<Date>`++if (result instanceof DecoderSuccess) {+  const value: Date = result.value;+}+```++This decoder will verify that a string is in a `YYYY-MM-DD` format and, if so, convert the string to a date. The return type of the decoder is `Date`.++## Decoder API++### assert()++```ts+export function assert<R, V>(decoder: Decoder<R, V>): { (value: V): R; (value: Promise<V>): Promise<R> };+export function assert<R, V>(decoder: PromiseDecoder<R, V>): (value: V | Promise<V>) => Promise<R>;+```++`assert()` accepts a single decoder as an argument and returns a new function which can be used to decode the same values as provided decoder. On decode success, the validated value is returned directly and on failure the `DecoderError` is thrown (rather than returned).++Example:++```ts+const validator = assert(isNumber());++const value: number = validator(1);++const value: number = validator('1'); // will throw a `DecoderError`+```++### isBoolean()++```ts+interface IBooleanDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isBoolean(options?: IBooleanDecoderOptions): Decoder<boolean, unknown>;+```++`isBoolean()` can be used to verify that an unknown value is a `boolean`.++### isString()++```ts+interface IStringDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isString(options?: IStringDecoderOptions): Decoder<string, unknown>;+```++`isString()` can be used to verify that an unknown value is a `string`.++### isNumber()++```ts+interface INumberDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNumber(options?: INumberDecoderOptions): Decoder<number, unknown>;+```++`isNumber()` can be used to verify that an unknown value is a `number`.++### isInteger()++```ts+interface IIntegerDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isInteger(options?: IIntegerDecoderOptions): Decoder<number, unknown>;+```++`isInteger()` can be used to verify that an unknown value is a whole `number`.++### isUndefined()++```ts+interface IUndefinedDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isUndefined(options?: IUndefinedDecoderOptions): Decoder<undefined, unknown>;+```++`isUndefined()` can be used to verify that an unknown value is `undefined`. This is a convenience function for `isExactly(undefined)`.++### isNull()++```ts+interface INullDecoderOptions {+  msg?: DecoderErrorMsgArg;+}++function isNull(options?: INullDecoderOptions): Decoder<null, unknown>;+```++`isNull()` can be used to verify that an unknown value is `null`. This is a convenience function for `isExactly(null)`.++### isRegex()

Should this be called isMatch or similar, since it doesn’t check the input to see if it’s a regular expression, but rather matches the passed regular expression against the input?

thefliik

comment created time in a day

pull request commentdenoland/deno_std

WIP: add type_decoders module

Why did you decide on the is prefix instead of having no prefix?

thefliik

comment created time in a day

pull request commentprettier/prettier

JavaScript: Keep unary expressions parentheses with comments

Looks like some tests are still failng @sosukesuzuki.

sosukesuzuki

comment created time in a day

push eventj-f1/read262

Jed Fox

commit sha 72fc77b9a22ce1f7a9c3e669991b2647ffb50ae4

Improve the offline search experience

view details

Jed Fox

commit sha 47b3c41dd3fe29bf9b6430db8ea1c7bebee3199d

Allow manually toggling between search UIs in DEV mode

view details

Jed Fox

commit sha 7c6e4c290830798c9038f62442faba9434b095c5

Move the offline search engine into a worker so the page doesn’t freeze when it loads

view details

push time in a day

starteddevelopit/workerize

started time in 2 days

startedolivernn/lunr.js

started time in 2 days

startedmoroshko/autosuggest-highlight

started time in 2 days

issue closedprettier/prettier

Typescript: unexpected semicolon at the beginning of a line

Behavior triggered by --no-semi

Prettier 1.18.2 Playground link

--parser typescript
--no-semi
--trailing-comma es5

Input:

class Foo {
  public componentWillUnmount() {
    (window as any).cancelIdleCallback(this.bar)
  }

  public bar() {}
}

Output:

class Foo {
  public componentWillUnmount() {
    ;(window as any).cancelIdleCallback(this.bar)
  }

  public bar() {}
}

Expected behavior: The semicolon in line 3 should not be there, it is however syntactically correct

Similar to https://github.com/prettier/prettier/issues/5837 but without a preceeding comment.

closed time in 2 days

thomasnordquist

issue commentprettier/prettier

Typescript: unexpected semicolon at the beginning of a line

This is correct behavior. If you were to add another line of code above the (window as any) line, it would behave unexpectedly:

Prettier 1.18.2 Playground link

--parser typescript
--no-semi
--trailing-comma es5

Input:

class Foo {
  public componentWillUnmount() {
    var foo = bar
    (window as any).cancelIdleCallback(this.bar)
  }

  public bar() {}
}

class Foo {
  public componentWillUnmount() {
    var foo = bar
    ;(window as any).cancelIdleCallback(this.bar)
  }

  public bar() {}
}

Output:

class Foo {
  public componentWillUnmount() {
    var foo = bar(window as any).cancelIdleCallback(this.bar)
  }

  public bar() {}
}

class Foo {
  public componentWillUnmount() {
    var foo = bar
    ;(window as any).cancelIdleCallback(this.bar)
  }

  public bar() {}
}

thomasnordquist

comment created time in 2 days

delete branch j-f1/forked-denoland-registry

delete branch : render-markdown

delete time in 2 days

push eventj-f1/read262

Jed Fox

commit sha f2cae678919f24562aea8e7348626fd55b39678f

Update deps; remove use-input-value package

view details

Jed Fox

commit sha 5e56bd8c68f823214c2a063f44fce72755163b4f

Update links to spec

view details

Jed Fox

commit sha 8160cf8ef919cf16aeaf336bd7d6dc1752b07438

Update the URLs of <img> and <object> tags to be absolute

view details

Jed Fox

commit sha 6cf258478364cc66b351de3c297ea71ad13db990

Remove the <Container /> component

view details

Jed Fox

commit sha 2da6e384f203522dc9f816a68dfc08a610fa7d5f

Add a dark theme

view details

Jed Fox

commit sha b2c21328e79f7a693798fa4e979c1e1060edd3ff

Disable HTTPS in dev mode It was causing `yarn develop` to crash.

view details

Jed Fox

commit sha a6833cab30220d4fadc1ad8d9cc800c8cccdfb45

Invert images too

view details

push time in 2 days

issue commenttypescript-eslint/typescript-eslint

[no-explicit-any] Option to disable rule for generic constraints

Does unknown have the same behavior here?

anilanar

comment created time in 2 days

push eventsosukesuzuki/prettier

Simon Lydell

commit sha 4893a86a6f6ab37b93a91441d0fac1affdb88285

Fix markdown syntax highlighting for babel-flow (#6231) Previously, the "Copy markdown" button in the playground generated markdown containing this when having selected "babel-flow" as parser: ```babel-flow This commit changes it to: ```jsx So that the code blocks get syntax highlighting on GitHub.

view details

Chris Brody

commit sha cacaa92a3f0acf9618f54cd60c9b36b37744dbde

@glimmer/syntax 0.39.3 update (#5983)

view details

James Reggio

commit sha 353b2ca064c943b1b0b20c3e8a6d12b7d2b12607

Config should not be evaluated for ignored files (#6233) Prior to this change, the CLI would resolve the config for a file before checking it against the ignored list. If the config was invalid, the CLI would report a failure. This change relocates the config-resolution phase until after the file is confirmed to not be ignored.

view details

Gavin Joyce

commit sha e8037ff25009609ca741ce68f944c97d5fe23968

[Glimmer] improve text/mustache formatting (#6206)

view details

Shinigami

commit sha f9c4ca7b389624a24666ba98b3d0403226149e6f

Add Pug plugin to the list of plugins (#6246) ref #6245

view details

Jed Fox

commit sha 7fc75e8eaaa30fc6822310e64352647b6e707ed2

Merge branch 'master' into fix-4154

view details

push time in 2 days

pull request commentprettier/prettier

Printing arguments: short circuiting some cases

Should we have some sort of perf test suite that includes inputs like this one?

duailibe

comment created time in 2 days

pull request commentprettier/prettier

Add `jsxSelfClosing` option

The difference is that with the function example, you’re still able to easily add parameters by simply typing between the parens, whereas to add children to a self-closing JSX element, you have to manually add the closing tag, which can be long if the tag has a long name. Additionally, as I said earlier, you can add an ESLint rule that will automatically make empty JSX tags self-closing, and running eslint --fix on save will result in the same behavior as older versions of Prettier in this respect.

meriadec

comment created time in 2 days

push eventj-f1/read262

Jed Fox

commit sha 09e1b5977fd1727ec5401395d3c66370b5147b5c

Re-add missing patch

view details

push time in 2 days

push eventj-f1/read262

Jed Fox

commit sha 7cf07d30bf485d6d4e47db6f55d3c53bfc7fa9a5

Run the postinstall script in the GitHub action

view details

push time in 3 days

push eventj-f1/read262

Jed Fox

commit sha c3e6e95a21145c66cf15f084793ae9079c2469cd

Add a real README

view details

Jed Fox

commit sha 747123eb40f46029e8edf58712dfd27061822a51

Create main.workflow I *definitely* didn’t struggle to do this properly; see 709ff6e14dfc606504fc8c81c9201a88945f3b87 for non-evidence of this fact.

view details

Jed Fox

commit sha fc8cfd65ee4a66d72110a362c7ebd3cee97005c8

Update dependencies

view details

Jed Fox

commit sha aaaab45a268b13ad288befdb591da956a29bdecd

Update patches

view details

push time in 3 days

push eventprettier/prettier

Shinigami

commit sha f9c4ca7b389624a24666ba98b3d0403226149e6f

Add Pug plugin to the list of plugins (#6246) ref #6245

view details

push time in 3 days

pull request commentprettier/prettier

Add Pug plugin to the list of plugins

Thank you for contributing to the Prettier ecosystem!

Shinigami92

comment created time in 3 days

PR merged prettier/prettier

Add Pug plugin to the list of plugins

ref #6245

I will release the plugin in an alpha state around next weekend?! Then it can be found at https://www.npmjs.com/package/prettier-plugin-pug

+1 -0

0 comment

1 changed file

Shinigami92

pr closed time in 3 days

PR closed prettier/prettier

Add `jsxSelfClosing` option

provide a way to put back the behavior removed in #6127 btw, this project is dope! :rocket:

Try the playground for this PR

nb: pretty huge diff in tests/jsx/__snapshots__/jsfmt.spec.js.snap :man_shrugging: is there a way to make snapshots update more "diffable"? seems that lot of code just moved, and I'm not rly used to snapshot testing

+1547 -180

8 comments

7 changed files

meriadec

pr closed time in 3 days

pull request commentprettier/prettier

Add `jsxSelfClosing` option

Sorry to shoot down your first PR to Prettier like this @meriadec 😞

meriadec

comment created time in 3 days

startedhshoff/vx

started time in 3 days

push eventj-f1/read262

Jed Fox

commit sha 0694aa48793012fa78d4bf2c0ed40069e6419fe5

Update dependencies

view details

push time in 3 days

push eventj-f1/read262

Jed Fox

commit sha f7dd0803cff9683a03b456650b2ab93f505a39c3

Update main.workflow

view details

push time in 3 days

startednektos/act

started time in 3 days

pull request commentdenoland/registry

Additional HTML renderer improvements

…and all the tests are passing 🎉

j-f1

comment created time in 3 days

push eventj-f1/read262

Jed Fox

commit sha f139faf0c2901c26255155dadafef6fe2c925f40

Update main.workflow

view details

push time in 3 days

push eventj-f1/read262

Jed Fox

commit sha 51a120ee9d28173acdd18974171efc540eb2bb60

Update main.workflow

view details

push time in 3 days

push eventj-f1/read262

Jed Fox

commit sha 73b277e2109d46d752f1c70fc8600429f5f4a735

Update main.workflow

view details

push time in 3 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha 11867ab2054a8cba42031e39538251c2e9e42f48

Install a newer version of Node on Travis

view details

push time in 3 days

push eventj-f1/read262

Jed Fox

commit sha 81e6117a4ff470de939dc7876642110a6d1c51ff

Update main.workflow

view details

push time in 3 days

push eventj-f1/read262

Jed Fox

commit sha 13d62f1bb822367b8de871c8963bca3b3c12276d

Update main.workflow

view details

push time in 3 days

Pull request review commentchalk/chalk

Fix bracketed unicode escapes in template literals

 'use strict';-const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi;+const TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,5}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi;

The Unicode standard allows six-character code points. Would it make sense to allow this?

Additionally, in the spec, unicode escape sequences with braces like this allow a hex value ≤ 0x10FFFF.

Qix-

comment created time in 3 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha a322a9f9910f68515e5f4718173310e96a607451

Install a newer version of Node on Travis

view details

push time in 3 days

issue commentdenoland/deno_std

Typo in testing's README

Can you open a PR to fix this/

motss

comment created time in 3 days

issue commentprettier/prettier

[TypeScript] Breaking pipe character when union types wrap

This behavior is intentional — people are much more likely to have a union type with lots of member types than an intersection type with lots of member types.

ArgonAlex

comment created time in 4 days

issue commentRich-Harris/packd

https://bundle.run/pixi-tiled-utils@1.0.7?name=__npm_module_1 doenst work

It opens fine for me. Try Ctrl+Shift+R to force your browser to redownload it maybe?

Prozi

comment created time in 4 days

issue commentRich-Harris/packd

https://bundle.run/pixi-tiled-utils@1.0.7?name=__npm_module_1 doenst work

What do you mean by “doesn’t work?”

Prozi

comment created time in 4 days

issue commentunpkg/unpkg.com

Does files link are served after a redirect?

The problem here is that when you don’t specify a version of the package, unpkg will redirect to the latest version. This code should work for you:

importScripts("https://unpkg.com/@khalyomede/browser-worker@0.7.0/dist/browser-worker.min.js");

BrowserWorker.enableDebug();

Note the addition of the version.

khalyomede

comment created time in 4 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha 32a97475d242f84a024fb525c4023f5568bb5be6

Remove trimRight, since it’s not supported.

view details

push time in 4 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha 2ed7b617a3d275cdffa3faa149a703fea719e5e4

Debug III

view details

push time in 4 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha 8d5b68fcd88e406e1702bbae570ca40c6aad4830

Debug II

view details

push time in 4 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha af4d96509ab0f9697b39f4f88c1730561278910a

Debug

view details

push time in 4 days

issue commenttypescript-eslint/typescript-eslint

Documentation: Getting Started Guide and User Journeys

I think the option you’re looking for is outDir.

JamesHenry

comment created time in 4 days

issue openedchalk/chalk

Bug: bracketed Unicode escapes not supported

Repro:

// example.js
const chalk = require('chalk')

console.log(chalk`\u{2192} Running test {bold foo}...`)

Expected output: <pre><code>→ Running test <strong>foo</strong>...</code></pre>

Actual output:

Error: Found extraneous } in Chalk template literal
    at node_modules/chalk/templates.js:109:11
    at String.replace (<anonymous>)
    at module.exports (node_modules/chalk/templates.js:99:6)
    at chalkTag (node_modules/chalk/index.js:221:9)
    at Chalk.chalk.template (node_modules/chalk/index.js:36:20)
    at Object.<anonymous> (example.js:3:17)

created time in 4 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha cc6c0be733a5ab89cc8097fa7ecef02936628350

Update the directory renderer to return 501 Not Implemented

view details

push time in 4 days

pull request commentdenoland/registry

Additional HTML renderer improvements

Like you did with req5, can you add another test which is a successful code render ?

I added one for Markdown, one for TypeScript, and one for a directory.

j-f1

comment created time in 5 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha 14748f4d54ef1a609ac382f058ba40f9a34c1295

Fix typos

view details

Jed Fox

commit sha d018dbf001fc3d89c071f281cc06125e5e3d8300

Add two new tests

view details

Jed Fox

commit sha 9f5ec3586ea020d0a201905144955cef41d53444

Add one more new test

view details

push time in 5 days

issue commentprettier/prettier

[Angular] Format values of i18n attributes

Feel free to open a PR!

voithos

comment created time in 5 days

Pull request review commentprettier/prettier

Added @reshadow/prettier

 Providing at least one path to `--plugin-search-dir`/`pluginSearchDirs` turns of - [`prettier-plugin-solidity`](https://github.com/prettier-solidity/prettier-plugin-solidity) by [**@mattiaerre**](https://github.com/mattiaerre) - [`prettier-plugin-svelte`](https://github.com/UnwrittenFun/prettier-plugin-svelte) by [**@UnwrittenFun**](https://github.com/UnwrittenFun) - [`prettier-plugin-toml`](https://github.com/bd82/toml-tools/tree/master/packages/prettier-plugin-toml) by [**@bd82**](https://github.com/bd82)+- [`@reshadow/prettier`](https://github.com/lttb/reshadow/tree/master/packages/prettier) by [**@lttb**] 
- [`@reshadow/prettier`](https://github.com/lttb/reshadow/tree/master/packages/prettier) by [**@lttb**](https://github.com/lttb)
comerc

comment created time in 5 days

pull request commentprettier/prettier

Config should not be evaluated for ignored files

Would be great to know what you release cadence is so I can plan to remove our local patch.

In the meantime, you can run npm install -D prettier/prettier/yarn add --dev prettier/prettierto install Prettier from this GitHub repo. It doesn’t support some older Node versions and it pulls down more dependencies than the production version, but you’ll get access to the latest changes.

jamesreggio

comment created time in 5 days

pull request commentprettier/prettier

Don't break simple template literals

Is this meant to keep expressions on one line even when they're over the line width?

I believe so. You could enable soft wrapping in your editor to make this easier to read, though.

jwbay

comment created time in 5 days

pull request commentdenoland/registry

Additional HTML renderer improvements

Additional HTML renderer improvements

- Support Markdown files (fixes #108)
- Return a 404 when the original file can’t be retrieved (fixes #107)
- Highlight files based on their extension (fixes #105)
- Refactor the server into multiple files
j-f1

comment created time in 5 days

pull request commentdenoland/registry

[WIP] Additional HTML renderer improvements

Renamed files ✅

j-f1

comment created time in 5 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha d1d77ef44831baa177daa2c4042e248e58bee6ee

Reorganize files again

view details

push time in 5 days

issue commenteslint/eslint.github.io

Proposal for adding a build step

There should be downtime since the site has an HSTS header that specifies that eslint.org should be hardcoded into browsers so it’s always visited over HTTPS.

kaicataldo

comment created time in 5 days

IssuesEvent

issue commenttypescript-eslint/typescript-eslint

[indent] Forbidden to use spaces when declaring a custom type with several types

I still think that this is a bug which should be fixed.

a-tarasyuk

comment created time in 5 days

issue commentprettier/prettier

[Angular] Unnecessary newline before ending angle bracket

Minimal repro:

Prettier 1.18.2 Playground link

--parser angular

Input:

<span>{{x}}</span>.

Output:

<span>{{ x }}</span
>.

voithos

comment created time in 5 days

issue commenttypescript-eslint/typescript-eslint

[indent] Forbidden to use spaces when declaring a custom type with several types

You might be able to fix this by putting the first type reference onto the next line and adding a | in front of it, which is syntactically valid.

a-tarasyuk

comment created time in 5 days

issue closedprettier/prettier

JavaScript: Breaking indentation with a tagged template literal.

<!--

BEFORE SUBMITTING AN ISSUE:

  1. Search for your issue on GitHub: https://github.com/prettier/prettier/issues A large number of opened issues are duplicates of existing issues. If someone has already opened an issue for what you are experiencing, you do not need to open a new issue — please add a 👍 reaction to the existing issue instead.

  2. We get a lot of requests for adding options, but Prettier is built on the principle of being opinionated about code formatting. This means we have a very high bar for adding new options. Find out more: https://prettier.io/docs/en/option-philosophy.html

Tip! Don't write this stuff manually.

  1. Go to https://prettier.io/playground
  2. Paste your code and set options
  3. Press the "Report issue" button in the lower right

-->

Prettier 1.18.2 Playground link

--parser=babel

Input:

function foo() {
  foo`
`;
}

Output:

function foo() {
  foo`
`;
}

Expected behavior:

function foo() {
  foo`
  `;
}

closed time in 5 days

sosukesuzuki

issue commentprettier/prettier

JavaScript: Breaking indentation with a tagged template literal.

This is intentional. Indenting the closing ` will add spaces to the string, which changes the meaning of the code.

sosukesuzuki

comment created time in 5 days

pull request commentprettier/prettier

Add `jsxSelfClosing` option

https://prettier.io/docs/en/option-philosophy.html

meriadec

comment created time in 5 days

Pull request review commentdenoland/registry

[WIP] Additional HTML renderer improvements

+const highlight = () => {

This file (and the CSS one) are only used for the code block renderer. Should they still be named with the generic render name, or should I call them code_scripts and code_styles?

j-f1

comment created time in 6 days

Pull request review commentdenoland/registry

[WIP] Additional HTML renderer improvements

+const path = require("path");

There already is a src/render.js, which picks which renderer to use based on the file type. What should I rename that to?

j-f1

comment created time in 6 days

pull request commentprettier/prettier

Add `jsxSelfClosing` option

Fortunately, that rule is fixable, so you can run eslint --fix and Prettier won’t mess with the result.

meriadec

comment created time in 6 days

issue commenteslint/eslint.github.io

Proposal for adding a build step

@not-an-aardvark It’s not too hard — Netlify will set up and automatically renew a Let’s Encrypt certificate with the click of a button. If you’re feeling really fancy, I believe there’s an option to upload a custom certificate too.

kaicataldo

comment created time in 6 days

issue commentdenoland/deno_std

suggestion: a decoder/assertion library

IMO allOf should be called something like chain to clarify that it passes the result of one decoder to the next one. How about value for the hardcoded decoder? For tuple, here’s a type definition that will work for an arbitrarily-large number of types:

declare function tuple<Tuple extends any[]>(
  ...args: { [I in keyof Tuple]: Decoder<Tuple[I]> }
): Decoder<Tuple>
thefliik

comment created time in 6 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha 6128de295f0ac847fee705cc966d9a6acb291ec8

Convert top-level arrow functions to old-style functions Also rename a couple functions to better fit their purpose

view details

Jed Fox

commit sha ac1acab29e6bc712bd1043f559c5640f08d54ad2

Test that an invalid URL returns 404 when requested from a browser

view details

push time in 6 days

Pull request review commentdenoland/registry

[WIP] Additional HTML renderer improvements

-const { lambdaHandler, proxy, indexPage } = require("./app");+const { lambdaHandler, proxy } = require("./app"); const assert = require("assert"); 

Done ✅

j-f1

comment created time in 6 days

Pull request review commentdenoland/registry

[WIP] Additional HTML renderer improvements

+exports.escapeHtml = unsafe =>

Is it OK if I stick with arrow functions in src/render-code/scripts.js since they’re just used to make it easier to write the JS code that’s embedded in the page?

j-f1

comment created time in 6 days

Pull request review commentdenoland/registry

[WIP] Additional HTML renderer improvements

+const { Converter } = require("showdown");

Updated.

j-f1

comment created time in 6 days

push eventj-f1/forked-denoland-registry

Jed Fox

commit sha bed7cf09e0274629091d7dba32b613a4f11a628f

Flatten directory structure

view details

push time in 6 days

more