Skip to content

Commit c014a2a

Browse files
committed
document withMutation
1 parent 31f9c28 commit c014a2a

File tree

2 files changed

+138
-43
lines changed

2 files changed

+138
-43
lines changed

README.md

Lines changed: 135 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
Yup
23
=======================
34

@@ -11,20 +12,73 @@ It also allows "stacking" conditions via `when` for properties that depend on mo
1112
child property. Yup separates the parsing and validating functions into separate steps so it can be used to parse
1213
json separate from validating it, via the `cast` method.
1314

14-
## Usage
15+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
16+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
17+
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
18+
19+
- [Usage](#usage)
20+
- [API](#api)
21+
- [`yup`](#yup)
22+
- [`.reach(Schema schema, String path, Object options)`](#reachschema-schema-string-path-object-options)
23+
- [`.addMethod(schemaType, name, method)`](#addmethodschematype-name-method)
24+
- [`ValidationError(String|Array<String> errors, Any value, String path)`](#validationerrorstringarraystring-errors-any-value-string-path)
25+
- [`mixed`](#mixed)
26+
- [`mixed.clone()`](#mixedclone)
27+
- [`mixed.concat(Schema schema)`](#mixedconcatschema-schema)
28+
- [`mixed.validate(Any value, [Object options, Function callback])`](#mixedvalidateany-value-object-options-function-callback)
29+
- [`mixed.isValid(Any value, [Object options, Function callback]) -> Promise`](#mixedisvalidany-value-object-options-function-callback---promise)
30+
- [`mixed.cast(value) -> Any`](#mixedcastvalue---any)
31+
- [`mixed.isType(Any value) -> Boolean`](#mixedistypeany-value---boolean)
32+
- [`mixed.strict()` (default: `false`)](#mixedstrict-default-false)
33+
- [`mixed.default(Any value)`](#mixeddefaultany-value)
34+
- [`mixed.default() -> Any`](#mixeddefault---any)
35+
- [`mixed.typeError(String message)` (default: '${path} (value: \`${value}\`) must be a \`${type}\` type')](#mixedtypeerrorstring-message-default-path-value-%5Cvalue%5C-must-be-a-%5Ctype%5C-type)
36+
- [`mixed.nullable(Bool isNullable)` (default: `false`)](#mixednullablebool-isnullable-default-false)
37+
- [`mixed.required([String message])`](#mixedrequiredstring-message)
38+
- [`mixed.oneOf(Array<Any> arrayOfValues, [String message])` Alias: `equals`](#mixedoneofarrayany-arrayofvalues-string-message-alias-equals)
39+
- [`mixed.notOneOf(Array<Any> arrayOfValues, [String message])`](#mixednotoneofarrayany-arrayofvalues-string-message)
40+
- [`mixed.when(String key, Object options | Function func)`](#mixedwhenstring-key-object-options--function-func)
41+
- [`mixed.test(String name, String message, Function fn, [Bool callbackStyleAsync])`](#mixedteststring-name-string-message-function-fn-bool-callbackstyleasync)
42+
- [`mixed.test(Object options)`](#mixedtestobject-options)
43+
- [`mixed.transform(Function fn)`](#mixedtransformfunction-fn)
44+
- [string](#string)
45+
- [`string.required([String message])`](#stringrequiredstring-message)
46+
- [`string.min(Number limit, [String message])`](#stringminnumber-limit-string-message)
47+
- [`string.max(Number limit, [String message])`](#stringmaxnumber-limit-string-message)
48+
- [`string.matches(Regex regex, [String message])`](#stringmatchesregex-regex-string-message)
49+
- [`string.email([String message])`](#stringemailstring-message)
50+
- [`string.url([String message])`](#stringurlstring-message)
51+
- [`string.trim([String message])`](#stringtrimstring-message)
52+
- [`string.lowercase([String message])`](#stringlowercasestring-message)
53+
- [`string.uppercase([String message])`](#stringuppercasestring-message)
54+
- [number](#number)
55+
- [`number.min(Number limit, [String message])`](#numberminnumber-limit-string-message)
56+
- [`number.max(Number limit, [String message])`](#numbermaxnumber-limit-string-message)
57+
- [`number.positive([String message])`](#numberpositivestring-message)
58+
- [`number.negative([String message])`](#numbernegativestring-message)
59+
- [`number.integer([String message])`](#numberintegerstring-message)
60+
- [`round(String type)` - 'floor', 'ceil', 'round'](#roundstring-type---floor-ceil-round)
61+
- [boolean](#boolean)
62+
- [date](#date)
63+
- [`date.min(Date|String limit, [String message])`](#datemindatestring-limit-string-message)
64+
- [`date.max(Date|String limit, [String message])`](#datemaxdatestring-limit-string-message)
65+
- [array](#array)
66+
- [`array.of(Schema type)`](#arrayofschema-type)
67+
- [`array.required([String message])`](#arrayrequiredstring-message)
68+
- [`array.min(Number limit, [String message])`](#arrayminnumber-limit-string-message)
69+
- [`array.max(Number limit, [String message])`](#arraymaxnumber-limit-string-message)
70+
- [`array.compact(Function rejector)`](#arraycompactfunction-rejector)
71+
- [object](#object)
72+
- [`object.shape(Object schemaHash, [noSortEdges])`](#objectshapeobject-schemahash-nosortedges)
73+
- [`object.from(String fromKey, String toKey, Bool alias)`](#objectfromstring-fromkey-string-tokey-bool-alias)
74+
- [`object.noUnknown([Bool onlyKnownKeys, String msg])`](#objectnounknownbool-onlyknownkeys-string-msg)
75+
- [`object.camelcase()`](#objectcamelcase)
76+
- [`object.constantcase()`](#objectconstantcase)
77+
- [Extending Schema Types](#extending-schema-types)
78+
79+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
1580

16-
- [Yup](#yup-1)
17-
+ [`mixed`](#mixed)
18-
+ [`string`](#string)
19-
+ [`number`](#number)
20-
+ [`boolean`](#boolean)
21-
+ [`date`](#date)
22-
+ [`array`](#array)
23-
+ [`object`](#array)
24-
- [`reach`](#reachschema-schema-string-path-object-options)
25-
- [`addMethod`](#addmethodschematype-name-method)
26-
- [`ValidationError`](#validationerrorstringarraystring-errors-string-path-any-value)
27-
- [Extending Schema Types](#extending-schema-types)
81+
## Usage
2882

2983
You define and create schema objects. Schema objects are immutable, so each call of a method returns a _new_ schema object.
3084

@@ -60,6 +114,8 @@ schema.cast({
60114
// => { name: 'jimmy', age: 24, createdOn: Date }
61115
```
62116

117+
## API
118+
63119
### `yup`
64120

65121
The module export.
@@ -128,7 +184,7 @@ Thrown on failed validations, with the following properties
128184
alternatively `errors` will have all the of the messages from each inner error.
129185

130186

131-
### `mixed`
187+
### mixed
132188

133189
Creates a schema that matches all types. All types inherit from this base type
134190

@@ -219,6 +275,30 @@ You should use `isType` for all Schema type checks.
219275
Sets the `strict` option to `true`. Strict schemas skip coercion and transformation attempts,
220276
validating the value "as is".
221277

278+
#### `mixed.withMutation(Function fn)`
279+
280+
First the legally required Rich Hickey quote:
281+
282+
> If a tree falls in the woods, does it make a sound?
283+
>
284+
> If a pure function mutates some local data in order to produce an immutable return value, is that ok?
285+
286+
`withMutation` allows you to mutate the schema in place, instead of the default behavior which clones before each change.
287+
Generally this isn't necessary since the vast majority of schema changes happen during the initial
288+
declaration, and only happen once over the lifetime of the schema, so performance isn't an issue.
289+
However certain mutations _do_ occur at cast/validation time, (such as conditional schema using `when()`), or
290+
when instantiating a schema object.
291+
292+
```js
293+
object()
294+
.shape({ key: string() })
295+
.withMutation(schema => {
296+
return arrayOfObjectTests.forEach(test => {
297+
schema.test(test)
298+
})
299+
})
300+
```
301+
222302
#### `mixed.default(Any value)`
223303

224304
Sets a default value to use when the value is `undefined` (or `null` when the schema is not nullable).
@@ -242,12 +322,7 @@ for objects and arrays. To avoid this overhead you can also pass a function that
242322
Calling `default` with no arguments will return the current default value
243323

244324

245-
#### `mixed.typeError(String message)` (default: '${path} (value: \`${value}\`) must be a \`${type}\` type')
246-
247-
Define an error message for failed type checks. The `${value}` and `${type}` interpolation can
248-
be used in the `message` argument.
249-
250-
#### `mixed.nullable(Bool isNullable)` (default: `false`)
325+
#### `mixed.nullable(Bool isNullable = false)`
251326

252327
Indicates that `null` is a valid value for the schema. Without `nullable()`
253328
`null` is treated as a different type and will fail `isType()` checks.
@@ -256,6 +331,11 @@ Indicates that `null` is a valid value for the schema. Without `nullable()`
256331

257332
Mark the schema as required. All field values apart from `undefined` meet this requirement.
258333

334+
#### `mixed.typeError(String message)`
335+
336+
Define an error message for failed type checks. The `${value}` and `${type}` interpolation can
337+
be used in the `message` argument.
338+
259339
#### `mixed.oneOf(Array<Any> arrayOfValues, [String message])` Alias: `equals`
260340

261341
Whitelist a set of values. Values added are automatically removed from any blacklist if they are in it.
@@ -692,8 +772,8 @@ var invalidDate = new Date('');
692772
function parseDateFromFormats(formats, parseStrict) {
693773

694774
return this.transform(function(value, originalValue){
695-
696-
if ( this.isType(value) ) return value
775+
if (this.isType(value))
776+
return value
697777

698778
value = Moment(originalValue, formats, parseStrict)
699779

@@ -707,32 +787,45 @@ yup.addMethod(yup.date, 'format', parseDateFromFormats)
707787
```
708788

709789
__Creating new Types__
710-
```js
711-
var inherits = require('inherits')
712-
var invalidDate = new Date(''); // our failed to coerce value
713790

714-
function MomentDateSchemaType(){
715-
// so we don't need to use the `new` keyword
716-
if ( !(this instanceof MomentDateSchemaType))
717-
return new MomentDateSchemaType()
791+
Yup schema use the common constructor pattern for modeling inheritance. You can use any
792+
utility or pattern that works with that pattern. The below demonstrates using the es6 class
793+
syntax since its less verbose, but you absolutely aren't required to use it.
718794

719-
yup.date.call(this)
795+
```js
796+
var DateSchema = yup.date
797+
var invalidDate = new Date(''); // our failed to coerce value
798+
799+
class MomentDateSchemaType extends DateSchema {
800+
constructor() {
801+
super();
802+
this._validFormats = [];
803+
804+
this.withMutation(() => {
805+
this.transform(function (value, originalvalue) {
806+
if (this.isType(value)) // we have a valid value
807+
return value
808+
return Moment(originalValue, this._validFormats, true)
809+
})
810+
})
720811
}
721812

722-
inherits(MomentDateSchemaType, yup.date)
723-
724-
MomentDateSchemaType.prototype.format = function(formats, strict){
725-
if (!formats) throw new Error('must enter a valid format')
813+
_typeCheck(value) {
814+
return super._typeCheck(value)
815+
|| (moment.isMoment(value) && value.isValid())
816+
}
726817

727-
this.transforms.push(function(value, originalValue) {
728-
if ( this.isType(value) ) // we have a valid value
729-
return value
730-
value = Moment(originalValue, formats, strict)
731-
return value.isValid() ? value.toDate() : invalidDate
732-
})
818+
format(formats) {
819+
if (!formats)
820+
throw new Error('must enter a valid format')
821+
let next = this.clone()
822+
next._validFormats = {}.concat(formats);
733823
}
824+
}
734825

735-
var schema = MomentDateSchemaType().format('YYYY-MM-DD')
826+
var schema = new MomentDateSchemaType()
736827

737-
schema.cast('It is 2012-05-25') // Fri May 25 2012 00:00:00 GMT-0400 (Eastern Daylight Time)
828+
schema
829+
.format('YYYY-MM-DD')
830+
.cast('It is 2012-05-25') // Fri May 25 2012 00:00:00 GMT-0400 (Eastern Daylight Time)
738831
```

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"test": "npm run build && karma start --single-run",
88
"tdd": "npm run build && karma start",
99
"clean": "rimaf ./lib/*",
10-
"build": "babel src --out-dir lib",
10+
"toc": "doctoc README.md --github",
11+
"build": "babel src --out-dir lib && toc",
1112
"release": "release"
1213
},
1314
"repository": {
@@ -28,6 +29,7 @@
2829
"benchmark": "^2.0.0",
2930
"chai": "^1.9.1",
3031
"chai-as-promised": "^4.1.1",
32+
"doctoc": "^1.0.0",
3133
"eslint": "^0.24.1",
3234
"karma": "^0.13.14",
3335
"karma-chrome-launcher": "^0.2.0",

0 commit comments

Comments
 (0)