decoders 2.0.0-beta8 → 2.0.0-beta9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -2
- package/README.md +234 -72
- package/_utils.js +1 -1
- package/_utils.js.flow +3 -3
- package/_utils.mjs +1 -1
- package/core/array.d.ts +3 -0
- package/core/array.js +12 -1
- package/core/array.js.flow +9 -2
- package/core/array.mjs +11 -2
- package/core/boolean.js +3 -3
- package/core/boolean.js.flow +2 -2
- package/core/boolean.mjs +2 -2
- package/core/composition.d.ts +5 -1
- package/core/composition.js +33 -5
- package/core/composition.js.flow +31 -5
- package/core/composition.mjs +31 -5
- package/core/date.js +3 -3
- package/core/date.js.flow +2 -2
- package/core/date.mjs +2 -2
- package/core/dispatch.d.ts +1 -1
- package/core/dispatch.js +8 -6
- package/core/dispatch.js.flow +9 -8
- package/core/dispatch.mjs +6 -5
- package/core/either.d.ts +63 -58
- package/core/either.js +30 -42
- package/core/either.js.flow +38 -84
- package/core/either.mjs +34 -34
- package/core/instanceOf.d.ts +1 -1
- package/core/json.js +3 -3
- package/core/json.js.flow +3 -3
- package/core/json.mjs +3 -3
- package/core/object.d.ts +5 -0
- package/core/object.js +59 -2
- package/core/object.js.flow +77 -21
- package/core/object.mjs +56 -3
- package/core/string.d.ts +3 -0
- package/core/string.js +15 -3
- package/core/string.js.flow +15 -2
- package/core/string.mjs +12 -3
- package/core/tuple.d.ts +28 -28
- package/core/tuple.js +28 -151
- package/core/tuple.js.flow +31 -191
- package/core/tuple.mjs +27 -141
- package/index.d.ts +17 -18
- package/index.js +33 -43
- package/index.js.flow +17 -18
- package/index.mjs +8 -9
- package/package.json +1 -1
- package/result.d.ts +2 -2
- package/result.js +9 -9
- package/result.js.flow +11 -11
- package/result.mjs +9 -9
- package/core/mapping.d.ts +0 -6
- package/core/mapping.js +0 -67
- package/core/mapping.js.flow +0 -62
- package/core/mapping.mjs +0 -58
package/CHANGELOG.md
CHANGED
|
@@ -10,9 +10,12 @@ Potentially breaking changes:
|
|
|
10
10
|
- Drop support for Flow versions below 0.142.0
|
|
11
11
|
- Drop support for TypeScript versions below 4.1.0
|
|
12
12
|
- Drop all package dependencies
|
|
13
|
+
- Renamed decoders:
|
|
14
|
+
- `map` → `transform` - see
|
|
15
|
+
[migration instructions](./MIGRATING-v2.md#map-is-now-transform)
|
|
16
|
+
- `dispatch` → `taggedUnion` - see
|
|
17
|
+
[migration instructions](./MIGRATING-v2.md#dispatch-is-now-taggedUnion)
|
|
13
18
|
- Decoders that have changed:
|
|
14
|
-
- `dispatch` has been renamed to `disjointUnion` - see
|
|
15
|
-
[migration instructions](./MIGRATING-v2.md#dispatch-is-now-disjointUnion)
|
|
16
19
|
- API of `guard` has changed (but only if you used its undocumented second argument
|
|
17
20
|
😉) - see [migration instructions](./MIGRATING-v2.md#changes-to-the-guard-api)
|
|
18
21
|
- API of `predicate` has changed - see
|
|
@@ -24,6 +27,12 @@ New features:
|
|
|
24
27
|
|
|
25
28
|
- Include ES modules in published NPM builds (yay tree-shaking! 🍃)
|
|
26
29
|
- Much smaller total bundle size
|
|
30
|
+
- New decoders:
|
|
31
|
+
- [`prep()`](https://nvie.com/decoders/api#prep)
|
|
32
|
+
- [`set()`](https://nvie.com/decoders/api#set)
|
|
33
|
+
- [`uuid`](https://nvie.com/decoders/api#uuid)
|
|
34
|
+
- [`uuidv1`](https://nvie.com/decoders/api#uuidv1)
|
|
35
|
+
- [`uuidv4`](https://nvie.com/decoders/api#uuidv4)
|
|
27
36
|
- Better error messages for nested `either`s
|
|
28
37
|
- Guard API now has a simpler way to specify formatters
|
|
29
38
|
|
package/README.md
CHANGED
|
@@ -68,14 +68,92 @@ And then, you can use it to decode values:
|
|
|
68
68
|
... })
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
## Understanding decoders and guards
|
|
72
|
+
|
|
73
|
+
At the heart, a decoder is a function that will take _any_ unsafe input, verify it, and
|
|
74
|
+
either return an "ok" or an annotated "err" result. It will never throw an error when
|
|
75
|
+
called.
|
|
76
|
+
|
|
77
|
+
A guard is a convenience wrapper which will use the decoder
|
|
78
|
+
|
|
71
79
|
## API
|
|
72
80
|
|
|
73
81
|
The decoders package consists of a few building blocks:
|
|
74
82
|
|
|
83
|
+
- [Guards](#guards)
|
|
75
84
|
- [Primitives](#primitives)
|
|
76
85
|
- [Compositions](#compositions)
|
|
77
86
|
- [Building custom decoders](#building-custom-decoders)
|
|
78
87
|
|
|
88
|
+
### Guards
|
|
89
|
+
|
|
90
|
+
<a name="guard" href="#guard">#</a> <b>guard</b>(decoder: <i>Decoder<T></i>,
|
|
91
|
+
formatter?: <i>Annotation => string</i>): <i>Guard<T></i>
|
|
92
|
+
[<>](https://github.com/nvie/decoders/blob/main/src/_guard.js 'Source')
|
|
93
|
+
|
|
94
|
+
Turns any given `Decoder<T>` into a `Guard<T>`.
|
|
95
|
+
|
|
96
|
+
A guard works like a decoder, but will either:
|
|
97
|
+
|
|
98
|
+
- Return the decoded value (aka the happy path)
|
|
99
|
+
- Or throw an exception
|
|
100
|
+
|
|
101
|
+
So a Guard bypasses the intermediate "Result" type that decoders output. An "ok" result
|
|
102
|
+
will get returned, an "err" result will be formatted into an error message and thrown.
|
|
103
|
+
|
|
104
|
+
The typical usage is that you keep composing decoders until you have one decoder for your
|
|
105
|
+
entire input object, and then use a guard to wrap that outer decoder. Decoders can be
|
|
106
|
+
composed to build larger decoders. Guards cannot be composed.
|
|
107
|
+
|
|
108
|
+
#### Formatting error messsages
|
|
109
|
+
|
|
110
|
+
By default, `guard()` will use the `formatInline` error formatter. You can pass another
|
|
111
|
+
built-in formatter as the second argument, or provide your own. (This will require
|
|
112
|
+
understanding the internal `Annotation` datastructure that decoders uses for error
|
|
113
|
+
reporting.)
|
|
114
|
+
|
|
115
|
+
Built-in formatters are:
|
|
116
|
+
|
|
117
|
+
- `formatInline` (default) — will echo back the input object and inline error messages
|
|
118
|
+
smartly. Example:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { array, guard, object, string } from 'decoders';
|
|
122
|
+
import { formatInline } from 'decoders/format';
|
|
123
|
+
|
|
124
|
+
const mydecoder = array(object({ name: string, age: number }));
|
|
125
|
+
|
|
126
|
+
const defaultGuard = guard(mydecoder, formatInline);
|
|
127
|
+
defaultGuard([{ name: 'Alice', age: '33' }]);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Will throw the following error message:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
Decoding error:
|
|
134
|
+
[
|
|
135
|
+
{
|
|
136
|
+
name: 'Alice',
|
|
137
|
+
age: '33',
|
|
138
|
+
^^^^ Must be number
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- `formatShort` — will report the _path_ into the object where the error happened.
|
|
144
|
+
Example:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { formatShort } from 'decoders/format';
|
|
148
|
+
const customGuard = guard(mydecoder, formatShort);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Will throw the following error message:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
Decoding error: Value at keypath 0.age: Must be number
|
|
155
|
+
```
|
|
156
|
+
|
|
79
157
|
### Primitives
|
|
80
158
|
|
|
81
159
|
<a name="number" href="#number">#</a> <b>number</b>: <i>Decoder<number></i>
|
|
@@ -319,6 +397,65 @@ const gitUrl: Decoder<URL> = predicate(
|
|
|
319
397
|
|
|
320
398
|
---
|
|
321
399
|
|
|
400
|
+
<a name="uuid" href="#uuid">#</a> <b>uuid</b>: <i>Decoder<string></i>
|
|
401
|
+
[(source)](https://github.com/nvie/decoders/blob/main/src/core/string.js 'Source')
|
|
402
|
+
|
|
403
|
+
Accepts strings that are valid [UUIDs][wiki-uuid] (universally unique identifier).
|
|
404
|
+
|
|
405
|
+
<!-- prettier-ignore-start -->
|
|
406
|
+
```javascript
|
|
407
|
+
const verify = guard(uuid);
|
|
408
|
+
|
|
409
|
+
// 👍
|
|
410
|
+
verify('123e4567-e89b-12d3-a456-426614174000') === '123e4567-e89b-12d3-a456-426614174000'
|
|
411
|
+
verify('123E4567-E89B-12D3-A456-426614174000') === '123E4567-E89B-12D3-A456-426614174000'
|
|
412
|
+
|
|
413
|
+
// 👎
|
|
414
|
+
verify('123E4567E89B12D3A456426614174000'); // throws
|
|
415
|
+
verify('abcdefgh-ijkl-mnop-qrst-uvwxyz012345'); // throws
|
|
416
|
+
```
|
|
417
|
+
<!-- prettier-ignore-end -->
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
<a name="uuidv1" href="#uuidv1">#</a> <b>uuidv1</b>: <i>Decoder<string></i>
|
|
422
|
+
[(source)](https://github.com/nvie/decoders/blob/main/src/core/string.js 'Source')
|
|
423
|
+
|
|
424
|
+
Like `uuid`, but only accepts [UUIDv1s][wiki-uuidv1] strings.
|
|
425
|
+
|
|
426
|
+
<!-- prettier-ignore-start -->
|
|
427
|
+
```javascript
|
|
428
|
+
const verify = guard(uuidv1);
|
|
429
|
+
|
|
430
|
+
// 👍
|
|
431
|
+
verify('123e4567-e89b-12d3-a456-426614174000') === '123e4567-e89b-42d3-a456-426614174000'
|
|
432
|
+
|
|
433
|
+
// 👎
|
|
434
|
+
verify('123e4567-e89b-42d3-a456-426614174000') // throws
|
|
435
|
+
```
|
|
436
|
+
<!-- prettier-ignore-end -->
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
<a name="uuidv4" href="#uuidv4">#</a> <b>uuidv4</b>: <i>Decoder<string></i>
|
|
441
|
+
[(source)](https://github.com/nvie/decoders/blob/main/src/core/string.js 'Source')
|
|
442
|
+
|
|
443
|
+
Like `uuid`, but only accepts [UUIDv4s][wiki-uuidv4] strings.
|
|
444
|
+
|
|
445
|
+
<!-- prettier-ignore-start -->
|
|
446
|
+
```javascript
|
|
447
|
+
const verify = guard(uuidv4);
|
|
448
|
+
|
|
449
|
+
// 👍
|
|
450
|
+
verify('123e4567-e89b-42d3-a456-426614174000') === '123e4567-e89b-42d3-a456-426614174000'
|
|
451
|
+
|
|
452
|
+
// 👎
|
|
453
|
+
verify('123e4567-e89b-12d3-a456-426614174000') // throws
|
|
454
|
+
```
|
|
455
|
+
<!-- prettier-ignore-end -->
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
322
459
|
<a name="boolean" href="#boolean">#</a> <b>boolean</b>: <i>Decoder<boolean></i>
|
|
323
460
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/boolean.js 'Source')
|
|
324
461
|
|
|
@@ -459,7 +596,7 @@ verify('hello world'); // throws
|
|
|
459
596
|
---
|
|
460
597
|
|
|
461
598
|
<a name="undefined_" href="#undefined_">#</a> <b>undefined\_</b>:
|
|
462
|
-
<i>Decoder<
|
|
599
|
+
<i>Decoder<undefined></i>
|
|
463
600
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/constants.js 'Source')
|
|
464
601
|
|
|
465
602
|
Accepts only the literal `undefined` value.
|
|
@@ -567,15 +704,13 @@ verify({ a: 'foo', b: 'bar' }); // throws
|
|
|
567
704
|
|
|
568
705
|
<a name="unknown" href="#unknown">#</a> <b>unknown</b>: <i>Decoder<unknown></i>
|
|
569
706
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/constants.js 'Source')
|
|
570
|
-
<a name="mixed" href="#mixed">#</a> <b>
|
|
707
|
+
<a name="mixed" href="#mixed">#</a> <b>mixed</b>: <i>Decoder<mixed></i>
|
|
571
708
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/constants.js 'Source')<br />
|
|
572
709
|
|
|
573
710
|
Accepts any value and returns it unchanged. Useful for situation in which you don't know
|
|
574
711
|
or expect a specific type. Of course, the downside is that you won't know the type of the
|
|
575
712
|
value statically and you'll have to further refine it yourself.
|
|
576
713
|
|
|
577
|
-
(Unknown is called `mixed` in Flow.)
|
|
578
|
-
|
|
579
714
|
```javascript
|
|
580
715
|
const verify = guard(mixed);
|
|
581
716
|
|
|
@@ -591,14 +726,14 @@ verify([1, 2]) === [1, 2];
|
|
|
591
726
|
|
|
592
727
|
### Compositions
|
|
593
728
|
|
|
594
|
-
Composite decoders
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
`Decoder<Array<Point>>`.
|
|
729
|
+
Composite decoders can build new decoders from existing decoders that can already decode a
|
|
730
|
+
"subtype". Examples are: if you already have a `string` decoder (of type
|
|
731
|
+
`Decoder<string>`), then you can use `array(string)` to automatically build a decoder for
|
|
732
|
+
arrays of strings, which will be of type `Decoder<Array<string>>`.
|
|
599
733
|
|
|
600
734
|
<a name="optional" href="#optional">#</a>
|
|
601
|
-
<b>optional</b><i><T></i>(<i>Decoder<T></i>): <i>Decoder<T |
|
|
735
|
+
<b>optional</b><i><T></i>(<i>Decoder<T></i>): <i>Decoder<T |
|
|
736
|
+
undefined></i>
|
|
602
737
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/optional.js 'Source')
|
|
603
738
|
|
|
604
739
|
Accepts only the literal value `undefined`, or whatever the given decoder accepts.
|
|
@@ -752,38 +887,17 @@ verify('hi'); // throws
|
|
|
752
887
|
|
|
753
888
|
---
|
|
754
889
|
|
|
755
|
-
<a name="
|
|
756
|
-
<
|
|
757
|
-
|
|
758
|
-
<a name="tuple2" href="#tuple2">#</a> <b>tuple2</b><i><T1,
|
|
759
|
-
T2></i>(<i>Decoder<T1></i>, <i>Decoder<T2></i>): <i>Decoder<[T1,
|
|
760
|
-
T2]></i>
|
|
761
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/tuple.js 'Source')<br />
|
|
762
|
-
<a name="tuple3" href="#tuple3">#</a> <b>tuple3</b><i><T1, T2,
|
|
763
|
-
T3></i>(<i>Decoder<T1></i>, <i>Decoder<T2></i>, <i>Decoder<T3></i>):
|
|
764
|
-
<i>Decoder<[T1, T2, T3]></i>
|
|
765
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/tuple.js 'Source')<br />
|
|
766
|
-
<a name="tuple4" href="#tuple4">#</a> <b>tuple4</b><i><T1, T2, T3,
|
|
767
|
-
T4></i>(<i>Decoder<T1></i>, <i>Decoder<T2></i>, <i>Decoder<T3></i>,
|
|
768
|
-
<i>Decoder<T4></i>): <i>Decoder<[T1, T2, T3, T4]></i>
|
|
769
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/tuple.js 'Source')<br />
|
|
770
|
-
<a name="tuple5" href="#tuple5">#</a> <b>tuple5</b><i><T1, T2, T3, T4,
|
|
771
|
-
T5></i>(<i>Decoder<T1></i>, <i>Decoder<T2></i>, <i>Decoder<T3></i>,
|
|
772
|
-
<i>Decoder<T3></i>, <i>Decoder<T4></i>, <i>Decoder<T5></i>):
|
|
773
|
-
<i>Decoder<[T1, T2, T3, T4, T5]></i>
|
|
774
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/tuple.js 'Source')<br />
|
|
775
|
-
<a name="tuple6" href="#tuple6">#</a> <b>tuple6</b><i><T1, T2, T3, T4, T5,
|
|
776
|
-
T6></i>(<i>Decoder<T1></i>, <i>Decoder<T2></i>, <i>Decoder<T3></i>,
|
|
777
|
-
<i>Decoder<T4></i>, <i>Decoder<T5></i>, <i>Decoder<T6></i>):
|
|
778
|
-
<i>Decoder<[T1, T2, T3, T4, T5, T6]></i>
|
|
890
|
+
<a name="tuple" href="#tuple">#</a> <b>tuple</b><i><A, B, C,
|
|
891
|
+
...></i>(<i>Decoder<A></i>, <i>Decoder<B></i>, <i>Decoder<C></i>):
|
|
892
|
+
<i>Decoder<[A, B, C, ...]></i>
|
|
779
893
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/tuple.js 'Source')
|
|
780
894
|
|
|
781
|
-
Accepts a
|
|
895
|
+
Accepts a tuple (an array with exactly _n_ items) of values accepted by the _n_ given
|
|
782
896
|
decoders.
|
|
783
897
|
|
|
784
898
|
<!-- prettier-ignore-start -->
|
|
785
899
|
```javascript
|
|
786
|
-
const verify = guard(
|
|
900
|
+
const verify = guard(tuple(string, number));
|
|
787
901
|
|
|
788
902
|
// 👍
|
|
789
903
|
verify(['hello', 1.2]) === ['hello', 1.2];
|
|
@@ -797,6 +911,26 @@ verify(['a', 1, 'c']); // throws, too many items
|
|
|
797
911
|
|
|
798
912
|
---
|
|
799
913
|
|
|
914
|
+
<a name="set" href="#set">#</a> <b>set</b><i><T></i>(<i>Decoder<T></i>):
|
|
915
|
+
<i>Decoder<Set<T>></i>
|
|
916
|
+
[<>](https://github.com/nvie/decoders/blob/main/src/core/array.js 'Source')
|
|
917
|
+
|
|
918
|
+
Similar to [`array`](#array), but returns the result as an ES6 Set.
|
|
919
|
+
|
|
920
|
+
<!-- prettier-ignore-start -->
|
|
921
|
+
```javascript
|
|
922
|
+
const verify = guard(set(string));
|
|
923
|
+
|
|
924
|
+
// 👍
|
|
925
|
+
verify(['hello', 'world']) // ≈ new Set(['hello', 'world']);
|
|
926
|
+
|
|
927
|
+
// 👎
|
|
928
|
+
verify(['hello', 1.2]); // throws
|
|
929
|
+
```
|
|
930
|
+
<!-- prettier-ignore-end -->
|
|
931
|
+
|
|
932
|
+
---
|
|
933
|
+
|
|
800
934
|
<a name="object" href="#object">#</a> <b>object</b><i><O: { [field: string]:
|
|
801
935
|
Decoder<any> }></i>(mapping: O): <i>Decoder<{ ... }></i>
|
|
802
936
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/object.js 'Source')
|
|
@@ -913,7 +1047,7 @@ verify(null); // throws
|
|
|
913
1047
|
|
|
914
1048
|
<a name="dict" href="#dict">#</a> <b>dict</b><i><T></i>(<i>Decoder<T></i>):
|
|
915
1049
|
<i>Decoder<{ [string]: <T>}></i>
|
|
916
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/
|
|
1050
|
+
[<>](https://github.com/nvie/decoders/blob/main/src/core/object.js 'Source')
|
|
917
1051
|
|
|
918
1052
|
Accepts objects where all values match the given decoder, and returns the result as a
|
|
919
1053
|
`{ [string]: T }`.
|
|
@@ -943,7 +1077,7 @@ verify({
|
|
|
943
1077
|
<a name="mapping" href="#mapping">#</a>
|
|
944
1078
|
<b>mapping</b><i><T></i>(<i>Decoder<T></i>): <i>Decoder<Map<string,
|
|
945
1079
|
T>></i>
|
|
946
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/
|
|
1080
|
+
[<>](https://github.com/nvie/decoders/blob/main/src/core/object.js 'Source')
|
|
947
1081
|
|
|
948
1082
|
Like `dict()`, but returns the result as a `Map<string, T>` instead.
|
|
949
1083
|
|
|
@@ -1042,22 +1176,13 @@ verify(null); // throws
|
|
|
1042
1176
|
|
|
1043
1177
|
---
|
|
1044
1178
|
|
|
1045
|
-
<a name="either" href="#either">#</a> <b>either</b><i><
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/either.js 'Source')<br />
|
|
1049
|
-
<a name="either2" href="#either2">#</a> <b>either2</b><i><T1,
|
|
1050
|
-
T2></i>(<i>Decoder<T1></i>, <i>Decoder<T2></i>): <i>Decoder<T1 |
|
|
1051
|
-
T2></i><br />
|
|
1052
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/either.js 'Source')<br />
|
|
1053
|
-
<a name="either3" href="#either3">#</a> <b>either3</b><i><T1, T2,
|
|
1054
|
-
T3></i>(<i>Decoder<T1></i>, <i>Decoder<T2></i>, <i>Decoder<T3></i>):
|
|
1055
|
-
<i>Decoder<T1 | T2 | T3></i>
|
|
1179
|
+
<a name="either" href="#either">#</a> <b>either</b><i><A, B, C,
|
|
1180
|
+
...></i>(<i>Decoder<A></i>, <i>Decoder<B></i>, <i>Decoder<C></i>,
|
|
1181
|
+
...): <i>Decoder<A | B | C | ...></i><br />
|
|
1056
1182
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/either.js 'Source')<br />
|
|
1057
|
-
...
|
|
1058
1183
|
|
|
1059
|
-
Accepts any value that is accepted by any of the given decoders.
|
|
1060
|
-
|
|
1184
|
+
Accepts any value that is accepted by any of the given decoders. The decoders are
|
|
1185
|
+
attempted on the input in given order. The first one that accepts the input "wins".
|
|
1061
1186
|
|
|
1062
1187
|
<!-- prettier-ignore-start -->
|
|
1063
1188
|
```javascript
|
|
@@ -1072,17 +1197,21 @@ verify(false); // throws
|
|
|
1072
1197
|
```
|
|
1073
1198
|
<!-- prettier-ignore-end -->
|
|
1074
1199
|
|
|
1200
|
+
**NOTE to Flow users:** In Flow, there is a max of 16 arguments with this construct. (This
|
|
1201
|
+
is no problem in TypeScript.) If you hit the 16 argument limit, you can work around that
|
|
1202
|
+
by stacking, e.g. do `either(<15 arguments here>, either(...))`.
|
|
1203
|
+
|
|
1075
1204
|
---
|
|
1076
1205
|
|
|
1077
|
-
<a name="
|
|
1078
|
-
|
|
1079
|
-
|
|
1206
|
+
<a name="taggedUnion" href="#taggedUnion">#</a> <b>taggedUnion</b><i><O: { [field:
|
|
1207
|
+
string]: (Decoder<T> | Decoder<V> | ...) }></i>(field: string, mapping: O):
|
|
1208
|
+
<i>Decoder<T | V | ...></i>
|
|
1080
1209
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/dispatch.js 'Source')
|
|
1081
1210
|
|
|
1082
1211
|
**NOTE:** In decoders@1.x, this was called `dispatch()`.
|
|
1083
1212
|
|
|
1084
|
-
Like
|
|
1085
|
-
|
|
1213
|
+
Like `either`, but only for building unions of object types with a common field (like a
|
|
1214
|
+
`type` field) that lets you distinguish members.
|
|
1086
1215
|
|
|
1087
1216
|
The following two decoders are effectively equivalent:
|
|
1088
1217
|
|
|
@@ -1092,14 +1221,14 @@ type Circle = { __type: 'circle', cx: number, cy: number, r: number };
|
|
|
1092
1221
|
// ^^^^^^
|
|
1093
1222
|
// Field that defines which decoder to pick
|
|
1094
1223
|
// vvvvvv
|
|
1095
|
-
const shape1: Decoder<Rect | Circle> =
|
|
1224
|
+
const shape1: Decoder<Rect | Circle> = taggedUnion('__type', { rect, circle });
|
|
1096
1225
|
const shape2: Decoder<Rect | Circle> = either(rect, circle);
|
|
1097
1226
|
```
|
|
1098
1227
|
|
|
1099
|
-
But using `
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1228
|
+
But using `taggedUnion()` will typically be more runtime-efficient than using `either()`.
|
|
1229
|
+
The reason is that `taggedUnion()` will first do minimal work to "look ahead" into the
|
|
1230
|
+
`type` field here, and based on that value, pick which decoder to invoke. Error messages
|
|
1231
|
+
will then also be tailored to the specific decoder.
|
|
1103
1232
|
|
|
1104
1233
|
The `either()` version will instead try each decoder in turn until it finds one that
|
|
1105
1234
|
matches. If none of the alternatives match, it needs to report all errors, which is
|
|
@@ -1167,8 +1296,9 @@ verify(3); // throws
|
|
|
1167
1296
|
|
|
1168
1297
|
---
|
|
1169
1298
|
|
|
1170
|
-
<a name="
|
|
1171
|
-
<i
|
|
1299
|
+
<a name="transform" href="#transform">#</a> <b>transform</b><i><T,
|
|
1300
|
+
V></i>(<i>Decoder<T></i>, <i><T></i> => <i><V></i>):
|
|
1301
|
+
<i>Decoder<V></i>
|
|
1172
1302
|
[<>](https://github.com/nvie/decoders/blob/main/src/core/utils.js 'Source')<br />
|
|
1173
1303
|
|
|
1174
1304
|
Accepts any value the given decoder accepts, and on success, will call the mapper value
|
|
@@ -1177,7 +1307,7 @@ fail using the error message as the failure reason.
|
|
|
1177
1307
|
|
|
1178
1308
|
<!-- prettier-ignore-start -->
|
|
1179
1309
|
```javascript
|
|
1180
|
-
const upper =
|
|
1310
|
+
const upper = transform(string, (s) => s.toUpperCase());
|
|
1181
1311
|
const verify = guard(upper);
|
|
1182
1312
|
|
|
1183
1313
|
// 👍
|
|
@@ -1207,7 +1337,7 @@ recommended to rely on this decoder directly for normal usage.
|
|
|
1207
1337
|
<a name="predicate" href="#predicate">#</a>
|
|
1208
1338
|
<b>predicate</b><i><T></i>(<i>Decoder<T></i>, <i><T> => boolean</i>,
|
|
1209
1339
|
string): <i>Decoder<T></i>
|
|
1210
|
-
[<>](https://github.com/nvie/decoders/blob/main/src/core/
|
|
1340
|
+
[<>](https://github.com/nvie/decoders/blob/main/src/core/composition.js 'Source')<br />
|
|
1211
1341
|
|
|
1212
1342
|
Accepts values that are accepted by the decoder _and_ also pass the predicate function.
|
|
1213
1343
|
|
|
@@ -1215,7 +1345,7 @@ Accepts values that are accepted by the decoder _and_ also pass the predicate fu
|
|
|
1215
1345
|
```typescript
|
|
1216
1346
|
const odd = predicate(
|
|
1217
1347
|
number,
|
|
1218
|
-
(n) => n % 2
|
|
1348
|
+
(n) => n % 2 !== 0,
|
|
1219
1349
|
'Must be odd'
|
|
1220
1350
|
);
|
|
1221
1351
|
const verify = guard(odd);
|
|
@@ -1234,6 +1364,38 @@ predicate][type-predicates], then this will be reflected in the return type, too
|
|
|
1234
1364
|
|
|
1235
1365
|
---
|
|
1236
1366
|
|
|
1367
|
+
<a name="prep" href="#prep">#</a> <b>prep</b><i><T, I></i>(<i>unknown => I</i>,
|
|
1368
|
+
<i>Decoder<T, I></i>): <i>Decoder<T></i>
|
|
1369
|
+
[<>](https://github.com/nvie/decoders/blob/main/src/core/composition.js 'Source')<br />
|
|
1370
|
+
|
|
1371
|
+
Pre-process the raw data input before passing it into the decoder. This gives you the
|
|
1372
|
+
ability to arbitrarily customize the input on the fly before passing it to the decoder. Of
|
|
1373
|
+
course, the input value at that point is still of `unknown` type, so you will have to deal
|
|
1374
|
+
with that accordingly.
|
|
1375
|
+
|
|
1376
|
+
<!-- prettier-ignore-start -->
|
|
1377
|
+
```typescript
|
|
1378
|
+
const verify = prep(
|
|
1379
|
+
// Will convert any input to an int first, before feeding it to
|
|
1380
|
+
// positiveInteger. This will effectively also allow numeric strings
|
|
1381
|
+
// to be accepted (and returned) as integers. If this ever throws,
|
|
1382
|
+
// then the error message will be what gets annotated on the input.
|
|
1383
|
+
x => parseInt(x),
|
|
1384
|
+
positiveInteger,
|
|
1385
|
+
);
|
|
1386
|
+
|
|
1387
|
+
// 👍
|
|
1388
|
+
verify(42) === 42;
|
|
1389
|
+
verify('3') === 3;
|
|
1390
|
+
|
|
1391
|
+
// 👎
|
|
1392
|
+
verify('-3'); // throws: not a positive number
|
|
1393
|
+
verify('hi'); // throws: not a number
|
|
1394
|
+
```
|
|
1395
|
+
<!-- prettier-ignore-end -->
|
|
1396
|
+
|
|
1397
|
+
---
|
|
1398
|
+
|
|
1237
1399
|
<a name="describe" href="#describe">#</a>
|
|
1238
1400
|
<b>describe</b><i><T></i>(<i>Decoder<T></i>, <i>string</i>):
|
|
1239
1401
|
<i>Decoder<T></i>
|
|
@@ -1304,13 +1466,13 @@ And a runtime input of:
|
|
|
1304
1466
|
| | Extra properties | Output value | Inferred type |
|
|
1305
1467
|
| ---------------- | ---------------- | ------------------------------ | ------------------------------------------- |
|
|
1306
1468
|
| `object(thing)` | discarded | `{a: "hi", b: 42}` | `{a: string, b: number}` |
|
|
1307
|
-
| `exact(thing)` | not allowed |
|
|
1469
|
+
| `exact(thing)` | not allowed | n/a (rejected) | `{a: string, b: number}` |
|
|
1308
1470
|
| `inexact(thing)` | retained | `{a: "hi", b: 42, c: "extra"}` | `{a: string, b: number, [string]: unknown}` |
|
|
1309
1471
|
|
|
1310
1472
|
### Building custom decoders
|
|
1311
1473
|
|
|
1312
|
-
There are two main building blocks for defining your own custom decoders: `
|
|
1313
|
-
`compose()`.
|
|
1474
|
+
There are two main building blocks for defining your own custom decoders: `transform()`
|
|
1475
|
+
and `compose()`.
|
|
1314
1476
|
|
|
1315
1477
|
There are roughly 3 use cases that you will want to use:
|
|
1316
1478
|
|
|
@@ -1325,7 +1487,7 @@ There are roughly 3 use cases that you will want to use:
|
|
|
1325
1487
|
To read one type from the input, but return another, use:
|
|
1326
1488
|
|
|
1327
1489
|
```js
|
|
1328
|
-
const numericString: Decoder<number> =
|
|
1490
|
+
const numericString: Decoder<number> = transform(
|
|
1329
1491
|
// At runtime, expect to read a string...
|
|
1330
1492
|
string,
|
|
1331
1493
|
// ...but return it as a number
|
|
@@ -1336,7 +1498,7 @@ const numericString: Decoder<number> = map(
|
|
|
1336
1498
|
To read one type, but change its value before returning:
|
|
1337
1499
|
|
|
1338
1500
|
```js
|
|
1339
|
-
const upperCase: Decoder<string> =
|
|
1501
|
+
const upperCase: Decoder<string> = transform(string, (s) => s.toUpperCase());
|
|
1340
1502
|
```
|
|
1341
1503
|
|
|
1342
1504
|
**WARNING:** While you can map anything to anything, it's typically **NOT A GOOD IDEA to
|
package/_utils.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.indent = indent;
|
|
|
7
7
|
exports.isDate = isDate;
|
|
8
8
|
exports.isMultiline = isMultiline;
|
|
9
9
|
exports.summarize = summarize;
|
|
10
|
-
// $FlowFixMe[unclear-type] - deliberate
|
|
10
|
+
// $FlowFixMe[unclear-type] - deliberate use of `any`
|
|
11
11
|
// Two spaces of indentation
|
|
12
12
|
var INDENT = ' ';
|
|
13
13
|
/**
|
package/_utils.js.flow
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Annotation } from './annotate';
|
|
4
4
|
|
|
5
|
-
// $FlowFixMe[unclear-type] - deliberate
|
|
6
|
-
type
|
|
5
|
+
// $FlowFixMe[unclear-type] - deliberate use of `any`
|
|
6
|
+
export type _Any = any;
|
|
7
7
|
|
|
8
8
|
// Two spaces of indentation
|
|
9
9
|
export const INDENT = ' ';
|
|
@@ -29,7 +29,7 @@ export function isDate(value: mixed): boolean {
|
|
|
29
29
|
* null.
|
|
30
30
|
*/
|
|
31
31
|
export function asDate(value: mixed): Date | null {
|
|
32
|
-
return isDate(value) ? ((value:
|
|
32
|
+
return isDate(value) ? ((value: _Any): Date) : null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function isMultiline(s: string): boolean {
|
package/_utils.mjs
CHANGED
package/core/array.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
/// <reference lib="es6" />
|
|
2
|
+
|
|
1
3
|
import { Decoder } from '../_types';
|
|
2
4
|
|
|
3
5
|
export const poja: Decoder<unknown[]>;
|
|
4
6
|
export function array<T>(decoder: Decoder<T>): Decoder<T[]>;
|
|
5
7
|
export function nonEmptyArray<T>(decoder: Decoder<T>): Decoder<[T, ...T[]]>;
|
|
8
|
+
export function set<T>(decoder: Decoder<T>): Decoder<Set<T>>;
|
package/core/array.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.__esModule = true;
|
|
|
4
4
|
exports.array = array;
|
|
5
5
|
exports.nonEmptyArray = nonEmptyArray;
|
|
6
6
|
exports.poja = void 0;
|
|
7
|
+
exports.set = set;
|
|
7
8
|
|
|
8
9
|
var _annotate = require("../annotate");
|
|
9
10
|
|
|
@@ -47,7 +48,7 @@ function all(items, blobs) {
|
|
|
47
48
|
for (var index = 0; index < items.length; ++index) {
|
|
48
49
|
var result = items[index];
|
|
49
50
|
|
|
50
|
-
if (result.
|
|
51
|
+
if (result.ok) {
|
|
51
52
|
results.push(result.value);
|
|
52
53
|
} else {
|
|
53
54
|
var ann = result.error; // Rewrite the annotation to include the index information, and inject it into the original blob
|
|
@@ -101,4 +102,14 @@ function nonEmptyArray(decoder) {
|
|
|
101
102
|
return (0, _composition.predicate)(array(decoder), function (arr) {
|
|
102
103
|
return arr.length > 0;
|
|
103
104
|
}, 'Must be non-empty array');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Similar to `array()`, but returns the result as an ES6 Set.
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
function set(decoder) {
|
|
112
|
+
return (0, _composition.transform)(array(decoder), function (items) {
|
|
113
|
+
return new Set(items);
|
|
114
|
+
});
|
|
104
115
|
}
|
package/core/array.js.flow
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @flow strict
|
|
2
2
|
|
|
3
3
|
import { annotate } from '../annotate';
|
|
4
|
-
import { compose, predicate } from './composition';
|
|
4
|
+
import { compose, predicate, transform } from './composition';
|
|
5
5
|
import { err, ok } from '../result';
|
|
6
6
|
import type { Decoder, DecodeResult } from '../_types';
|
|
7
7
|
|
|
@@ -40,7 +40,7 @@ function all<T>(
|
|
|
40
40
|
const results: Array<T> = [];
|
|
41
41
|
for (let index = 0; index < items.length; ++index) {
|
|
42
42
|
const result = items[index];
|
|
43
|
-
if (result.
|
|
43
|
+
if (result.ok) {
|
|
44
44
|
results.push(result.value);
|
|
45
45
|
} else {
|
|
46
46
|
const ann = result.error;
|
|
@@ -98,3 +98,10 @@ export function array<T>(decoder: Decoder<T>): Decoder<Array<T>> {
|
|
|
98
98
|
export function nonEmptyArray<T>(decoder: Decoder<T>): Decoder<Array<T>> {
|
|
99
99
|
return predicate(array(decoder), (arr) => arr.length > 0, 'Must be non-empty array');
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Similar to `array()`, but returns the result as an ES6 Set.
|
|
104
|
+
*/
|
|
105
|
+
export function set<T>(decoder: Decoder<T>): Decoder<Set<T>> {
|
|
106
|
+
return transform(array(decoder), (items) => new Set(items));
|
|
107
|
+
}
|
package/core/array.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { annotate } from '../annotate.mjs';
|
|
2
|
-
import { compose, predicate } from './composition.mjs';
|
|
2
|
+
import { compose, predicate, transform } from './composition.mjs';
|
|
3
3
|
import { err, ok } from '../result.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -35,7 +35,7 @@ function all(items, blobs) {
|
|
|
35
35
|
for (var index = 0; index < items.length; ++index) {
|
|
36
36
|
var result = items[index];
|
|
37
37
|
|
|
38
|
-
if (result.
|
|
38
|
+
if (result.ok) {
|
|
39
39
|
results.push(result.value);
|
|
40
40
|
} else {
|
|
41
41
|
var ann = result.error; // Rewrite the annotation to include the index information, and inject it into the original blob
|
|
@@ -88,4 +88,13 @@ export function nonEmptyArray(decoder) {
|
|
|
88
88
|
return predicate(array(decoder), function (arr) {
|
|
89
89
|
return arr.length > 0;
|
|
90
90
|
}, 'Must be non-empty array');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Similar to `array()`, but returns the result as an ES6 Set.
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
export function set(decoder) {
|
|
97
|
+
return transform(array(decoder), function (items) {
|
|
98
|
+
return new Set(items);
|
|
99
|
+
});
|
|
91
100
|
}
|