-
Notifications
You must be signed in to change notification settings - Fork 933
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewritten in TS, and with some (hopefully) improvements in accuracy over the DT types. Along with the rewrite come a few organizational and behavioral breaking changes. See the typescript docs for more info about how the types here differ from DT. BREAKING CHANGE: `concat` doesn't check for "unset" nullable or presence when merging meaning the nullability and presence will always be the same as the schema passed to `concat()`. They can be overridden if needed after concatenation BREAKING CHANGE: schema factory functions are no longer constructors. The classes are now also exported for extension or whatever else. e.g. `import { StringSchema, string } from 'yup'`
- Loading branch information
Showing
64 changed files
with
6,553 additions
and
2,532 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.eslintrc | ||
.eslintrc.js |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# Extending Schema | ||
|
||
For simple cases where you want to reuse common schema configurations, creating | ||
and passing around instances works great and is automatically typed correctly | ||
|
||
```js | ||
import * as yup from 'yup'; | ||
|
||
const requiredString = yup.string().required().default(''); | ||
|
||
const momentDate = (parseFormats = ['MMM dd, yyy']) => | ||
yup.date().transform(function (value, originalValue) { | ||
if (this.isType(value)) return value; | ||
|
||
// the default coercion transform failed so let's try it with Moment instead | ||
value = Moment(originalValue, parseFormats); | ||
return value.isValid() ? value.toDate() : yup.date.INVALID_DATE; | ||
}); | ||
|
||
export { momentDate, requiredString }; | ||
``` | ||
|
||
Schema are immutable so each can be configured further without changing the original. | ||
|
||
## Extending Schema with new methods | ||
|
||
`yup` provides a `addMethod()` utility for extending built-in schema: | ||
|
||
```js | ||
function parseDateFromFormats(formats, parseStrict) { | ||
return this.transform(function (value, originalValue) { | ||
if (this.isType(value)) return value; | ||
|
||
value = Moment(originalValue, formats, parseStrict); | ||
|
||
return value.isValid() ? value.toDate() : yup.date.INVALID_DATE; | ||
}); | ||
} | ||
|
||
yup.addMethod(yup.date, 'format', parseDateFromFormats); | ||
``` | ||
|
||
Note that `addMethod` isn't really magic, it mutates the prototype of the passed in schema. | ||
|
||
> Note: if you are using TypeScript you also need to adjust the class or interface | ||
> see the [typescript](./typescript) docs for details. | ||
## Creating new Schema types | ||
|
||
If you're use case calls for creating an entirely new type. inheriting from | ||
and existing schema class may be best: Generally you should not inheriting from | ||
the abstract `Schema` unless you know what you are doing. The other types are fair game though. | ||
|
||
You should keep in mind some basic guidelines when extending schemas: | ||
|
||
- never mutate an existing schema, always `clone()` and then mutate the new one before returning it. | ||
Built-in methods like `test` and `transform` take care of this for you, so you can safely use them (see below) without worrying | ||
|
||
- transforms should never mutate the `value` passed in, and should return an invalid object when one exists | ||
(`NaN`, `InvalidDate`, etc) instead of `null` for bad values. | ||
|
||
- by the time validations run the `value` is guaranteed to be the correct type, however it still may | ||
be `null` or `undefined` | ||
|
||
```js | ||
import { DateSchema } from 'yup'; | ||
|
||
class MomentDateSchema extends DateSchema { | ||
static create() { | ||
return MomentDateSchema(); | ||
} | ||
|
||
constructor() { | ||
super(); | ||
this._validFormats = []; | ||
|
||
this.withMutation(() => { | ||
this.transform(function (value, originalvalue) { | ||
if (this.isType(value)) | ||
// we have a valid value | ||
return value; | ||
return Moment(originalValue, this._validFormats, true); | ||
}); | ||
}); | ||
} | ||
|
||
_typeCheck(value) { | ||
return ( | ||
super._typeCheck(value) || (moment.isMoment(value) && value.isValid()) | ||
); | ||
} | ||
|
||
format(formats) { | ||
if (!formats) throw new Error('must enter a valid format'); | ||
let next = this.clone(); | ||
next._validFormats = {}.concat(formats); | ||
} | ||
} | ||
|
||
let schema = new MomentDateSchema(); | ||
|
||
schema.format('YYYY-MM-DD').cast('It is 2012-05-25'); // => Fri May 25 2012 00:00:00 GMT-0400 (Eastern Daylight Time) | ||
``` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
## TypeScript Support | ||
|
||
`yup` comes with robust typescript support! However, because of how dynamic `yup` is | ||
not everything can be statically typed safely, but for most cases it's "Good Enough". | ||
|
||
Not that `yup` schema actually produce _two_ different types: the result of casting an input, and the value after validation. | ||
Why are these types different? Because a schema can produce a value via casting that | ||
would not pass validation! | ||
|
||
```js | ||
const schema = string().nullable().required(); | ||
|
||
schema.cast(null); // -> null | ||
schema.validateSync(null); // ValidationError this is required! | ||
``` | ||
|
||
By itself this seems weird, but has it uses when handling user input. To get a | ||
TypeScript type that matches all possible `cast()` values, use `yup.TypeOf<typeof schema>`. | ||
To produce a type that matches a valid object for the schema use `yup.Asserts<typeof schema>>` | ||
|
||
```ts | ||
import * as yup from 'yup'; | ||
|
||
const personSchema = yup.object({ | ||
firstName: yup | ||
.string() | ||
// Here we use `defined` instead of `required` to more closely align with | ||
// TypeScript. Both will have the same effect on the resulting type by | ||
// excluding `undefined`, but `required` will also disallow empty strings. | ||
.defined(), | ||
// defaults also affect the possible output type! | ||
// schema with default values won't produce `undefined` values. Remember object schema | ||
// have a default value built in. | ||
nickName: yup.string().default('').nullable(), | ||
gender: yup | ||
.mixed() | ||
// Note `as const`: this types the array as `["male", "female", "other"]` | ||
// instead of `string[]`. | ||
.oneOf(['male', 'female', 'other'] as const) | ||
.defined(), | ||
email: yup.string().nullable().notRequired().email(), | ||
birthDate: yup.date().nullable().notRequired().min(new Date(1900, 0, 1)), | ||
}); | ||
``` | ||
|
||
You can derive the TypeScript type as follows: | ||
|
||
```ts | ||
import type { Asserts, TypeOf } from 'yup'; | ||
|
||
const parsed: Typeof<typeof personSchema> = personSchema.cast(json); | ||
|
||
const validated: Asserts<typeof personSchema> = personSchema.validateSync( | ||
parsed, | ||
); | ||
``` | ||
|
||
You can also go the other direction, specifying an interface and ensuring that a schema would match it: | ||
|
||
```ts | ||
import { string, object, number, SchemaOf } from 'yup'; | ||
|
||
type Person = { | ||
firstName: string; | ||
}; | ||
|
||
// ✔️ compiles | ||
const goodPersonSchema: SchemaOf<Person> = object({ | ||
firstName: string().defined(), | ||
}).defined(); | ||
|
||
// ❌ errors: | ||
// "Type 'number | undefined' is not assignable to type 'string'." | ||
const badPersonSchema: SchemaOf<Person> = object({ | ||
firstName: number(), | ||
}); | ||
``` | ||
|
||
### TypeScript settings | ||
|
||
For type utilties to work correctly with required and nullable types you have | ||
to set `strict: true` or `strictNullChecks: true` in your tsconfig.json. | ||
|
||
### Extending built-in types | ||
|
||
You can use TypeScript's interface merging behavior to extend the schema types | ||
if needed. Type extensions should go in an "ambient" type def file such as your | ||
`globals.d.ts`. | ||
|
||
```ts | ||
declare module 'yup' { | ||
class StringSchema<TIn, TContext, TOut> { | ||
myMethod(param: string): this; | ||
} | ||
} | ||
``` | ||
|
||
> Watch out!: If your method needs to adjust schema generics, you likely | ||
> need to also extend the Required*, and Defined* interfaces associated with | ||
> each basic type. Consult the core types for examples on how to do this |
This file contains 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
This file contains 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
This file contains 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
Oops, something went wrong.