From a9e638dbdaa2c213e48e6c5f8d5c53685a520b8b Mon Sep 17 00:00:00 2001 From: Maurice de Beijer Date: Fri, 21 Jun 2019 09:58:01 +0200 Subject: [PATCH] Support Yup required/notRequired derived types (#36155) * Support undefined with notRequired() * ArraySchema instead of this * children can be undefined * Export InferType * Support undefined and null for arrays * Reduced types count and dupicated code for arrays * Added boolean overloads --- types/yup/index.d.ts | 123 +++++++--- types/yup/yup-tests.ts | 497 +++++++++++++++++++++-------------------- 2 files changed, 351 insertions(+), 269 deletions(-) diff --git a/types/yup/index.d.ts b/types/yup/index.d.ts index 04792a1fcd..0e4e5fb5f8 100644 --- a/types/yup/index.d.ts +++ b/types/yup/index.d.ts @@ -72,10 +72,6 @@ export interface Schema { withMutation(fn: (current: this) => void): void; default(value: any): this; default(): T; - nullable(isNullable?: true): Schema; - nullable(isNullable: false): Schema>; - required(message?: TestOptionsMessage): this; - notRequired(): this; typeError(message?: TestOptionsMessage): this; oneOf(arrayOfValues: any[], message?: TestOptionsMessage): this; notOneOf(arrayOfValues: any[], message?: TestOptionsMessage): this; @@ -100,15 +96,20 @@ export interface MixedSchemaConstructor { new (options?: { type?: string; [key: string]: any }): MixedSchema; } -// tslint:disable-next-line:no-empty-interface -export interface MixedSchema extends Schema {} +export interface MixedSchema extends Schema { + nullable(isNullable?: true): MixedSchema; + nullable(isNullable: false): MixedSchema>; + nullable(isNullable?: boolean): MixedSchema; + required(message?: TestOptionsMessage): MixedSchema>; + notRequired(): MixedSchema; +} export interface StringSchemaConstructor { (): StringSchema; new (): StringSchema; } -export interface StringSchema +export interface StringSchema extends Schema { length(limit: number | Ref, message?: TestOptionsMessage): StringSchema; min(limit: number | Ref, message?: TestOptionsMessage): StringSchema; @@ -127,6 +128,9 @@ export interface StringSchema uppercase(message?: TestOptionsMessage): StringSchema; nullable(isNullable?: true): StringSchema; nullable(isNullable: false): StringSchema>; + nullable(isNullable?: boolean): StringSchema; + required(message?: TestOptionsMessage): StringSchema>; + notRequired(): StringSchema; } export interface NumberSchemaConstructor { @@ -134,7 +138,7 @@ export interface NumberSchemaConstructor { new (): NumberSchema; } -export interface NumberSchema +export interface NumberSchema extends Schema { min(limit: number | Ref, message?: TestOptionsMessage): NumberSchema; max(limit: number | Ref, message?: TestOptionsMessage): NumberSchema; @@ -153,6 +157,9 @@ export interface NumberSchema round(type: "floor" | "ceil" | "trunc" | "round"): NumberSchema; nullable(isNullable?: true): NumberSchema; nullable(isNullable: false): NumberSchema>; + nullable(isNullable?: boolean): NumberSchema; + required(message?: TestOptionsMessage): NumberSchema>; + notRequired(): NumberSchema; } export interface BooleanSchemaConstructor { @@ -160,11 +167,15 @@ export interface BooleanSchemaConstructor { new (): BooleanSchema; } -// tslint:disable-next-line:no-empty-interface -export interface BooleanSchema +export interface BooleanSchema extends Schema { nullable(isNullable?: true): BooleanSchema; nullable(isNullable: false): BooleanSchema>; + nullable(isNullable?: boolean): BooleanSchema; + required( + message?: TestOptionsMessage + ): BooleanSchema>; + notRequired(): BooleanSchema; } export interface DateSchemaConstructor { @@ -172,7 +183,8 @@ export interface DateSchemaConstructor { new (): DateSchema; } -export interface DateSchema extends Schema { +export interface DateSchema + extends Schema { min( limit: Date | string | Ref, message?: TestOptionsMessage @@ -183,6 +195,9 @@ export interface DateSchema extends Schema { ): DateSchema; nullable(isNullable?: true): DateSchema; nullable(isNullable: false): DateSchema>; + nullable(isNullable?: boolean): DateSchema; + required(message?: TestOptionsMessage): DateSchema>; + notRequired(): DateSchema; } export interface ArraySchemaConstructor { @@ -190,17 +205,52 @@ export interface ArraySchemaConstructor { new (): ArraySchema<{}>; } -export interface ArraySchema extends Schema { - of(type: Schema): ArraySchema; - min(limit: number | Ref, message?: TestOptionsMessage): ArraySchema; - max(limit: number | Ref, message?: TestOptionsMessage): ArraySchema; - ensure(): ArraySchema; - compact( - rejector?: (value: T, index: number, array: T[]) => boolean - ): ArraySchema; +interface BasicArraySchema + extends Schema { + min(limit: number | Ref, message?: TestOptionsMessage): this; + max(limit: number | Ref, message?: TestOptionsMessage): this; + ensure(): this; + compact(rejector?: (value: T, index: number, array: T[]) => boolean): this; } -export type ObjectSchemaDefinition = { +export interface NotRequiredNullableArraySchema + extends BasicArraySchema { + of(type: Schema): NotRequiredNullableArraySchema; + nullable(isNullable?: true): NotRequiredNullableArraySchema; + nullable(isNullable: false): NotRequiredArraySchema; + nullable(isNullable?: boolean): ArraySchema; + required(message?: TestOptionsMessage): NullableArraySchema; + notRequired(): NotRequiredNullableArraySchema; +} + +export interface NullableArraySchema extends BasicArraySchema { + of(type: Schema): NullableArraySchema; + nullable(isNullable?: true): NullableArraySchema; + nullable(isNullable: false): ArraySchema; + nullable(isNullable?: boolean): ArraySchema; + required(message?: TestOptionsMessage): NullableArraySchema; + notRequired(): NotRequiredNullableArraySchema; +} + +export interface NotRequiredArraySchema + extends BasicArraySchema { + of(type: Schema): NotRequiredArraySchema; + nullable(isNullable?: true): NotRequiredNullableArraySchema; + nullable(isNullable: false): NotRequiredArraySchema; + nullable(isNullable: boolean): ArraySchema; + required(message?: TestOptionsMessage): ArraySchema; + notRequired(): NotRequiredArraySchema; +} + +export interface ArraySchema extends BasicArraySchema { + of(type: Schema): ArraySchema; + nullable(isNullable?: true): NullableArraySchema; + nullable(isNullable: false | boolean): ArraySchema; + required(message?: TestOptionsMessage): ArraySchema; + notRequired(): NotRequiredArraySchema; +} + +export type ObjectSchemaDefinition = { [field in keyof T]: Schema | Ref }; @@ -209,7 +259,7 @@ export type ObjectSchemaDefinition = { * This is conducive to the functionality of * [yup's `object.shape()` method](https://www.npmjs.com/package/yup#objectshapefields-object-nosortedges-arraystring-string-schema). */ -export type Shape = { +export type Shape = { [P in keyof T]: P extends keyof U ? U[P] : T[P] } & U; @@ -219,7 +269,8 @@ export interface ObjectSchemaConstructor { new (): ObjectSchema<{}>; } -export interface ObjectSchema extends Schema { +export interface ObjectSchema + extends Schema { shape( fields: ObjectSchemaDefinition, noSortEdges?: Array<[string, string]> @@ -232,6 +283,11 @@ export interface ObjectSchema extends Schema { transformKeys(callback: (key: any) => any): void; camelCase(): ObjectSchema; constantCase(): ObjectSchema; + nullable(isNullable?: true): ObjectSchema; + nullable(isNullable: false): ObjectSchema>; + nullable(isNullable?: boolean): ObjectSchema; + required(message?: TestOptionsMessage): ObjectSchema>; + notRequired(): ObjectSchema; } export type TransformFunction = ( @@ -257,13 +313,13 @@ export type WhenOptionsBuilderObjectIs = export type WhenOptionsBuilderObject = | { - is: WhenOptionsBuilderObjectIs; - then: any; - otherwise: any + is: WhenOptionsBuilderObjectIs; + then: any; + otherwise: any; } | object; -export type WhenOptions = + export type WhenOptions = | WhenOptionsBuilderFunction | WhenOptionsBuilderObject; @@ -435,3 +491,18 @@ export interface LocaleObject { array?: { [key in keyof ArraySchema]?: string }; object?: { [key in keyof ObjectSchema]?: string }; } + +export type InferType = T extends Schema + ? InnerInferType

+ : never; + +// Shut off automatic exporting after this statement +export {}; + +type KeyOfUndefined = { + [P in keyof T]-?: undefined extends T[P] ? P : never +}[keyof T]; + +type RequiredProps = Pick>>; +type NotRequiredProps = Partial>>; +type InnerInferType = NotRequiredProps & RequiredProps; diff --git a/types/yup/yup-tests.ts b/types/yup/yup-tests.ts index ccf926793c..8de2e8dbbe 100644 --- a/types/yup/yup-tests.ts +++ b/types/yup/yup-tests.ts @@ -1,4 +1,4 @@ -import * as yup from "yup"; +import * as yup from 'yup'; // tslint:disable-next-line:no-duplicate-imports import { @@ -14,48 +14,40 @@ import { ValidateOptions, NumberSchema, TestContext, - LocaleObject -} from "yup"; + LocaleObject, +} from 'yup'; // reach function const schema1 = yup.object().shape({ nested: yup.object().shape({ - arr: yup.array().of(yup.object().shape({ num: yup.number().max(4) })) - }) + arr: yup.array().of(yup.object().shape({ num: yup.number().max(4) })), + }), }); -reach(schema1, "nested.arr.num"); -reach(schema1, "nested.arr[].num"); +reach(schema1, 'nested.arr.num'); +reach(schema1, 'nested.arr[].num'); // isSchema function const isSchemaResult1: boolean = isSchema(schema1); const isSchemaResult2: boolean = isSchema({}); // addMethod function -yup.addMethod(yup.number, "minimum", function( - this, - minValue: number, - message: string -) { +yup.addMethod(yup.number, 'minimum', function(this, minValue: number, message: string) { return this.min(minValue, message); }); -yup.addMethod(yup.date, "newMethod", function( - this: yup.DateSchema, - date: Date, - message?: string -) { +yup.addMethod(yup.date, 'newMethod', function(this: yup.DateSchema, date: Date, message?: string) { return this.max(date, message); }); // ref function const schema2 = yup.object().shape({ - baz: yup.ref("foo.bar"), + baz: yup.ref('foo.bar'), foo: yup.object().shape({ - bar: yup.string() + bar: yup.string(), }), - x: yup.ref("$x") + x: yup.ref('$x'), }); -let ref: yup.Ref = yup.ref("foo.bar"); +let ref: yup.Ref = yup.ref('foo.bar'); // $ExpectError ref = {}; @@ -64,18 +56,18 @@ ref = {}; ref = { __isYupRef: true }; // cast function -schema2.cast({ foo: { bar: "boom" } }, { context: { x: 5 } }); +schema2.cast({ foo: { bar: 'boom' } }, { context: { x: 5 } }); // lazy function const node: ObjectSchema = yup.object().shape({ id: yup.number(), - child: yup.lazy(() => node.default(undefined)) + child: yup.lazy(() => node.default(undefined)), }); const renderable = yup.lazy(value => { switch (typeof value) { - case "number": + case 'number': return yup.number(); - case "string": + case 'string': return yup.string(); default: return yup.mixed(); @@ -85,63 +77,56 @@ const renderables = yup.array().of(renderable); // ValidationError static methods // $ExpectType boolean -ValidationError.isError(new ValidationError("error", "value", "path")); +ValidationError.isError(new ValidationError('error', 'value', 'path')); // $ExpectType string | ((params?: any) => string) -ValidationError.formatError("error", { path: "path" }); -ValidationError.formatError("error"); -ValidationError.formatError(() => "error"); -ValidationError.formatError(() => "error", { path: "path" }); +ValidationError.formatError('error', { path: 'path' }); +ValidationError.formatError('error'); +ValidationError.formatError(() => 'error'); +ValidationError.formatError(() => 'error', { path: 'path' }); // ValidationError -let error: ValidationError = new yup.ValidationError("error", "value", "path"); -error = new yup.ValidationError(["error", "error2"], true, "path"); -error = new yup.ValidationError(["error", "error2"], 5, "path"); -error = new yup.ValidationError(["error", "error2"], { name: "value" }, "path"); -error = new yup.ValidationError( - ["error", "error2"], - { name: "value" }, - "path", - "type" -); +let error: ValidationError = new yup.ValidationError('error', 'value', 'path'); +error = new yup.ValidationError(['error', 'error2'], true, 'path'); +error = new yup.ValidationError(['error', 'error2'], 5, 'path'); +error = new yup.ValidationError(['error', 'error2'], { name: 'value' }, 'path'); +error = new yup.ValidationError(['error', 'error2'], { name: 'value' }, 'path', 'type'); error = { - name: "ValidationError", - message: "error", - path: "path", - errors: ["error"], - inner: [new yup.ValidationError("error", true, "path")], - type: "date", - value: { start: "2017-11-10" } + name: 'ValidationError', + message: 'error', + path: 'path', + errors: ['error'], + inner: [new yup.ValidationError('error', true, 'path')], + type: 'date', + value: { start: '2017-11-10' }, }; -error.value = "value"; +error.value = 'value'; error.value = true; error.value = 5; -error.value = { name: "value" }; +error.value = { name: 'value' }; error.type = {}; error.type = []; -error.errors = ["error"]; +error.errors = ['error']; // mixed let mixed: MixedSchema = yup.mixed(); mixed.clone(); -mixed.label("label"); -mixed.meta({ meta: "value" }); +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.validateSync({ hello: "world" }, { strict: true }); -mixed.validateAt("path", {}, { strict: true, context: {} }); -mixed - .validateAt("path", {}, { strict: true, context: {} }) - .then(value => value); -mixed.validateSyncAt("path", {}, { strict: true, context: {} }); +mixed.validate({ hello: 'world' }, { strict: true }).then(value => value); +mixed.validateSync({ hello: 'world' }, { strict: true }); +mixed.validateAt('path', {}, { strict: true, context: {} }); +mixed.validateAt('path', {}, { strict: true, context: {} }).then(value => value); +mixed.validateSyncAt('path', {}, { strict: true, context: {} }); mixed.isValid(undefined, (valid: true) => true); -mixed.isValid({ hello: "world" }).then(valid => valid); +mixed.isValid({ hello: 'world' }).then(valid => valid); mixed.cast({}); -mixed.isType("hello"); +mixed.isType('hello'); mixed.strict(true); mixed.strip(true); mixed.withMutation(schema => {}); @@ -151,53 +136,43 @@ mixed.default(); mixed.nullable(true); mixed.nullable(); mixed.required(); -mixed.required("Foo"); -mixed.required(() => "Foo"); -mixed.notRequired(); // $ExpectType MixedSchema -mixed.typeError("type error"); -mixed.typeError(() => "type error"); -mixed.oneOf(["hello", "world"], "message"); -mixed.oneOf(["hello", "world"], () => "message"); -mixed.notOneOf(["hello", "world"], "message"); -mixed.notOneOf(["hello", "world"], () => "message"); -mixed.when("isBig", { +mixed.required('Foo'); +mixed.required(() => 'Foo'); +mixed.notRequired(); // $ExpectType MixedSchema +mixed.typeError('type error'); +mixed.typeError(() => 'type error'); +mixed.oneOf(['hello', 'world'], 'message'); +mixed.oneOf(['hello', 'world'], () => 'message'); +mixed.notOneOf(['hello', 'world'], 'message'); +mixed.notOneOf(['hello', 'world'], () => 'message'); +mixed.when('isBig', { is: value => true, then: yup.number().min(5), - otherwise: yup.number().min(0) + otherwise: yup.number().min(0), }); -mixed.when(["isBig", "isSpecial"], { +mixed.when(['isBig', 'isSpecial'], { is: (isBig, isSpecial) => isBig && isSpecial, then: yup.number().min(5), otherwise: yup.number().min(0), }); mixed - .when("isBig", { + .when('isBig', { is: true, then: yup.number().min(5), - otherwise: yup.number().min(0) + otherwise: yup.number().min(0), }) - .when("$other", (value: any, schema: MixedSchema) => - value === 4 ? schema.required() : schema - ); + .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( - "is-jimmy", - ({ path, value }) => `${path} has an error, it is ${value}`, - value => value === "jimmy" -); +mixed.test('is-jimmy', '${path} is not Jimmy', value => value === 'jimmy'); +mixed.test('is-jimmy', ({ path, value }) => `${path} has an error, it is ${value}`, value => value === 'jimmy'); mixed.test({ - name: "lessThan5", + 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 + message: '${path} must be less than 5 characters', + test: value => value == null || value.length <= 5, }); -mixed.test( - "with-promise", - "It contains invalid value", - value => new Promise(resolve => true) -); +mixed.test('with-promise', 'It contains invalid value', value => new Promise(resolve => true)); const testContext = function(this: TestContext) { // $ExpectType string this.path; @@ -210,50 +185,38 @@ const testContext = function(this: TestContext) { // $ExpectType (value: any) => any this.resolve; // $ExpectType ValidationError - this.createError({ path: "1", message: "1" }); + this.createError({ path: '1', message: '1' }); // $ExpectType ValidationError - this.createError({ message: "1" }); + this.createError({ message: '1' }); // $ExpectType ValidationError - this.createError({ path: "1" }); + this.createError({ path: '1' }); // $ExpectType ValidationError this.createError(); return true; }; -mixed.test("with-context", "it uses function context", testContext); +mixed.test('with-context', 'it uses function context', testContext); mixed.test({ - test: testContext + test: testContext, }); // Async ValidationError -const asyncValidationErrorTest = function( - this: TestContext -): Promise { - return new Promise(resolve => - resolve(this.createError({ path: "testPath", message: "testMessage" })) - ); +const asyncValidationErrorTest = function(this: TestContext): Promise { + return new Promise(resolve => resolve(this.createError({ path: 'testPath', message: 'testMessage' }))); }; -mixed.test( - "async-validation-error", - "Returns async ValidationError", - asyncValidationErrorTest -); +mixed.test('async-validation-error', 'Returns async ValidationError', asyncValidationErrorTest); mixed.test({ - test: asyncValidationErrorTest + test: asyncValidationErrorTest, }); // Sync ValidationError const syncValidationErrorTest = function(this: TestContext): ValidationError { - return this.createError({ path: "testPath", message: "testMessage" }); + return this.createError({ path: 'testPath', message: 'testMessage' }); }; -mixed.test( - "sync-validation-error", - "Returns sync ValidationError", - syncValidationErrorTest -); +mixed.test('sync-validation-error', 'Returns sync ValidationError', syncValidationErrorTest); mixed.test({ - test: syncValidationErrorTest + test: syncValidationErrorTest, }); yup.string().transform(function(this, value: any, originalvalue: any) { @@ -266,7 +229,7 @@ mixed = new ExtendsMixed(); class ExtendsMixed2 extends yup.mixed { constructor() { - super({ type: "CustomType" }); + super({ type: 'CustomType' }); } } mixed = new ExtendsMixed2(); @@ -277,79 +240,79 @@ mixed = new ExtendsMixed2(); class DateSchema extends yup.date { isWednesday(message?: string): DateSchema { return this.clone().test({ - name: "Wednesday", + name: 'Wednesday', // tslint:disable-next-line:no-invalid-template-strings - message: message || "${path} must be Wednesday", - test: value => true /* Check that day is Wednesday */ + message: message || '${path} must be Wednesday', + test: value => true /* Check that day is Wednesday */, }); } } yup.object() .shape({ - startDate: new DateSchema().isWednesday().required() + startDate: new DateSchema().isWednesday().required(), }) .isValidSync({ - startDate: "2017-11-29" + startDate: '2017-11-29', }); // String schema const strSchema = yup.string(); // $ExpectType StringSchema -strSchema.isValid("hello"); // => true +strSchema.isValid('hello'); // => true strSchema.required(); -strSchema.required("req"); -strSchema.required(() => "req"); -strSchema.length(5, "message"); -strSchema.length(5, () => "message"); -strSchema.min(5, "message"); -strSchema.min(5, () => "message"); -strSchema.max(5, "message"); -strSchema.max(5, () => "message"); +strSchema.required('req'); +strSchema.required(() => 'req'); +strSchema.length(5, 'message'); +strSchema.length(5, () => 'message'); +strSchema.min(5, 'message'); +strSchema.min(5, () => 'message'); +strSchema.max(5, 'message'); +strSchema.max(5, () => 'message'); strSchema.matches(/(hi|bye)/); -strSchema.matches(/(hi|bye)/, "invalid"); -strSchema.matches(/(hi|bye)/, () => "invalid"); +strSchema.matches(/(hi|bye)/, 'invalid'); +strSchema.matches(/(hi|bye)/, () => 'invalid'); strSchema.email(); -strSchema.email("invalid"); -strSchema.email(() => "invalid"); +strSchema.email('invalid'); +strSchema.email(() => 'invalid'); strSchema.url(); -strSchema.url("bad url"); -strSchema.url(() => "bad url"); +strSchema.url('bad url'); +strSchema.url(() => 'bad url'); strSchema.ensure(); strSchema.trim(); -strSchema.trim("trimmed"); -strSchema.trim(() => "trimmed"); +strSchema.trim('trimmed'); +strSchema.trim(() => 'trimmed'); strSchema.lowercase(); -strSchema.lowercase("lower"); -strSchema.lowercase(() => "lower"); +strSchema.lowercase('lower'); +strSchema.lowercase(() => 'lower'); strSchema.uppercase(); -strSchema.uppercase("upper"); -strSchema.uppercase(() => "upper"); +strSchema.uppercase('upper'); +strSchema.uppercase(() => 'upper'); // Number schema const numSchema = yup.number(); // $ExpectType NumberSchema numSchema.isValid(10); // => true numSchema.min(5); -numSchema.min(5, "message"); -numSchema.min(5, () => "message"); +numSchema.min(5, 'message'); +numSchema.min(5, () => 'message'); numSchema.max(5); -numSchema.max(5, "message"); -numSchema.max(5, () => "message"); +numSchema.max(5, 'message'); +numSchema.max(5, () => 'message'); numSchema.positive(); -numSchema.positive("pos"); -numSchema.positive(() => "pos"); +numSchema.positive('pos'); +numSchema.positive(() => 'pos'); numSchema.negative(); -numSchema.negative("neg"); -numSchema.negative(() => "neg"); +numSchema.negative('neg'); +numSchema.negative(() => 'neg'); numSchema.lessThan(5); -numSchema.lessThan(5, "lt"); -numSchema.lessThan(5, () => "lt"); +numSchema.lessThan(5, 'lt'); +numSchema.lessThan(5, () => 'lt'); numSchema.moreThan(5); -numSchema.moreThan(5, "mt"); -numSchema.moreThan(5, () => "mt"); +numSchema.moreThan(5, 'mt'); +numSchema.moreThan(5, () => 'mt'); numSchema.integer(); -numSchema.integer("int"); -numSchema.integer(() => "int"); +numSchema.integer('int'); +numSchema.integer(() => 'int'); numSchema.truncate(); -numSchema.round("floor"); +numSchema.round('floor'); numSchema .validate(5, { strict: true }) .then(value => value) @@ -363,30 +326,30 @@ boolSchema.isValid(true); // => true 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.min("2017-11-12", () => "message"); +dateSchema.min('2017-11-12'); +dateSchema.min(new Date(), 'message'); +dateSchema.min('2017-11-12', '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"); -dateSchema.max("2017-11-12", () => "message"); +dateSchema.max('2017-11-12'); +dateSchema.max(new Date(), 'message'); +dateSchema.max('2017-11-12', '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.required("req"); -arrSchema.required(() => "req"); +arrSchema.required('req'); +arrSchema.required(() => 'req'); arrSchema.ensure(); arrSchema.max(5); -arrSchema.max(5, "max"); -arrSchema.max(5, () => "max"); +arrSchema.max(5, 'max'); +arrSchema.max(5, () => 'max'); arrSchema.min(5); -arrSchema.min(5, "min"); -arrSchema.min(5, () => "min"); +arrSchema.min(5, 'min'); +arrSchema.min(5, () => 'min'); arrSchema.compact((value, index, array) => value === array[index]); const arr = yup.array(); @@ -404,48 +367,48 @@ const objSchema = yup.object().shape({ .positive() .integer(), email: yup.string().email(), - website: yup.string().url() + website: yup.string().url(), }); yup.object().shape({ - num: yup.number() + num: yup.number(), }); // or yup.object({ - num: yup.number() + num: yup.number(), }); -objSchema.from("prop", "myProp"); -objSchema.from("prop", "myProp", true); +objSchema.from('prop', 'myProp'); +objSchema.from('prop', 'myProp', true); objSchema.noUnknown(); objSchema.noUnknown(true); -objSchema.noUnknown(true, "message"); -objSchema.noUnknown(true, () => "message"); +objSchema.noUnknown(true, 'message'); +objSchema.noUnknown(true, () => 'message'); objSchema.transformKeys(key => key.toUpperCase()); objSchema.camelCase(); objSchema.constantCase(); const description: SchemaDescription = { - type: "type", - label: "label", - meta: { key: "value" }, - tests: [{ name: "test1", params: {} }, { name: "test2", params: {} }], - fields: { key: "value" } + type: 'type', + label: 'label', + meta: { key: 'value' }, + tests: [{ name: 'test1', params: {} }, { name: 'test2', params: {} }], + fields: { key: 'value' }, }; const testOptions: TestOptions = { - name: "name", + name: 'name', test: value => true, - message: "validation error message", - params: { param1: "value" }, - exclusive: true + message: 'validation error message', + params: { param1: 'value' }, + exclusive: true, }; const testOptionsWithPromise: TestOptions = { - name: "name", + name: 'name', test: value => new Promise(resolve => true), - message: "validation error message", - params: { param1: "value" }, - exclusive: true + message: 'validation error message', + params: { param1: 'value' }, + exclusive: true, }; const validateOptions: ValidateOptions = { @@ -454,45 +417,40 @@ const validateOptions: ValidateOptions = { stripUnknown: true, recursive: true, context: { - key: "value" - } + key: 'value', + }, }; const localeNotType1: LocaleObject = { mixed: { - required: "message", - notType: "message" - } + required: 'message', + notType: 'message', + }, }; const localeNotType2: LocaleObject = { mixed: { - required: "message", - notType: () => "message" - } + required: 'message', + notType: () => 'message', + }, }; const localeNotType3: LocaleObject = { mixed: { - required: "message", + required: 'message', notType: _ref => { - const isCast: boolean = - _ref.originalValue != null && _ref.originalValue !== _ref.value; - const finalPartOfTheMessage = isCast - ? ` (cast from the value '${_ref.originalValue}').` - : "."; + const isCast: boolean = _ref.originalValue != null && _ref.originalValue !== _ref.value; + const finalPartOfTheMessage = isCast ? ` (cast from the value '${_ref.originalValue}').` : '.'; return ( `${_ref.path} must be a '${_ref.type}'` + - ` but the final value was: '${ - _ref.value - }'${finalPartOfTheMessage}` + ` but the final value was: '${_ref.value}'${finalPartOfTheMessage}` ); - } - } + }, + }, }; yup.setLocale({ - number: { max: "Max message", min: "Min message" }, - string: { email: "String message" } + number: { max: 'Max message', min: 'Min message' }, + string: { email: 'String message' }, }); interface MyInterface { @@ -512,19 +470,19 @@ const typedSchema = yup.object({ numberField: yup.number().required(), // $ExpectType NumberSchema subFields: yup .object({ - testField: yup.string().required() + testField: yup.string().required(), }) .required(), - arrayField: yup.array(yup.string()).required() // $ExpectType ArraySchema + arrayField: yup.array(yup.string()).required(), // $ExpectType ArraySchema }); const testObject: MyInterface = { - stringField: "test1", + stringField: 'test1', numberField: 123, subFields: { - testField: "test2" + testField: 'test2', }, - arrayField: ["hi"] + arrayField: ['hi'], }; typedSchema.validateSync(testObject); // $ExpectType MyInterface @@ -547,19 +505,19 @@ interface ExpectedABC { } const expectedAbc: ExpectedABC = { - a: "qwerty", - b: "asdfg", - c: 123 + a: 'qwerty', + b: 'asdfg', + c: 123, }; const actualAbc: yup.Shape = expectedAbc; const definitionAB: yup.ObjectSchemaDefinition = { a: yup.string(), - b: yup.number() + b: yup.number(), }; const definitionBC: yup.ObjectSchemaDefinition = { b: yup.string(), - c: yup.number() + c: yup.number(), }; const combinedSchema = yup.object(definitionAB).shape(definitionBC); // $ExpectType ObjectSchema> @@ -568,10 +526,10 @@ yup.object({ stringField: yup.string().required(), subFields: yup .object({ - testField: yup.string().required() + testField: yup.string().required(), }) .required(), - arrayField: yup.array(yup.string()).required() + arrayField: yup.array(yup.string()).required(), }); // $ExpectError @@ -580,17 +538,17 @@ yup.object({ numberField: yup.number().required(), subFields: yup .object({ - testField: yup.string().required() + testField: yup.string().required(), }) .required(), - arrayField: yup.array(yup.string()).required() + arrayField: yup.array(yup.string()).required(), }); // $ExpectError yup.object({ stringField: yup.string().required(), numberField: yup.number().required(), - arrayField: yup.array(yup.string()).required() + arrayField: yup.array(yup.string()).required(), }); // $ExpectError @@ -599,10 +557,10 @@ yup.object({ numberField: yup.number().required(), subFields: yup .object({ - testField: yup.number().required() + testField: yup.number().required(), }) .required(), - arrayField: yup.array(yup.string()).required() + arrayField: yup.array(yup.string()).required(), }); const personSchema = yup.object({ @@ -610,48 +568,101 @@ const personSchema = yup.object({ email: yup .string() .nullable() + .notRequired() .email(), birthDate: yup .date() .nullable() + .notRequired() .min(new Date(1900, 0, 1)), canBeNull: yup.string().nullable(true), // $ExpectType StringSchema - isAlive: yup.boolean().nullable(), + isAlive: yup + .boolean() + .nullable() + .notRequired(), mustBeAString: yup .string() .nullable(true) - .nullable(false), - children: yup.array(yup.string()).nullable() + .nullable(false) + .notRequired() + .required(), + children: yup + .array(yup.string()) + .nullable() + .notRequired() + .min(1), }); -type Person = ReturnType; +type Person = yup.InferType; // Equivalent to: // type Person = { // firstName: string; -// email: string | null; -// birthDate: Date | null; +// email?: string | null | undefined; +// birthDate?: Date | null | undefined; // canBeNull: string | null; -// isAlive: boolean | null; +// isAlive: boolean | null | undefined; // mustBeAString: string; -// children: string[] | null; +// children?: string[] | null | undefined; // } +const minimalPerson: Person = { + firstName: '', + canBeNull: null, + mustBeAString: '', +}; + const person: Person = { - firstName: "", + firstName: '', email: null, birthDate: null, canBeNull: null, isAlive: null, - mustBeAString: "", - children: null + mustBeAString: '', + children: null, }; -person.email = "some@email.com"; +person.email = 'some@email.com'; +person.email = undefined; person.birthDate = new Date(); person.isAlive = true; -person.children = ["1", "2", "3"]; +person.isAlive = undefined; +person.children = ['1', '2', '3']; +person.children = undefined; // $ExpectError person.firstName = null; // $ExpectError +person.firstName = undefined; +// $ExpectError person.mustBeAString = null; +// $ExpectError +person.mustBeAString = undefined; + +const castPerson = personSchema.cast({}); +castPerson.firstName = ''; +// $ExpectError +castPerson.firstName = null; +// $ExpectError +castPerson.firstName = undefined; +castPerson.email = 'some@email.com'; +castPerson.email = null; +castPerson.email = undefined; +castPerson.birthDate = new Date(); +castPerson.birthDate = null; +castPerson.birthDate = undefined; +castPerson.isAlive = true; +castPerson.isAlive = null; +castPerson.isAlive = undefined; +castPerson.children = ['1', '2', '3']; +castPerson.children = null; +castPerson.children = undefined; + +function wrapper(b: false, msx: MixedSchema): MixedSchema; +function wrapper(b: true, msx: MixedSchema): MixedSchema; + +function wrapper(b: boolean, msx: MixedSchema): MixedSchema { + return msx.nullable(b); +} + +const resultingSchema1 = wrapper(false, yup.mixed().oneOf(['1', 2])); // $ExpectType MixedSchema +const resultingSchema2 = wrapper(true, yup.mixed().oneOf(['1', 2])); // $ExpectType MixedSchema