From 24ea87eba2b97630c5f1e80022405c8df4a2f1d2 Mon Sep 17 00:00:00 2001 From: Vladyslav Tserman Date: Wed, 29 Nov 2017 17:43:06 -0800 Subject: [PATCH] Add Generics, as well as some fixes --- types/yup/index.d.ts | 124 ++++++++++++--------- types/yup/yup-tests.ts | 245 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 291 insertions(+), 78 deletions(-) diff --git a/types/yup/index.d.ts b/types/yup/index.d.ts index e7a8e29cde..a7f9f4a8d3 100644 --- a/types/yup/index.d.ts +++ b/types/yup/index.d.ts @@ -5,11 +5,12 @@ // TypeScript Version: 2.2 export function reach(schema: Schema, path: string, value?: any, context?: any): Schema; -export function addMethod(schemaType: Schema, name: string, method: () => Schema): void; -export function ref(path: string, options: { contextPrefix: string }): Ref; +export function addMethod(schemaType: Schema, name: string, method: (this: Schema) => Schema): void; +export function ref(path: string, options?: { contextPrefix: string }): Ref; export function lazy(fn: (value: any) => Schema): Lazy; +export function ValidationError(errors: string | string[], value: any, path: string, type?: any): ValidationError; -export const mixed: SchemaConstructor; +export const mixed: MixedSchemaConstructor; export const string: StringSchemaConstructor; export const number: NumberSchemaConstructor; export const boolean: BooleanSchemaConstructor; @@ -18,51 +19,40 @@ export const date: DateSchemaConstructor; export const array: ArraySchemaConstructor; export const object: ObjectSchemaConstructor; -export interface ValidationError { - errors: string | string[]; - value: any; - path: string; - inner?: ValidationError[]; -} - -export interface Ref { -} - -export interface Lazy { -} - -export interface SchemaConstructor { - (): Schema; - new(options?: any): Schema; -} - export interface Schema { - clone(): Schema; - label(label: string): Schema; - meta(metadata: any): Schema; + clone(): this; + label(label: string): this; + meta(metadata: any): this; meta(): any; describe(): SchemaDescription; - concat(schema: Schema): Schema; - validate(value: any, options?: ValidateOptions, callback?: () => void): Promise; - validateSync(value: any, options?: ValidateOptions): any; - isValid(value: any, options?: any, callback?: () => void): Promise; + concat(schema: this): this; + validate(value: U, options?: ValidateOptions): Promise; + validateSync(value: U, options?: ValidateOptions): ValidationError|U; + isValid(value: any, options?: any): Promise; isValidSync(value: any, options?: any): boolean; - cast(value: any): any; + cast(value: any, options?: any): any; isType(value: any): boolean; - strict(isStrict: boolean): Schema; - strip(stripField: boolean): Schema; - withMutation(builder: (current: Schema) => void): void; - default(value: any): Schema; - default(): any; - nullable(isNullable: boolean): Schema; - required(message?: string): Schema; - typeError(message?: string): Schema; - oneOf(arrayOfValues: any[], message?: string): Schema; - notOneOf(arrayOfValues: any[], message?: string): Schema; - when(keys: string | any[], builder: ((value: any, schema: Schema) => Schema) | object): Schema; - test(name: string, message: string, test: (value: any) => boolean, callbackStyleAsync?: boolean): Schema; - test(options: TestOptions): Schema; - transform(transformation: (currentValue: any, originalValue: any) => any): Schema; + strict(isStrict: boolean): this; + strip(strip: boolean): this; + withMutation(fn: (current: this) => void): void; + default(value?: any): this; + nullable(isNullable: boolean): this; + required(message?: string): this; + typeError(message?: string): this; + oneOf(arrayOfValues: any[], message?: string): this; + notOneOf(arrayOfValues: any[], message?: string): this; + when(keys: string | any[], builder: WhenOptions): this; + test(name: string, message: string, test: (value?: any) => boolean, callbackStyleAsync?: boolean): this; + test(options: TestOptions): this; + transform(fn: TransformFunction): this; +} + +export interface MixedSchemaConstructor { + (): MixedSchema; + new(options?: { type?: string, [key: string]: any }): MixedSchema; +} + +export interface MixedSchema extends Schema { } export interface StringSchemaConstructor { @@ -71,7 +61,6 @@ export interface StringSchemaConstructor { } export interface StringSchema extends Schema { - required(message?: string): StringSchema; min(limit: number | Ref, message?: string): StringSchema; max(limit: number | Ref, message?: string): StringSchema; matches(regex: RegExp, message?: string): StringSchema; @@ -89,7 +78,6 @@ export interface NumberSchemaConstructor { } export interface NumberSchema extends Schema { - required(message?: string): NumberSchema; min(limit: number | Ref, message?: string): NumberSchema; max(limit: number | Ref, message?: string): NumberSchema; positive(message?: string): NumberSchema; @@ -124,7 +112,6 @@ export interface ArraySchemaConstructor { export interface ArraySchema extends Schema { of(type: Schema): ArraySchema; - required(message?: string): ArraySchema; min(limit: number | Ref, message?: string): ArraySchema; max(limit: number | Ref, message?: string): ArraySchema; ensure(): ArraySchema; @@ -132,19 +119,32 @@ export interface ArraySchema extends Schema { } export interface ObjectSchemaConstructor { - (): ObjectSchema; + (fields?: any): ObjectSchema; new(): ObjectSchema; } export interface ObjectSchema extends Schema { shape(fields: any, noSortEdges?: Array<[string, string]>): ObjectSchema; - from(fromKey: string, toKey: string, alias: boolean): ObjectSchema; + from(fromKey: string, toKey: string, alias?: boolean): ObjectSchema; noUnknown(onlyKnownKeys: boolean, message?: string): ObjectSchema; - transformKeys(callback: (key: any) => any): void + transformKeys(callback: (key: any) => any): void; camelCase(): ObjectSchema; constantCase(): ObjectSchema; } +export type TransformFunction = ((this: T, value: any, originalValue: any) => any); + +export interface WhenOptionsBuilder { + (value: any, schema: T): T; + (v1: any, v2: any, schema: T): T; + (v1: any, v2: any, v3: any, schema: T): T; + (v1: any, v2: any, v3: any, v4: any, schema: T): T; +} + +export type WhenOptions = WhenOptionsBuilder +| { is: boolean | ((value: any) => boolean), then: any, otherwise: any } +| object; + export interface ValidateOptions { /** * Only validate the input, and skip and coercion or transformation. Default - false @@ -201,3 +201,29 @@ export interface SchemaDescription { meta: object; tests: string[]; } + +export interface ValidationError { + name: string; + value: any; + /** + * A string, indicating where there error was thrown. path is empty at the root level. + */ + path: string; + type: any; + /** + * array of error messages + */ + errors: string | string[]; + + /** + * In the case of aggregate errors, inner is an array of ValidationErrors throw earlier in the validation chain. + */ + inner?: ValidationError[]; +} + +export interface Ref { + [key: string]: any; +} + +export interface Lazy extends Schema { +} diff --git a/types/yup/yup-tests.ts b/types/yup/yup-tests.ts index bba33755b1..a22d96f594 100644 --- a/types/yup/yup-tests.ts +++ b/types/yup/yup-tests.ts @@ -1,46 +1,233 @@ import * as yup from 'yup'; +// tslint:disable-next-line:no-duplicate-imports +import { reach, date, Schema, ObjectSchema, ValidationError, MixedSchema, SchemaDescription, TestOptions, ValidateOptions } from 'yup'; -const schema = yup.object().shape({ - name: yup.string().required(), - age: yup.number().required().positive().integer(), - email: yup.string().email(), - website: yup.string().url(), - createdOn: yup.date().default(() => new Date()) +// reach function +let schema = yup.object().shape({ + nested: yup.object().shape({ + arr: yup.array().of( + yup.object().shape({ num: yup.number().max(4) }) + ) + }) + }); +reach(schema, 'nested.arr.num'); +reach(schema, 'nested.arr[].num'); + +// addMethod function +yup.addMethod(yup.date(), 'format', function(this: Schema) { + return this.clone(); }); -// check validity -schema.isValid({ - name: 'jimmy', - age: 24 -}) -.then(valid => valid /* => true */); +// ref function +schema = yup.object().shape({ + baz: yup.ref('foo.bar'), + foo: yup.object().shape({ + bar: yup.string() + }), + x: yup.ref('$x') +}); -class CustomDateSchema extends yup.date { - isWednesday(message?: string): CustomDateSchema { - return this.test({ +schema.cast({ foo: { bar: 'boom' } }, { context: { x: 5 } }); + +// lazy function +const node: ObjectSchema = yup.object().shape({ + id: yup.number(), + child: yup.lazy(() => + node.default(undefined) + ) +}); +const renderable = yup.lazy(value => { + switch (typeof value) { + case 'number': + return yup.number(); + case 'string': + return yup.string(); + default: + return yup.mixed(); + } +}); +const renderables = yup.array().of(renderable); + +// ValidationError +let error: ValidationError = yup.ValidationError('error', 'value', 'path'); +error = yup.ValidationError(['error', 'error2'], true, 'path'); +error = yup.ValidationError(['error', 'error2'], 5, 'path'); + +// mixed +let mixed: MixedSchema = yup.mixed(); +mixed.clone(); +mixed.label('label'); +mixed.meta({ meta: 'value' }); +mixed.describe().label; +mixed.describe().meta; +mixed.describe().tests; +mixed.describe().type; +mixed.concat(yup.string()); +mixed.validate({}); +mixed.validate({ hello: 'world' }, { strict: true }).then(value => value); +mixed.isValid(undefined, (valid: true) => true); +mixed.isValid({ hello: 'world' }).then(valid => valid); +mixed.cast({}); +mixed.isType('hello'); +mixed.strict(true); +mixed.strip(true); +mixed.withMutation(schema => {}); +mixed.default({ number: 5}); +mixed.default(() => ({ number: 5})); +mixed.default(); +mixed.nullable(true); +mixed.required(); +mixed.typeError('type error'); +mixed.oneOf(['hello', 'world'], 'message'); +mixed.notOneOf(['hello', 'world'], 'message'); +mixed.when('isBig', { + is: value => true, + then: yup.number().min(5), + otherwise: yup.number().min(0) +}); +mixed.when('isBig', { + is: true, + then: yup.number().min(5), + otherwise: yup.number().min(0) +}).when('$other', (value: any, schema: MixedSchema) => value === 4 ? schema.required() : schema); +// tslint:disable-next-line:no-invalid-template-strings +mixed.test('is-jimmy', '${path} is not Jimmy', value => value === 'jimmy'); +mixed.test({ + name: 'lessThan5', + exclusive: true, + // tslint:disable-next-line:no-invalid-template-strings + message: '${path} must be less than 5 characters', + test: value => value == null || value.length <= 5 +}); + +yup.string().transform(function(this, value: any, originalvalue: any) { + return this.isType(value) && value !== null ? value.toUpperCase() : value; +}); + +// Extending Schema Types +class ExtendsMixed extends yup.mixed {} +mixed = new ExtendsMixed(); + +class ExtendsMixed2 extends yup.mixed { + constructor() { + super({ type: 'CustomType' }); + } +} +mixed = new ExtendsMixed2(); + +/** + * Creating new Types + */ +class DateSchema extends yup.date { + isWednesday(message?: string): DateSchema { + return this.clone().test({ name: 'Wednesday', // tslint:disable-next-line:no-invalid-template-strings message: message || '${path} must be Wednesday', test: value => true /* Check that day is Wednesday */, - }) as CustomDateSchema; + }); } } -const date = () => new CustomDateSchema(); - yup.object().shape({ - startDate: date().isWednesday().required() -}); -const valid = schema.isValidSync({ + startDate: new DateSchema().isWednesday().required() +}).isValidSync({ startDate: '2017-11-29', }); -// test when -yup.object().shape({ - checkWednesday: yup.bool().required(), +// String schema +const strSchema = yup.string(); +strSchema.isValid('hello'); // => true +strSchema.required(); +strSchema.min(5, 'message'); +strSchema.max(5, 'message'); +strSchema.matches(/(hi|bye)/); +strSchema.email(); +strSchema.url(); +strSchema.ensure(); +strSchema.trim(); +strSchema.lowercase(); +strSchema.uppercase(); + +// Number schema +const numSchema = yup.number(); +numSchema.isValid(10); // => true +numSchema.min(5, 'message'); +numSchema.max(5, 'message'); +numSchema.positive(); +numSchema.negative(); +numSchema.integer(); +numSchema.truncate(); +numSchema.round('floor'); +numSchema.validate(5, { strict: true }).then(value => value).catch(err => err); + +// Boolean Schema +const boolSchema = yup.boolean(); +boolSchema.isValid(true); // => true + +// Date Schema +const dateSchema = yup.date(); +dateSchema.isValid(new Date()); // => true +dateSchema.min(new Date()); +dateSchema.min('2017-11-12'); +dateSchema.min(new Date(), 'message'); +dateSchema.min('2017-11-12', 'message'); +dateSchema.max(new Date()); +dateSchema.max('2017-11-12'); +dateSchema.max(new Date(), 'message'); +dateSchema.max('2017-11-12', 'message'); + +// Array Schema +const arrSchema = yup.array().of(yup.number().min(2)); +arrSchema.isValid([2, 3]); // => true +arrSchema.isValid([1, -24]); // => false +arrSchema.required(); +arrSchema.ensure(); +arrSchema.compact(value => value === null); + +// Object Schema +const objSchema = yup.object().shape({ name: yup.string().required(), - startDate: date() - .required() - .when(['checkWednesday', 'name'], (checkWednesday: boolean, name: string, schema: CustomDateSchema) => { - return checkWednesday ? schema.isWednesday() : schema; - }) + age: yup.number().required().positive().integer(), + email: yup.string().email(), + website: yup.string().url(), }); +yup.object().shape({ + num: yup.number() +}); +// or +yup.object({ + num: yup.number() +}); + +objSchema.from('prop', 'myProp'); +objSchema.from('prop', 'myProp', true); +objSchema.noUnknown(true); +objSchema.noUnknown(true, 'message'); +objSchema.transformKeys(key => key.toUpperCase()); +objSchema.camelCase(); +objSchema.constantCase(); + +const description: SchemaDescription = { + type: 'type', + label: 'label', + meta: { key: 'value' }, + tests: ['test1', 'test2'] +}; + +const testOptions: TestOptions = { + name: 'name', + test: value => true, + message: 'validation error message', + params: { param1: 'value'}, + exclusive: true +}; + +const validateOptions: ValidateOptions = { + strict: true, + abortEarly: true, + stripUnknown: true, + recursive: true, + context: { + key: 'value' + } +};