Add Generics, as well as some fixes

This commit is contained in:
Vladyslav Tserman
2017-11-30 00:18:57 -08:00
parent cfd43ded94
commit 24ea87eba2
2 changed files with 291 additions and 78 deletions
+75 -49
View File
@@ -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<any>;
validateSync(value: any, options?: ValidateOptions): any;
isValid(value: any, options?: any, callback?: () => void): Promise<boolean>;
concat(schema: this): this;
validate<U>(value: U, options?: ValidateOptions): Promise<ValidationError|U>;
validateSync<U>(value: U, options?: ValidateOptions): ValidationError|U;
isValid(value: any, options?: any): Promise<boolean>;
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>): this;
test(name: string, message: string, test: (value?: any) => boolean, callbackStyleAsync?: boolean): this;
test(options: TestOptions): this;
transform(fn: TransformFunction<this>): 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<T> = ((this: T, value: any, originalValue: any) => any);
export interface WhenOptionsBuilder<T> {
(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<T> = WhenOptionsBuilder<T>
| { 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 {
}
+216 -29
View File
@@ -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'
}
};