Created
May 21, 2019 18:33
-
-
Save webstrand/65e609015b386b5644b67a10102ecb55 to your computer and use it in GitHub Desktop.
Runtime type validation for typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * A TypeScript type compatible runtime type-verification system. | |
| * | |
| * Each instance of {@link Runtype} is a complete type-verification system that | |
| * can prove that an _unknown_ type is actually _known_ type. Runtypes can be | |
| * composed via the convenience constructors {@link Union}, | |
| * {@link Intersection}, {@link Tuple}, and {@link struct}. Additionally, when | |
| * constructing a new {@link Runtype}, a parent constraint may be specified in | |
| * {@link RuntypeConfig}. | |
| * | |
| * Intentionally similar to perl's `Type::Tiny` which is used by the backend to | |
| * validate data structures send by the client. | |
| * | |
| * The {@link Runtype.assay} method provides diagnostic introspection into proof | |
| * failures. | |
| * | |
| * @author webstrand | |
| */ | |
| /** | |
| * A small dictionary of letters that should be proceeded by the indefinite | |
| * article "an" instead of "a". This is far from perfect, but provides decent | |
| * results for use in error messages. | |
| * @ignore | |
| */ | |
| const AN: Record<string, true> = ( | |
| "8@AEIOXaeiouÀÁÄÅÆÉÒÓÖÜàáæèéíöüĀāīİŌōœΩαεωϵеℓ" | |
| .split("") | |
| .reduce( | |
| (o, k) => { | |
| o[k] = true; | |
| return o; | |
| }, | |
| {} as Record<string, true> | |
| ) | |
| ); | |
| /** | |
| * A recursive tree structure of messages from runtypes that failed to match. | |
| * Keys 0..N represent messages from the immediate Runtype. The prior array | |
| * represents messages from failed Runtypes that must match for the immediate | |
| * runtype to also match. | |
| * @typedef RuntypeReport | |
| */ | |
| export interface RuntypeReport { | |
| 0: string; | |
| [i: number]: string | undefined; | |
| prior?: RuntypeReport[] | |
| } | |
| /** | |
| * Configuration object for {@link Runtype}. | |
| */ | |
| interface RuntypeConfig<T> { | |
| /** | |
| * Display name to set on the newly created {@link Runtype}. | |
| */ | |
| name?: string; | |
| /** | |
| * Either a string or a function that generates a string, producing a | |
| * message that explains why the constraint failed to match `x`. | |
| */ | |
| explain?: string | ((x: unknown, propname: string) => string); | |
| /** | |
| * Parent {@link Runtype} constraint that will be checked beforehand. | |
| */ | |
| parent?: Runtype<T>; | |
| } | |
| export type RuntypeProof<T> = T extends Runtype<infer U> ? U : T; | |
| export type RuntypeUnionResult<T extends [unknown, ...unknown[]]> = { [P in keyof T]: RuntypeProof<T[P]> }[any]; | |
| export type RuntypeIntersectionResult<T extends [unknown, ...unknown[]]> = Intersect<{ [P in keyof T]: T[P] extends Runtype<infer U> ? U : T[P] }[number]>; | |
| export type RuntypeStructResult<T extends { [key: string]: unknown }> = { [P in keyof T]: RuntypeProof<T[P]> }; | |
| export type RuntypeTupleResult<T extends unknown[]> = { [P in keyof T]: RuntypeProof<T[P]> }; | |
| export type RuntypeRecordResult<T> = { [key: string]: RuntypeProof<T> }; | |
| export type RuntypeArrayResult<T> = RuntypeProof<T>[]; | |
| /** | |
| * Generate a name for the constant type constraint. | |
| * | |
| * * `type === undefined` produces `"undefined"` | |
| * * `type === null` produces `"null"` | |
| * * `"toString" in type` produces `"Constant[${type}]"` | |
| * * Otherwise, `"Constant[${JSON.stringify(type)}]"` | |
| * | |
| * @param type Non-runtype type | |
| */ | |
| function generateName(type: unknown): string { | |
| const t = typeof type; | |
| if(typeof type === "undefined") { | |
| return "undefined"; | |
| } | |
| else if(type === null) { | |
| return "null"; | |
| } | |
| else if(typeof type === "object" && "toString" in type!) { | |
| return `Constant[${type}]`; | |
| } | |
| else { | |
| return `Constant[${JSON.stringify(type)}]`; | |
| } | |
| } | |
| /** | |
| * Generate a name for a {@link RuntypeUnion} where each sub-type's name is | |
| * joined by `" | "`. For example: `"Foo | Bar | Baz"`. | |
| * @param types List of sub-types. Either Runtype or non-runtype. | |
| */ | |
| function generateNameForUnion(types: unknown[]): string { | |
| return types.map((type) => type instanceof Runtype ? type.name : generateName(type)).join(" | "); | |
| } | |
| /** | |
| * Generarte a name for a {@link RuntypeIntersection} where each sub-type's name | |
| * is joined by `" | "`. For example: `"Foo & Bar & Baz"`. | |
| * @param types List of sub-types. Either Runtype or non-runtype. | |
| */ | |
| function generateNameForIntersection(types: unknown[]): string { | |
| return types.map((type) => type instanceof Runtype ? type.name : generateName(type)).join(" & "); | |
| } | |
| /** @ignore */ | |
| type ExplainGeneratorFunction = Exclude<RuntypeConfig<any>['explain'], undefined | string>; | |
| /** @ignore */ | |
| function generateExplainGenerator(message: undefined | string | ExplainGeneratorFunction, name: string): ExplainGeneratorFunction { | |
| const an = AN[name[0]] ? "an " : "a "; | |
| if(typeof message === "function") { | |
| return (x, propname) => propname + " is not " + an + name + ": " + message(x, propname); | |
| } | |
| else if(typeof message === "string") { | |
| return (x, propname) => propname + " is not " + an + name + ": " + message; | |
| } | |
| else { | |
| return (x, propname) => propname + " is not " + an + name; | |
| } | |
| } | |
| /** | |
| * Runtime type introspection and verification. | |
| * | |
| * @typeparam T Type that {@link prove} will prove or disprove. | |
| * @typeparam U _Optional_ type that the {@link parent} proves or disproves. | |
| */ | |
| export class Runtype<T extends U, U = unknown> implements Runtype<T> { | |
| /** Display name for debugging messages */ | |
| readonly name: string; | |
| /** Parent type-constraint */ | |
| readonly parent?: Runtype<U>; | |
| /** Prove that `x is T` */ | |
| readonly prove: (x: unknown) => x is T; | |
| /** Generate a report concluding `x is not T` */ | |
| readonly assay: (x: unknown, propname?: string) => null|RuntypeReport; | |
| /** | |
| * Create a new Runtype that proves `x is T` when {@link prove} returns | |
| * true. | |
| */ | |
| constructor(prove: (x: U) => boolean, name?: string); | |
| /** | |
| * Create a new Runtype that proves `x is T` when {@link prove} returns | |
| * true. | |
| */ | |
| constructor(prove: (x: U) => boolean, config: RuntypeConfig<U>); | |
| /** | |
| * Create a new Runtype with the provided properties. For subclass | |
| * constructors. | |
| */ | |
| constructor(prove: (x: U) => boolean, assay: ((x: unknown) => null | RuntypeReport), name: string, parent?: Runtype<U>); | |
| constructor(prove: (x: U) => boolean, configOrAssay?: string|RuntypeConfig<U>|((x: unknown) => null | RuntypeReport), name?: string, parent?: Runtype<U>) { | |
| if(typeof configOrAssay === "function") { | |
| this.name = name!; | |
| this.parent = parent; | |
| this.prove = prove as (x: unknown) => x is T; | |
| this.assay = configOrAssay; | |
| } | |
| else { | |
| const config = typeof configOrAssay === "string" ? { name: configOrAssay} : configOrAssay || {}; | |
| name = this.name = config.name || prove.name; | |
| parent = this.parent = config.parent; | |
| const explain = generateExplainGenerator(config.explain, name); | |
| if(parent) { | |
| const parentProve = parent.prove; | |
| this.prove = function (x): x is T { | |
| return parentProve(x) && prove(x); | |
| }; | |
| const parentAssay = parent.assay; | |
| this.assay = function (x, propname = "<root>") { | |
| const report = parentAssay(x, propname); | |
| if(report !== null) return { 0: explain(x, propname), prior: [report] }; | |
| if(prove(x as U)) return null; | |
| return { 0: explain(x, propname) }; | |
| }; | |
| } | |
| else { | |
| const _prove = this.prove = prove as (x: unknown) => x is T; | |
| this.assay = (x, propname = "<root>") => _prove(x) ? null : ({ 0: explain(x, propname) }); | |
| } | |
| } | |
| } | |
| /** Stringify to the display {@link name} */ | |
| toString() { return this.name } | |
| /** `object` type constraint */ | |
| static object: Runtype<object> = new Runtype((x) => typeof x === "object" && x !== null, "object"); | |
| /** `Record<string, unknown>` type constraint */ | |
| static record: Runtype<Record<string, unknown>> = new Runtype((x) => typeof x === "object" && x !== null, "record"); | |
| /** `unknown[]` type constraint */ | |
| static array: Runtype<unknown[]> = new Runtype((x) => Array.isArray(x), "array"); | |
| /** `string` type constraint */ | |
| static string: Runtype<string> = new Runtype((x) => typeof x === "string", "string"); | |
| /** `number` type constraint */ | |
| static number: Runtype<number> = new Runtype((x) => typeof x === "number", "number"); | |
| /** `boolean` type constraint */ | |
| static boolean: Runtype<boolean> = new Runtype((x) => typeof x === "boolean", "boolean"); | |
| /** `undefined` type constraint */ | |
| static undefined: Runtype<undefined> = new Runtype((x) => typeof x === "undefined", "undefined"); | |
| /** `null` type constraint */ | |
| static null: Runtype<null> = new Runtype((x) => x === null, "null"); | |
| /** See {@link RuntypeUnion} */ | |
| static Union: typeof RuntypeUnion; | |
| /** See {@link RuntypeIntersection} */ | |
| static Intersection: typeof RuntypeIntersection; | |
| /** See {@link RuntypeMaybe} */ | |
| static Maybe: typeof RuntypeMaybe; | |
| /** See {@link RuntypeStruct} */ | |
| static Struct: typeof RuntypeStruct; | |
| /** See {@link RuntypeTuple} */ | |
| static Tuple: typeof RuntypeTuple; | |
| /** See {@link RuntypeRecord} */ | |
| static Record: typeof RuntypeRecord; | |
| /** See {@link RuntypeArray} */ | |
| static Array: typeof RuntypeArray; | |
| } | |
| /** | |
| * A type constraint representing the union of a set of types. | |
| * @typeparam T An array of at least one element. Non-Runtype elements are | |
| * treated as constants. | |
| */ | |
| export class RuntypeUnion<T extends [unknown, ...unknown[]]> extends Runtype<RuntypeUnionResult<T>> { | |
| /** | |
| * Generate a new union type constraint that proves `x` satisfies one of the | |
| * constraints in {@link types}. | |
| * @param types A list of constraints. If an element is not a Runtype, it is | |
| * treated as a constant. | |
| * @param name Display name, defaults to {@link generateNameForUnion}. | |
| */ | |
| constructor(types: T, name: string = generateNameForUnion(types)) { | |
| const prove: RuntypeUnion<T>["prove"] = function (x): x is RuntypeUnionResult<T> { | |
| for(const type of types) { | |
| if(type instanceof Runtype) { | |
| if(type.prove(x)) return true; | |
| } | |
| else if(type === x) return true; | |
| } | |
| return false; | |
| }; | |
| const an = AN[name[0]] ? "an " : "a "; | |
| const assay: RuntypeUnion<T>["assay"] = function (x, propname = "<root>") { | |
| const children: RuntypeReport[] = []; | |
| for(const type of types) { | |
| if(type instanceof Runtype) { | |
| const report = type.assay(x, propname); | |
| if(report === null) return null; | |
| children.push(report); | |
| } | |
| else if(type === x) { | |
| return null; | |
| } | |
| else { | |
| children.push({ 0: propname + " !== " + generateName(type) }); | |
| } | |
| } | |
| if(children.length !== types.length) return null; | |
| return { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| }; | |
| super(prove, assay, name); | |
| } | |
| } | |
| Runtype.Union = RuntypeUnion; | |
| /** | |
| * A type constraint representing the intersection of a set of types. | |
| * | |
| * Note: There's an issue with the intersection of {@link RuntypeStruct}s or | |
| * {@link RuntypeTuples}. If excess properties are not allowed and the keys of | |
| * all the tuples or structs in the intersection don't overlap, the intersection | |
| * will always fail. This is because RuntypeIntersection checks only that _all_ | |
| * subtypes match, not that their structural equivalent matches as with | |
| * TypeScript's `&`. | |
| * | |
| * This is a complicated problem to fix, so for now I'm leaving it alone. | |
| */ | |
| export class RuntypeIntersection<T extends [unknown, ...unknown[]]> extends Runtype<RuntypeIntersectionResult<T>> { | |
| /** | |
| * Generate a new intersection type constraint that proves `x` satisfies all of the | |
| * constraints in {@link types}. | |
| * @param types A list of type constraints. If an element is not a Runtype, it is | |
| * treated as a constant. | |
| * @param name Display name, defaults to {@link generateNameForIntersection}. | |
| */ | |
| constructor(types: T, name: string = generateNameForIntersection(types)) { | |
| const prove: RuntypeIntersection<T>["prove"] = function (x): x is RuntypeIntersectionResult<T> { | |
| for(const type of types) { | |
| if(type instanceof Runtype) { | |
| if(!type.prove(x)) return false; | |
| } | |
| else if(type !== x) return false; | |
| } | |
| return true; | |
| }; | |
| const an = AN[name[0]] ? "an " : "a "; | |
| const assay: RuntypeIntersection<T>["assay"] = function (x, propname = "<root>") { | |
| const children: RuntypeReport[] = []; | |
| for(const type of types) { | |
| if(type instanceof Runtype) { | |
| const report = type.assay(x, propname); | |
| if(report !== null) children.push(report); | |
| } | |
| else if(type !== x) { | |
| children.push({ 0: propname + " !== " + generateName(type) }); | |
| } | |
| } | |
| if(children.length === 0) return null; | |
| return { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| }; | |
| super(prove, assay, name); | |
| } | |
| } | |
| Runtype.Intersection = RuntypeIntersection; | |
| /** | |
| * A type constraint representing an optional field in a {@link RuntypeStruct} | |
| * type constraint. | |
| */ | |
| export class RuntypeMaybe<T> extends Runtype<RuntypeProof<T>> { | |
| /** | |
| * Generate an optional field wrapper for the given {@link type}. | |
| * @param type A type constraint or constant to wrap. | |
| * @param name Display name, only used if constant. | |
| */ | |
| constructor(type: T, name?: string) { | |
| if(type instanceof Runtype) { | |
| super(type.prove, type.assay, type.name, type.parent); | |
| } | |
| else { | |
| super( | |
| (x) => x === type, | |
| (x, propname = "<root>") => x === type ? null : { 0: propname + " !== " + generateName(type) }, | |
| name || `Maybe[${generateName(type)}]`, | |
| ); | |
| } | |
| } | |
| } | |
| Runtype.Maybe = RuntypeMaybe; | |
| /** | |
| * A type constraint representing a primitive object with specific keys. | |
| */ | |
| export class RuntypeStruct<T extends Record<string, unknown>> extends Runtype<RuntypeStructResult<T>> { | |
| /** | |
| * Generate a primitive object type constraint matching the structure of | |
| * {@link spec}. | |
| * @param spec An object, where each key is a type constraint that must | |
| * match with a corresponding key in `x`. If a key needs to be optional, use | |
| * {@link RuntypeMaybe}. | |
| * @param name Display name, defaults to `"struct"`. | |
| * @param allowExtended When true, excess keys not listed in {@link spec} | |
| * will not result in a disproof. | |
| */ | |
| constructor(spec: T, name: string = "Struct", allowExtended: boolean = true) { | |
| const prove: RuntypeStruct<T>["prove"] = function (x): x is RuntypeStructResult<T> { | |
| if(!Runtype.record.prove(x)) return false; | |
| for(const key in spec) { | |
| const type = spec[key]; | |
| if(type instanceof Runtype.Maybe) { | |
| if(key in x && !type.prove(x[key])) return false; | |
| } | |
| if(type instanceof Runtype) { | |
| if(!type.prove(x[key])) return false; | |
| } | |
| else { | |
| if(x[key] !== type) return false; | |
| } | |
| } | |
| if(!allowExtended) { | |
| for(const key in x) { | |
| if(!(key in spec)) return false; | |
| } | |
| } | |
| return true; | |
| }; | |
| const an = AN[name[0]] ? "an " : "a "; | |
| const assay: RuntypeStruct<T>["assay"] = function (x, propname = "<root>") { | |
| const parentReport = Runtype.record.assay(x, propname); | |
| if(parentReport !== null) return { 0: propname + " is not " + an + name, prior: [parentReport] }; | |
| const children: RuntypeReport[] = []; | |
| for(const key in spec) { | |
| const type = spec[key]; | |
| if(type instanceof Runtype.Maybe) { | |
| if(key in (x as Record<string, unknown>)) { | |
| const report = type.assay((x as Record<string, unknown>)[key], propname + "." + key); | |
| if(report !== null) children.push(report); | |
| } | |
| } | |
| else if(type instanceof Runtype) { | |
| const report = type.assay((x as Record<string, unknown>)[key], propname + "." + key) | |
| if(report !== null) children.push(report); | |
| } | |
| else { | |
| if((x as Record<string, unknown>)[key] !== type) { | |
| children.push({ 0: propname + "." + key + " !== " + generateName(type) }) | |
| } | |
| } | |
| } | |
| if(!allowExtended) { | |
| for(const key in (x as Record<string, unknown>)) { | |
| if(!(key in spec)) children.push({ 0: propname + "." + key + " is not in spec" }); | |
| } | |
| } | |
| return children.length === 0 ? null : { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| }; | |
| super(prove, assay, name, Runtype.record); | |
| } | |
| } | |
| Runtype.Struct = RuntypeStruct; | |
| /** | |
| * A type constraint representing a fixed-length tuple with specific values. | |
| */ | |
| export class RuntypeTuple<T extends unknown[]> extends Runtype<RuntypeTupleResult<T>> { | |
| /** | |
| * Generate a tuple type constraint matching the structure of {@link spec}. | |
| * @param spec An array, where each value is a type constraint that must | |
| * match with the corresponding value in `x`. | |
| * @param name Display name, defaults to `"Tuple"`. | |
| * @param allowExtended When true, values in `x` with an index greater than | |
| * the last type constraint in {@link spec} will be ignored. | |
| */ | |
| constructor(spec: T, name: string = "Tuple", allowExtended: boolean = true) { | |
| const prove: RuntypeTuple<T>["prove"] = function (x): x is RuntypeTupleResult<T> { | |
| if(!Runtype.array.prove(x)) return false; | |
| if(!allowExtended && x.length !== spec.length) return false; | |
| for(let key = 0; key < spec.length; key += 1) { | |
| const type = spec[key]; | |
| if(type instanceof Runtype.Maybe) { | |
| if(key in x && !type.prove(x[key])) return false; | |
| } | |
| if(type instanceof Runtype) { | |
| if(!type.prove(x[key])) return false; | |
| } | |
| else { | |
| if(x[key] !== type) return false; | |
| } | |
| } | |
| return true; | |
| }; | |
| const an = AN[name[0]] ? "an " : "a "; | |
| const assay: RuntypeTuple<T>["assay"] = function (x, propname = "<root>") { | |
| const parentReport = Runtype.array.assay(x, propname); | |
| if(parentReport !== null) return { 0: propname + " is not " + an + name, prior: [parentReport] }; | |
| if(!allowExtended && (x as []).length !== spec.length) { | |
| return { 0: propname + " is not " + an + name + ": " + propname + ".length !== " + spec.length } | |
| } | |
| const children: RuntypeReport[] = []; | |
| for(let key = 0; key < spec.length; key += 1) { | |
| const type = spec[key]; | |
| if(type instanceof Runtype.Maybe) { | |
| if(key in (x as [])) { | |
| const report = type.assay((x as [])[key], propname + "[" + key + "]"); | |
| if(report !== null) children.push(report); | |
| } | |
| } | |
| else if(type instanceof Runtype) { | |
| const report = type.assay((x as [])[key], propname + "[" + key + "]"); | |
| if(report !== null) children.push(report); | |
| } | |
| else { | |
| if((x as [])[key] !== type) { | |
| children.push({ 0: propname + "[" + key + "] !== " + generateName(type) }) | |
| } | |
| } | |
| } | |
| return children.length === 0 ? null : { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| }; | |
| super(prove, assay, name, Runtype.array); | |
| } | |
| } | |
| Runtype.Tuple = RuntypeTuple; | |
| /** | |
| * A type constraint representing a primitive object with values of a specific | |
| * type. | |
| */ | |
| class RuntypeRecord<T> extends Runtype<RuntypeRecordResult<T>> { | |
| /** | |
| * Generate a new type constraint that requires all values on an object to | |
| * match {@link type}. | |
| * @param type Value type constraint. | |
| * @param name Display name, defaults to `"Record[" + type.name + "]"` | |
| */ | |
| constructor(type: T, name: string = "Record[" + generateName(type) + "]") { | |
| const an = AN[name[0]] ? "an " : "a "; | |
| if(type instanceof Runtype) { | |
| const prove: RuntypeRecord<T>["prove"] = function (x): x is RuntypeRecordResult<T> { | |
| if(!Runtype.record.prove(x)) return false; | |
| for(const key in x) { | |
| if(!type.prove(x[key])) return false; | |
| } | |
| return true; | |
| } | |
| const assay: RuntypeRecord<T>["assay"] = function (x, propname = "<root>") { | |
| const parentReport = Runtype.record.assay(x, propname); | |
| if(parentReport !== null) return { 0: propname + " is not " + an + name, prior: [parentReport] }; | |
| const children: RuntypeReport[] = []; | |
| for(const key in (x as Record<string, unknown>)) { | |
| const report = type.assay((x as Record<string, unknown>)[key], propname + "." + key); | |
| if(report !== null) children.push(report); | |
| } | |
| return children.length === 0 ? null : { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| } | |
| super(prove, assay, name, Runtype.record); | |
| } | |
| else { | |
| const prove: RuntypeRecord<T>["prove"] = function (x): x is RuntypeRecordResult<T> { | |
| if(!Runtype.record.prove(x)) return false; | |
| for(const key in x) { | |
| if(type !== x[key]) return false; | |
| } | |
| return true; | |
| }; | |
| const assay: RuntypeRecord<T>["assay"] = function (x, propname = "<root>") { | |
| const parentReport = Runtype.record.assay(x, propname); | |
| if(parentReport !== null) return { 0: propname + " is not " + an + name, prior: [parentReport] }; | |
| const children: RuntypeReport[] = []; | |
| const typename = generateName(type); | |
| for(const key in (x as Record<string, unknown>)) { | |
| if(type !== (x as Record<string, unknown>)[key]) { | |
| children.push({ 0: propname + "." + key + " !== " + typename }); | |
| } | |
| } | |
| return children.length === 0 ? null : { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| } | |
| super(prove, assay, name, Runtype.record); | |
| } | |
| } | |
| } | |
| Runtype.Record = RuntypeRecord; | |
| /** | |
| * A type constraint representing an array with values of a specific type. | |
| */ | |
| export class RuntypeArray<T> extends Runtype<RuntypeArrayResult<T>> { | |
| /** | |
| * Generate a new array type constraint that requires all values to | |
| * match {@link type}. | |
| * @param type Value type constraint. | |
| * @param name Display name, defaults to `"Record[" + type.name + "]"`. | |
| */ | |
| constructor(type: T, name: string = "Array[" + generateName(type) + "]") { | |
| const an = AN[name[0]] ? "an " : "a "; | |
| if(type instanceof Runtype) { | |
| const prove: RuntypeArray<T>["prove"] = function (x): x is RuntypeArrayResult<T> { | |
| if(!Runtype.array.prove(x)) return false; | |
| for(let key = 0; key < x.length; key += 1) { | |
| if(!type.prove(x[key])) return false; | |
| } | |
| return true; | |
| } | |
| const assay: RuntypeArray<T>["assay"] = function (x, propname = "<root>") { | |
| const parentReport = Runtype.record.assay(x, propname); | |
| if(parentReport !== null) return { 0: propname + " is not " + an + name, prior: [parentReport] }; | |
| const children: RuntypeReport[] = []; | |
| for(let key = 0; key < (x as []).length; key += 1) { | |
| const report = type.assay((x as [])[key], propname + "[" + key + "]"); | |
| if(report !== null) children.push(report); | |
| } | |
| return children.length === 0 ? null : { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| } | |
| super(prove, assay, name, Runtype.array); | |
| } | |
| else { | |
| const prove: RuntypeArray<T>["prove"] = function (x): x is RuntypeArrayResult<T> { | |
| if(!Runtype.array.prove(x)) return false; | |
| for(let key = 0; key < x.length; key += 1) { | |
| if(type !== x[key]) return false; | |
| } | |
| return true; | |
| }; | |
| const assay: RuntypeArray<T>["assay"] = function (x, propname = "<root>") { | |
| const parentReport = Runtype.record.assay(x, propname); | |
| if(parentReport !== null) return { 0: propname + " is not " + an + name, prior: [parentReport] }; | |
| const children: RuntypeReport[] = []; | |
| const typename = generateName(type); | |
| for(let key = 0; key < (x as []).length; key += 1) { | |
| if(type !== (x as [])[key]) { | |
| children.push({ 0: propname + "[" + key + "] !== " + typename }); | |
| } | |
| } | |
| return children.length === 0 ? null : { | |
| 0: propname + " is not " + an + name, | |
| children, | |
| }; | |
| } | |
| super(prove, assay, name, Runtype.array); | |
| } | |
| } | |
| } | |
| Runtype.Array = RuntypeArray; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment