[yup] Use TestOptionsMessage consistently throughout (#37648)

* [yup] Use TestOptionsMessage consistently throughout

* With updated formatting

* full on prettier

* update to 3.1 (forgot it on the redo)

* Update tests to illustrate changes

* Add bad setLocale test

* Add example of message returning an object

* restrict return type to string | Record<string, any>

* Make return value generic, fill out remaining messages with their respective params

* Add name to maintainers
This commit is contained in:
Kalley Powell 2019-08-20 13:54:53 -05:00 committed by Sheetal Nandi
parent 6dccf84292
commit e650ec14ab
2 changed files with 123 additions and 139 deletions

207
types/yup/index.d.ts vendored
View File

@ -11,19 +11,15 @@
// Dan Rumney <https://github.com/dancrumb>
// Desmond Koh <https://github.com/deskoh>
// Maurice de Beijer <https://github.com/mauricedb>
// Kalley Powell <https://github.com/kalley>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// TypeScript Version: 3.1
export function reach<T>(
schema: Schema<T>,
path: string,
value?: any,
context?: any
): Schema<T>;
export function reach<T>(schema: Schema<T>, path: string, value?: any, context?: any): Schema<T>;
export function addMethod<T extends Schema<any>>(
schemaCtor: AnySchemaConstructor,
name: string,
method: (this: T, ...args: any[]) => T
method: (this: T, ...args: any[]) => T,
): void;
export function ref(path: string, options?: { contextPrefix: string }): Ref;
export function lazy<T>(fn: (value: T) => Schema<T>): Lazy;
@ -48,9 +44,9 @@ export type AnySchemaConstructor =
| ArraySchemaConstructor
| ObjectSchemaConstructor;
export type TestOptionsMessage =
export type TestOptionsMessage<Extra extends Record<string, any> = {}, R = any> =
| string
| ((params: object & Partial<TestMessageParams>) => string);
| ((params: Extra & Partial<TestMessageParams>) => R);
export interface Schema<T> {
clone(): this;
@ -73,21 +69,17 @@ export interface Schema<T> {
default(value: any): this;
default(): T;
typeError(message?: TestOptionsMessage): this;
oneOf(arrayOfValues: Array<T | Ref>, message?: TestOptionsMessage): this;
notOneOf(arrayOfValues: any[], message?: TestOptionsMessage): this;
oneOf(arrayOfValues: Array<T | Ref | null>, message?: TestOptionsMessage<{ values: T | Ref }>): this;
notOneOf(arrayOfValues: any[], message?: TestOptionsMessage<{ values: T | Ref }>): this;
when(keys: string | any[], builder: WhenOptions<this>): this;
test(
name: string,
message:
| string
| ((params: object & Partial<TestMessageParams>) => string),
test: (
this: TestContext,
value?: any
) => boolean | ValidationError | Promise<boolean | ValidationError>,
callbackStyleAsync?: boolean
message: TestOptionsMessage,
test: (this: TestContext, value?: any) => boolean | ValidationError | Promise<boolean | ValidationError>,
callbackStyleAsync?: boolean,
): this;
test(options: TestOptions): this;
// tslint:disable-next-line:no-unnecessary-generics
test<P>(options: TestOptions<P>): this;
transform(fn: TransformFunction<this>): this;
}
@ -105,7 +97,7 @@ export interface MixedSchema<T = any> extends Schema<T> {
required(message?: TestOptionsMessage): MixedSchema<Exclude<T, undefined>>;
notRequired(): MixedSchema<T | undefined>;
concat(schema: this): this;
concat<U >(schema: MixedSchema<U>): MixedSchema<T | U>;
concat<U>(schema: MixedSchema<U>): MixedSchema<T | U>;
}
export interface StringSchemaConstructor {
@ -113,19 +105,18 @@ export interface StringSchemaConstructor {
new (): StringSchema;
}
export interface StringSchema<T extends string | null | undefined = string>
extends Schema<T> {
length(limit: number | Ref, message?: TestOptionsMessage): StringSchema<T>;
min(limit: number | Ref, message?: TestOptionsMessage): StringSchema<T>;
max(limit: number | Ref, message?: TestOptionsMessage): StringSchema<T>;
export interface StringSchema<T extends string | null | undefined = string> extends Schema<T> {
length(limit: number | Ref, message?: TestOptionsMessage<{ length: number | Ref }>): StringSchema<T>;
min(limit: number | Ref, message?: TestOptionsMessage<{ min: number | Ref }>): StringSchema<T>;
max(limit: number | Ref, message?: TestOptionsMessage<{ max: number | Ref }>): StringSchema<T>;
matches(
regex: RegExp,
messageOrOptions?:
| TestOptionsMessage
| { message?: TestOptionsMessage; excludeEmptyString?: boolean }
| TestOptionsMessage<{ regex: RegExp }>
| { message?: TestOptionsMessage<{ regex: RegExp }>; excludeEmptyString?: boolean },
): StringSchema<T>;
email(message?: TestOptionsMessage): StringSchema<T>;
url(message?: TestOptionsMessage): StringSchema<T>;
email(message?: TestOptionsMessage<{ regex: RegExp }>): StringSchema<T>;
url(message?: TestOptionsMessage<{ regex: RegExp }>): StringSchema<T>;
ensure(): StringSchema<T>;
trim(message?: TestOptionsMessage): StringSchema<T>;
lowercase(message?: TestOptionsMessage): StringSchema<T>;
@ -142,23 +133,16 @@ export interface NumberSchemaConstructor {
new (): NumberSchema;
}
export interface NumberSchema<T extends number | null | undefined = number>
extends Schema<T> {
min(limit: number | Ref, message?: TestOptionsMessage): NumberSchema<T>;
max(limit: number | Ref, message?: TestOptionsMessage): NumberSchema<T>;
lessThan(
limit: number | Ref,
message?: TestOptionsMessage
): NumberSchema<T>;
moreThan(
limit: number | Ref,
message?: TestOptionsMessage
): NumberSchema<T>;
positive(message?: TestOptionsMessage): NumberSchema<T>;
negative(message?: TestOptionsMessage): NumberSchema<T>;
export interface NumberSchema<T extends number | null | undefined = number> extends Schema<T> {
min(limit: number | Ref, message?: TestOptionsMessage<{ min: number }>): NumberSchema<T>;
max(limit: number | Ref, message?: TestOptionsMessage<{ max: number }>): NumberSchema<T>;
lessThan(limit: number | Ref, message?: TestOptionsMessage<{ less: number }>): NumberSchema<T>;
moreThan(limit: number | Ref, message?: TestOptionsMessage<{ more: number }>): NumberSchema<T>;
positive(message?: TestOptionsMessage<{ more: number }>): NumberSchema<T>;
negative(message?: TestOptionsMessage<{ less: number }>): NumberSchema<T>;
integer(message?: TestOptionsMessage): NumberSchema<T>;
truncate(): NumberSchema<T>;
round(type: "floor" | "ceil" | "trunc" | "round"): NumberSchema<T>;
round(type: 'floor' | 'ceil' | 'trunc' | 'round'): NumberSchema<T>;
nullable(isNullable?: true): NumberSchema<T | null>;
nullable(isNullable: false): NumberSchema<Exclude<T, null>>;
nullable(isNullable?: boolean): NumberSchema<T>;
@ -171,14 +155,11 @@ export interface BooleanSchemaConstructor {
new (): BooleanSchema;
}
export interface BooleanSchema<T extends boolean | null | undefined = boolean>
extends Schema<T> {
export interface BooleanSchema<T extends boolean | null | undefined = boolean> extends Schema<T> {
nullable(isNullable?: true): BooleanSchema<T | null>;
nullable(isNullable: false): BooleanSchema<Exclude<T, null>>;
nullable(isNullable?: boolean): BooleanSchema<T>;
required(
message?: TestOptionsMessage
): BooleanSchema<Exclude<T, undefined>>;
required(message?: TestOptionsMessage): BooleanSchema<Exclude<T, undefined>>;
notRequired(): BooleanSchema<T | undefined>;
}
@ -187,16 +168,9 @@ export interface DateSchemaConstructor {
new (): DateSchema;
}
export interface DateSchema<T extends Date | null | undefined = Date>
extends Schema<T> {
min(
limit: Date | string | Ref,
message?: TestOptionsMessage
): DateSchema<T>;
max(
limit: Date | string | Ref,
message?: TestOptionsMessage
): DateSchema<T>;
export interface DateSchema<T extends Date | null | undefined = Date> extends Schema<T> {
min(limit: Date | string | Ref, message?: TestOptionsMessage<{ min: Date | string }>): DateSchema<T>;
max(limit: Date | string | Ref, message?: TestOptionsMessage<{ max: Date | string }>): DateSchema<T>;
nullable(isNullable?: true): DateSchema<T | null>;
nullable(isNullable: false): DateSchema<Exclude<T, null>>;
nullable(isNullable?: boolean): DateSchema<T>;
@ -209,22 +183,16 @@ export interface ArraySchemaConstructor {
new (): ArraySchema<{}>;
}
interface BasicArraySchema<T extends any[] | null | undefined>
extends Schema<T> {
min(limit: number | Ref, message?: TestOptionsMessage): this;
max(limit: number | Ref, message?: TestOptionsMessage): this;
interface BasicArraySchema<T extends any[] | null | undefined> extends Schema<T> {
min(limit: number | Ref, message?: TestOptionsMessage<{ min: number }>): this;
max(limit: number | Ref, message?: TestOptionsMessage<{ max: number }>): this;
ensure(): this;
compact(
rejector?: (
value: InferredArrayType<T>,
index: number,
array: Array<InferredArrayType<T>>
) => boolean
rejector?: (value: InferredArrayType<T>, index: number, array: Array<InferredArrayType<T>>) => boolean,
): this;
}
export interface NotRequiredNullableArraySchema<T>
extends BasicArraySchema<T[] | null | undefined> {
export interface NotRequiredNullableArraySchema<T> extends BasicArraySchema<T[] | null | undefined> {
of<U>(type: Schema<U>): NotRequiredNullableArraySchema<U>;
nullable(isNullable?: true): NotRequiredNullableArraySchema<T>;
nullable(isNullable: false): NotRequiredArraySchema<T>;
@ -242,8 +210,7 @@ export interface NullableArraySchema<T> extends BasicArraySchema<T[] | null> {
notRequired(): NotRequiredNullableArraySchema<T>;
}
export interface NotRequiredArraySchema<T>
extends BasicArraySchema<T[] | undefined> {
export interface NotRequiredArraySchema<T> extends BasicArraySchema<T[] | undefined> {
of<U>(type: Schema<U>): NotRequiredArraySchema<U>;
nullable(isNullable?: true): NotRequiredNullableArraySchema<T>;
nullable(isNullable: false): NotRequiredArraySchema<T>;
@ -261,7 +228,7 @@ export interface ArraySchema<T> extends BasicArraySchema<T[]> {
}
export type ObjectSchemaDefinition<T extends object | null | undefined> = {
[field in keyof T]: Schema<T[field]> | Ref
[field in keyof T]: Schema<T[field]> | Ref;
};
/**
@ -270,7 +237,7 @@ export type ObjectSchemaDefinition<T extends object | null | undefined> = {
* [yup's `object.shape()` method](https://www.npmjs.com/package/yup#objectshapefields-object-nosortedges-arraystring-string-schema).
*/
export type Shape<T extends object | null | undefined, U extends object> = {
[P in keyof T]: P extends keyof U ? U[P] : T[P]
[P in keyof T]: P extends keyof U ? U[P] : T[P];
} &
U;
@ -279,17 +246,13 @@ export interface ObjectSchemaConstructor {
new (): ObjectSchema<{}>;
}
export interface ObjectSchema<T extends object | null | undefined = object>
extends Schema<T> {
export interface ObjectSchema<T extends object | null | undefined = object> extends Schema<T> {
shape<U extends object>(
fields: ObjectSchemaDefinition<U>,
noSortEdges?: Array<[string, string]>
noSortEdges?: Array<[string, string]>,
): ObjectSchema<Shape<T, U>>;
from(fromKey: string, toKey: string, alias?: boolean): ObjectSchema<T>;
noUnknown(
onlyKnownKeys?: boolean,
message?: TestOptionsMessage
): ObjectSchema<T>;
noUnknown(onlyKnownKeys?: boolean, message?: TestOptionsMessage): ObjectSchema<T>;
transformKeys(callback: (key: any) => any): void;
camelCase(): ObjectSchema<T>;
constantCase(): ObjectSchema<T>;
@ -302,11 +265,7 @@ export interface ObjectSchema<T extends object | null | undefined = object>
concat<U extends object>(schema: ObjectSchema<U>): ObjectSchema<T & U>;
}
export type TransformFunction<T> = (
this: T,
value: any,
originalValue: any
) => any;
export type TransformFunction<T> = (this: T, value: any, originalValue: any) => any;
export interface WhenOptionsBuilderFunction<T> {
(value: any, schema: T): T;
@ -315,13 +274,7 @@ export interface WhenOptionsBuilderFunction<T> {
(v1: any, v2: any, v3: any, v4: any, schema: T): T;
}
export type WhenOptionsBuilderObjectIs =
| ((...values: any[]) => boolean)
| boolean
| number
| null
| object
| string;
export type WhenOptionsBuilderObjectIs = ((...values: any[]) => boolean) | boolean | number | null | object | string;
export type WhenOptionsBuilderObject =
| {
@ -331,9 +284,7 @@ export type WhenOptionsBuilderObject =
}
| object;
export type WhenOptions<T> =
| WhenOptionsBuilderFunction<T>
| WhenOptionsBuilderObject;
export type WhenOptions<T> = WhenOptionsBuilderFunction<T> | WhenOptionsBuilderObject;
export interface TestContext {
path: string;
@ -341,10 +292,7 @@ export interface TestContext {
parent: any;
schema: Schema<any>;
resolve: (value: any) => any;
createError: (params?: {
path?: string;
message?: string;
}) => ValidationError;
createError: (params?: { path?: string; message?: string }) => ValidationError;
}
export interface ValidateOptions {
@ -377,7 +325,7 @@ export interface TestMessageParams {
label: string;
}
export interface TestOptions {
export interface TestOptions<P extends Record<string, any> = {}, R = any> {
/**
* Unique name identifying the test
*/
@ -386,20 +334,17 @@ export interface TestOptions {
/**
* Test function, determines schema validity
*/
test: (
this: TestContext,
value: any
) => boolean | ValidationError | Promise<boolean | ValidationError>;
test: (this: TestContext, value: any) => boolean | ValidationError | Promise<boolean | ValidationError>;
/**
* The validation error message
*/
message?: TestOptionsMessage;
message?: TestOptionsMessage<P, R>;
/**
* Values passed to message for interpolation
*/
params?: object;
params?: P;
/**
* Mark the test as exclusive, meaning only one of the same can be active at once
@ -441,17 +386,9 @@ export class ValidationError extends Error {
params?: object;
static isError(err: any): err is ValidationError;
static formatError(
message: string | ((params?: any) => string),
params?: any
): string | ((params?: any) => string);
static formatError(message: string | ((params?: any) => string), params?: any): string | ((params?: any) => string);
constructor(
errors: string | string[],
value: any,
path: string,
type?: any
);
constructor(errors: string | string[], value: any, path: string, type?: any);
}
// It is tempting to declare `Ref` very simply, but there are problems with these approaches:
@ -493,29 +430,35 @@ export interface FormatErrorParams {
export type LocaleValue = string | ((params: FormatErrorParams) => string);
type MessageFromParameters<P extends unknown[]> = {
[K in keyof P]: P[K] extends TestOptionsMessage<any> ? P[K] : never;
}[number];
type MappedLocaleSchema<S extends Schema<any>> = {
[key in keyof S]?: S[key] extends (...args: infer P) => any ? MessageFromParameters<Required<P>> : never;
};
export interface LocaleObject {
mixed?: { [key in keyof MixedSchema]?: string } & { notType?: LocaleValue };
string?: { [key in keyof StringSchema]?: string };
number?: { [key in keyof NumberSchema]?: string };
boolean?: { [key in keyof BooleanSchema]?: string };
bool?: { [key in keyof BooleanSchema]?: string };
date?: { [key in keyof DateSchema]?: string };
array?: { [key in keyof ArraySchema<any>]?: string };
object?: { [key in keyof ObjectSchema<any>]?: string };
mixed?: MappedLocaleSchema<MixedSchema> & { notType?: LocaleValue };
string?: MappedLocaleSchema<StringSchema>;
number?: MappedLocaleSchema<NumberSchema>;
boolean?: MappedLocaleSchema<BooleanSchema>;
bool?: MappedLocaleSchema<BooleanSchema>;
date?: MappedLocaleSchema<DateSchema>;
array?: MappedLocaleSchema<ArraySchema<any>>;
object?: MappedLocaleSchema<ObjectSchema<any>>;
}
export type InferType<T> = T extends Schema<infer P>
? InnerInferType<P>
: never;
export type InferType<T> = T extends Schema<infer P> ? InnerInferType<P> : never;
// Shut off automatic exporting after this statement
export {};
type KeyOfUndefined<T> = {
[P in keyof T]-?: undefined extends T[P] ? P : never
[P in keyof T]-?: undefined extends T[P] ? P : never;
}[keyof T];
type Id<T> = {[K in keyof T]: T[K]};
type Id<T> = { [K in keyof T]: T[K] };
type RequiredProps<T> = Pick<T, Exclude<keyof T, KeyOfUndefined<T>>>;
type NotRequiredProps<T> = Partial<Pick<T, KeyOfUndefined<T>>>;
type InnerInferType<T> = Id<NotRequiredProps<T> & RequiredProps<T>>;

View File

@ -143,6 +143,9 @@ mixed.typeError('type error');
mixed.typeError(() => 'type error');
mixed.oneOf(['hello', 'world'], 'message');
mixed.oneOf(['hello', 'world'], () => 'message');
mixed.oneOf(['hello', 'world'], ({ values }) => `one of ${values}`);
// $ExpectError
mixed.oneOf(['hello', 'world'], ({ random }) => `one of ${random}`);
mixed.notOneOf(['hello', 'world'], 'message');
mixed.notOneOf(['hello', 'world'], () => 'message');
mixed.when('isBig', {
@ -198,6 +201,12 @@ mixed.test('with-context', 'it uses function context', testContext);
mixed.test({
test: testContext,
});
mixed.test({
message: ({ passed }) => (passed ? 'You passed' : 'You failed'),
name: 'checkParams',
params: { passed: true },
test: value => !!value,
});
// mixed with concat
yup.object({ name: yup.string() }).concat(yup.object({ when: yup.date() })); // $ExpectType ObjectSchema<{ name: string; } & { when: Date; }>
@ -267,19 +276,31 @@ strSchema.required('req');
strSchema.required(() => 'req');
strSchema.length(5, 'message');
strSchema.length(5, () => 'message');
strSchema.length(5, ({ length }) => `must be ${length}`);
// $ExpectError
strSchema.length(5, ({ min }) => `must be ${min}`);
strSchema.min(5, 'message');
strSchema.min(5, () => 'message');
strSchema.min(5, ({ min }) => `more than ${min}`);
// $ExpectError
strSchema.min(5, ({ max }) => `more than ${max}`);
strSchema.max(5, 'message');
strSchema.max(5, () => 'message');
strSchema.max(5, ({ max }) => `less than ${max}`);
// $ExpectError
strSchema.max(5, ({ min }) => `less than ${min}`);
strSchema.matches(/(hi|bye)/);
strSchema.matches(/(hi|bye)/, 'invalid');
strSchema.matches(/(hi|bye)/, () => 'invalid');
strSchema.matches(/(hi|bye)/, ({ regex }) => `Does not match ${regex}`);
strSchema.email();
strSchema.email('invalid');
strSchema.email(() => 'invalid');
strSchema.email(({ regex }) => `Does not match ${regex}`);
strSchema.url();
strSchema.url('bad url');
strSchema.url(() => 'bad url');
strSchema.url(({ regex }) => `Does not match ${regex}`);
strSchema.ensure();
strSchema.trim();
strSchema.trim('trimmed');
@ -297,9 +318,15 @@ numSchema.isValid(10); // => true
numSchema.min(5);
numSchema.min(5, 'message');
numSchema.min(5, () => 'message');
numSchema.min(5, ({ min }) => `more than ${min}`);
// $ExpectError
numSchema.min(5, ({ max }) => `more than ${max}`);
numSchema.max(5);
numSchema.max(5, 'message');
numSchema.max(5, () => 'message');
numSchema.max(5, ({ max }) => `less than ${max}`);
// $ExpectError
numSchema.max(5, ({ min }) => `more than ${min}`);
numSchema.positive();
numSchema.positive('pos');
numSchema.positive(() => 'pos');
@ -358,8 +385,8 @@ arrSchema.compact((value, index, array) => value === array[index]);
const arrOfObjSchema = yup.array().of(
yup.object().shape({
field: yup.number()
})
field: yup.number(),
}),
);
arrOfObjSchema.compact((value, index, array) => {
return value.field > 10 && array[index].field > 10;
@ -462,8 +489,21 @@ const localeNotType3: LocaleObject = {
};
yup.setLocale({
mixed: {
required: options => options,
},
number: { max: 'Max message', min: 'Min message' },
string: { email: 'String message' },
string: {
email: 'String message',
length: ({ length }) => ({ key: 'stringLength', options: { length } }),
},
});
yup.setLocale({
// $ExpectError
string: {
nullable: 'message',
},
});
interface MyInterface {
@ -583,9 +623,7 @@ enum Gender {
const personSchema = yup.object({
firstName: yup.string(), // $ExpectType StringSchema<string>
gender: yup
.mixed<Gender>()
.oneOf([Gender.Male, Gender.Female]),
gender: yup.mixed<Gender>().oneOf([Gender.Male, Gender.Female]),
email: yup
.string()
.nullable()
@ -685,7 +723,10 @@ castPerson.children = undefined;
const loginSchema = yup.object({
password: yup.string(),
confirmPassword: yup.string().nullable().oneOf([yup.ref("password"), null]),
confirmPassword: yup
.string()
.nullable()
.oneOf([yup.ref('password'), null]),
});
function wrapper<T>(b: false, msx: MixedSchema<T>): MixedSchema<T>;