/** * Copyright 2022 Gravwell, Inc. All rights reserved. * * Contact: [legal@gravwell.io](mailto:legal@gravwell.io) * * This software may be modified and distributed under the terms of the MIT * license. See the LICENSE file for details. */ import { array as arrayDecoder, boolean as booleanDecoder, constant as constantDecoder, Decoder, DecodeResult, dict as dictDecoder, either as eitherDecoder, exact as exactDecoder, inexact as inexactDecoder, instanceOf as instanceOfDecoder, integer as integerDecoder, iso8601 as iso8601Decoder, json as jsonDecoder, jsonArray as jsonArrayDecoder, jsonObject as jsonObjectDecoder, lazy as lazyDecoder, maybe as maybeDecoder, nonEmptyArray as nonEmptyArrayDecoder, nonEmptyString as nonEmptyStringDecoder, null_ as nullDecoder, nullable as nullableDecoder, number as numberDecoder, object as objectDecoder, oneOf as oneofDecoder, optional as optionalDecoder, regex as regexDecoder, string as stringDecoder, taggedUnion as taggedUnionDecoder, tuple as tupleDecoder, undefined_ as undefinedDecoder, unknown as unknownDecoder, } from 'decoders'; import { Annotation } from 'decoders/annotate'; import { AcceptanceFn, DecoderType, Scalar } from 'decoders/Decoder'; import { ObjectDecoderType } from 'decoders/lib/objects'; import { DecoderTypes, Values } from 'decoders/lib/unions'; import { Instance, Klass } from 'decoders/lib/utilities'; /** * A `Verifier` is a decoder that performs no transforms to the value it * inspects. * * That is, a `Verifier` can tell you if it's possible to decode an unknown * value `V` into type `T` without changing the type of `V`. As such, a * `Verifier` can work as a `Decoder` AND as a type guard for type `T`. */ export interface Verifier extends Decoder { /** * Returns true if `value` can be decoded to a `T`, otherwise false. * * Because `guard` asserts that `value` already _is_ a `T` (that is, it can be * decoded to a `T` with no type transforms), `guard` can safely be used as a * type guard for type `T`. */ guard(value: unknown): value is T; /** * Adds an extra rejection predicate to the Verifier. * * @see {@link https://decoders.cc/Decoder.html#reject} */ rejectVerifier(rejectFn: (value: T) => string | Annotation | null): Verifier; /** * Adds an extra acceptance predicate to the Verifier. * * @see {@link https://decoders.cc/Decoder.html#refine} */ refineVerifier(predicate: (value: T) => value is N, msg: string): Verifier; refineVerifier(predicate: (value: T) => boolean, msg: string): Verifier; /** * Changes the error message of the Verifier. * * @see {@link https://decoders.cc/Decoder.html#describe} */ describeVerifier(message: string): Verifier; } /** * Creates a Verifier from a Decoder. Be careful with this - one should only * create a verifier from a decoder if the decoder never changes the type of a * decoded value. * * For example, the following decoders shouldn't be turned into verifiers: * * - iso8601: Decodes a **string** to a **Date** * - numericBoolean: Decodes a **number** to a **boolean** * - set: Decodes an **Array** to a **Set** */ const verifierFactory = (d: Decoder): Verifier => ({ guard: (value: unknown): value is T => d.decode(value).ok, refineVerifier: (predicate: unknown, msg: string): Verifier => verifierFactory(d.refine(predicate as any, msg)), rejectVerifier: (rejectFn: (value: T) => string | Annotation | null): Verifier => verifierFactory(d.reject(rejectFn)), describeVerifier: (message: string): Verifier => verifierFactory(d.describe(message)), verify: (blob: unknown, formatterFn?: ((ann: Annotation) => string | Error) | undefined): T => d.verify(blob, formatterFn), value: (blob: unknown): T | undefined => d.value(blob), decode: (blob: unknown): DecodeResult => d.decode(blob), refine: (predicate: unknown, msg: string): Decoder => d.refine(predicate as any, msg), reject: (rejectFn: (value: T) => string | Annotation | null): Decoder => d.reject(rejectFn), describe: (message: string): Decoder => d.describe(message), transform: (transformFn: (value: T) => V): Decoder => d.transform(transformFn), then: (next: AcceptanceFn): Decoder => d.then(next), peek_UNSTABLE: (next: AcceptanceFn): Decoder => d.peek_UNSTABLE(next), }); export const string: Verifier = verifierFactory(stringDecoder); export const regex = (r: RegExp, msg: string): Verifier => verifierFactory(regexDecoder(r, msg)); export const iso8601String: Verifier = verifierFactory(iso8601Decoder.transform(d => d.toISOString())); export const nonEmptyString: Verifier = verifierFactory(nonEmptyStringDecoder); export const number: Verifier = verifierFactory(numberDecoder); export const integer: Verifier = verifierFactory(integerDecoder); export const boolean: Verifier = verifierFactory(booleanDecoder); export const null_: Verifier = verifierFactory(nullDecoder); export const undefined_: Verifier = verifierFactory(undefinedDecoder); export const unknown: Verifier = verifierFactory(unknownDecoder); export const mixed: Verifier = unknown; export const jsonObject = verifierFactory(jsonObjectDecoder); export const jsonArray = verifierFactory(jsonArrayDecoder); export const json = verifierFactory(jsonDecoder); export const instanceOf = >(klass: K): Verifier> => verifierFactory(instanceOfDecoder(klass)); export const constant = (c: T): Verifier => verifierFactory(constantDecoder(c)); export const optional = (g: Verifier): Verifier => verifierFactory(optionalDecoder(g)); export const nullable = (g: Verifier): Verifier => verifierFactory(nullableDecoder(g)); export const maybe = (g: Verifier): Verifier => verifierFactory(maybeDecoder(g)); export const array = (g: Verifier): Verifier> => verifierFactory(arrayDecoder(g)); export const nonEmptyArray = (g: Verifier): Verifier<[T, ...Array]> => verifierFactory(nonEmptyArrayDecoder(g)); export const tuple1 = (a: Verifier): Verifier<[A]> => verifierFactory(tupleDecoder(a)); export const tuple2 = (a: Verifier, b: Verifier): Verifier<[A, B]> => verifierFactory(tupleDecoder(a, b)); export const tuple3 = (a: Verifier, b: Verifier, c: Verifier): Verifier<[A, B, C]> => verifierFactory(tupleDecoder(a, b, c)); export const tuple4 = ( a: Verifier, b: Verifier, c: Verifier, d: Verifier, ): Verifier<[A, B, C, D]> => verifierFactory(tupleDecoder(a, b, c, d)); export const tuple5 = ( a: Verifier, b: Verifier, c: Verifier, d: Verifier, e: Verifier, ): Verifier<[A, B, C, D, E]> => verifierFactory(tupleDecoder(a, b, c, d, e)); export const exact = >>( VerifiersByKey: O, ): Verifier<{ [K in keyof ObjectDecoderType]: ObjectDecoderType[K] }> => verifierFactory(exactDecoder(VerifiersByKey)); export const object = >>( VerifiersByKey: O, ): Verifier<{ [K in keyof ObjectDecoderType]: ObjectDecoderType[K] }> => verifierFactory(objectDecoder(VerifiersByKey)); export const inexact = >>( VerifiersByKey: O, ): Verifier<{ [K in keyof ObjectDecoderType]: ObjectDecoderType[K] } & Record> => verifierFactory(inexactDecoder(VerifiersByKey)); export const dict = (v: Verifier): Verifier> => verifierFactory(dictDecoder(v)); export const either = >>(...vs: T): Verifier> => verifierFactory(eitherDecoder(...vs)); export const oneOf = (constants: ReadonlyArray): Verifier => verifierFactory(oneofDecoder(constants)); export const taggedUnion = >>( field: string, mapping: O, ): Verifier }>> => verifierFactory(taggedUnionDecoder(field, mapping)); export const lazy = (decoderFn: () => Verifier): Verifier => verifierFactory(lazyDecoder(decoderFn));