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.
Files changed (56) hide show
  1. package/CHANGELOG.md +11 -2
  2. package/README.md +234 -72
  3. package/_utils.js +1 -1
  4. package/_utils.js.flow +3 -3
  5. package/_utils.mjs +1 -1
  6. package/core/array.d.ts +3 -0
  7. package/core/array.js +12 -1
  8. package/core/array.js.flow +9 -2
  9. package/core/array.mjs +11 -2
  10. package/core/boolean.js +3 -3
  11. package/core/boolean.js.flow +2 -2
  12. package/core/boolean.mjs +2 -2
  13. package/core/composition.d.ts +5 -1
  14. package/core/composition.js +33 -5
  15. package/core/composition.js.flow +31 -5
  16. package/core/composition.mjs +31 -5
  17. package/core/date.js +3 -3
  18. package/core/date.js.flow +2 -2
  19. package/core/date.mjs +2 -2
  20. package/core/dispatch.d.ts +1 -1
  21. package/core/dispatch.js +8 -6
  22. package/core/dispatch.js.flow +9 -8
  23. package/core/dispatch.mjs +6 -5
  24. package/core/either.d.ts +63 -58
  25. package/core/either.js +30 -42
  26. package/core/either.js.flow +38 -84
  27. package/core/either.mjs +34 -34
  28. package/core/instanceOf.d.ts +1 -1
  29. package/core/json.js +3 -3
  30. package/core/json.js.flow +3 -3
  31. package/core/json.mjs +3 -3
  32. package/core/object.d.ts +5 -0
  33. package/core/object.js +59 -2
  34. package/core/object.js.flow +77 -21
  35. package/core/object.mjs +56 -3
  36. package/core/string.d.ts +3 -0
  37. package/core/string.js +15 -3
  38. package/core/string.js.flow +15 -2
  39. package/core/string.mjs +12 -3
  40. package/core/tuple.d.ts +28 -28
  41. package/core/tuple.js +28 -151
  42. package/core/tuple.js.flow +31 -191
  43. package/core/tuple.mjs +27 -141
  44. package/index.d.ts +17 -18
  45. package/index.js +33 -43
  46. package/index.js.flow +17 -18
  47. package/index.mjs +8 -9
  48. package/package.json +1 -1
  49. package/result.d.ts +2 -2
  50. package/result.js +9 -9
  51. package/result.js.flow +11 -11
  52. package/result.mjs +9 -9
  53. package/core/mapping.d.ts +0 -6
  54. package/core/mapping.js +0 -67
  55. package/core/mapping.js.flow +0 -62
  56. package/core/mapping.mjs +0 -58
package/core/either.js CHANGED
@@ -1,14 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.either = either;
5
- exports.either3 = either3;
6
- exports.either4 = either4;
7
- exports.either5 = either5;
8
- exports.either6 = either6;
9
- exports.either7 = either7;
10
- exports.either8 = either8;
11
- exports.either9 = either9;
4
+ exports.either = void 0;
12
5
  exports.oneOf = oneOf;
13
6
 
14
7
  var _annotate = require("../annotate");
@@ -17,9 +10,11 @@ var _result = require("../result");
17
10
 
18
11
  var _utils = require("../_utils");
19
12
 
13
+ var EITHER_PREFIX = 'Either:\n';
20
14
  /**
21
15
  * Indents and adds a dash in front of this (potentially multiline) string.
22
16
  */
17
+
23
18
  function itemize(s) {
24
19
  return '-' + (0, _utils.indent)(s).substring(1);
25
20
  }
@@ -51,51 +46,44 @@ function itemize(s) {
51
46
 
52
47
 
53
48
  function nest(errText) {
54
- var EITHER_PREFIX = 'Either:\n';
55
49
  return errText.startsWith(EITHER_PREFIX) ? errText.substr(EITHER_PREFIX.length) : itemize(errText);
56
- }
50
+ } // prettier-ignore
57
51
 
58
- function either(d1, d2) {
59
- return function (blob) {
60
- return (0, _result.orElse)(d1(blob), function (err1) {
61
- return (0, _result.orElse)(d2(blob), function (err2) {
62
- var serr1 = (0, _utils.summarize)(err1).join('\n');
63
- var serr2 = (0, _utils.summarize)(err2).join('\n');
64
- var text = ['Either:', nest(serr1), nest(serr2)].join('\n');
65
- return (0, _result.err)((0, _annotate.annotate)(blob, text));
66
- });
67
- });
68
- };
69
- }
70
52
 
71
- function either3(d1, d2, d3) {
72
- return either(d1, either(d2, d3));
73
- }
53
+ function _either() {
54
+ for (var _len = arguments.length, decoders = new Array(_len), _key = 0; _key < _len; _key++) {
55
+ decoders[_key] = arguments[_key];
56
+ }
74
57
 
75
- function either4(d1, d2, d3, d4) {
76
- return either(d1, either3(d2, d3, d4));
77
- }
58
+ if (decoders.length === 0) {
59
+ throw new Error('Pass at least one decoder to either()');
60
+ }
78
61
 
79
- function either5(d1, d2, d3, d4, d5) {
80
- return either(d1, either4(d2, d3, d4, d5));
81
- }
62
+ return function (blob) {
63
+ // Collect errors here along the way
64
+ var errors = [];
82
65
 
83
- function either6(d1, d2, d3, d4, d5, d6) {
84
- return either(d1, either5(d2, d3, d4, d5, d6));
85
- }
66
+ for (var _i = 0; _i < decoders.length; _i++) {
67
+ var result = decoders[_i](blob);
86
68
 
87
- function either7(d1, d2, d3, d4, d5, d6, d7) {
88
- return either(d1, either6(d2, d3, d4, d5, d6, d7));
89
- }
69
+ if (result.ok) {
70
+ return result;
71
+ } else {
72
+ errors.push(result.error);
73
+ }
74
+ } // Decoding all alternatives failed, return the combined error message
90
75
 
91
- function either8(d1, d2, d3, d4, d5, d6, d7, d8) {
92
- return either(d1, either7(d2, d3, d4, d5, d6, d7, d8));
93
- }
94
76
 
95
- function either9(d1, d2, d3, d4, d5, d6, d7, d8, d9) {
96
- return either(d1, either8(d2, d3, d4, d5, d6, d7, d8, d9));
77
+ var text = EITHER_PREFIX + errors.map(function (err) {
78
+ return nest((0, _utils.summarize)(err).join('\n'));
79
+ }).join('\n');
80
+ return (0, _result.err)((0, _annotate.annotate)(blob, text));
81
+ };
97
82
  }
98
83
 
84
+ var either = _either;
85
+ exports.either = either;
86
+
99
87
  function oneOf(constants) {
100
88
  return function (blob) {
101
89
  var winner = constants.find(function (c) {
@@ -1,9 +1,12 @@
1
1
  // @flow strict
2
2
 
3
3
  import { annotate } from '../annotate';
4
- import { err, ok, orElse } from '../result';
4
+ import { err, ok } from '../result';
5
5
  import { indent, summarize } from '../_utils';
6
- import type { Decoder, Scalar } from '../_types';
6
+ import type { _Any } from '../_utils';
7
+ import type { Decoder, DecodeResult, Scalar } from '../_types';
8
+
9
+ const EITHER_PREFIX = 'Either:\n';
7
10
 
8
11
  /**
9
12
  * Indents and adds a dash in front of this (potentially multiline) string.
@@ -38,100 +41,51 @@ function itemize(s: string): string {
38
41
  *
39
42
  */
40
43
  function nest(errText: string): string {
41
- const EITHER_PREFIX = 'Either:\n';
42
44
  return errText.startsWith(EITHER_PREFIX)
43
45
  ? errText.substr(EITHER_PREFIX.length)
44
46
  : itemize(errText);
45
47
  }
46
48
 
47
- export function either<T1, T2>(d1: Decoder<T1>, d2: Decoder<T2>): Decoder<T1 | T2> {
48
- return (blob: mixed) =>
49
- orElse(d1(blob), (err1) =>
50
- orElse(d2(blob), (err2) => {
51
- const serr1 = summarize(err1).join('\n');
52
- const serr2 = summarize(err2).join('\n');
53
- const text = ['Either:', nest(serr1), nest(serr2)].join('\n');
54
- return err(annotate(blob, text));
55
- }),
56
- );
57
- }
58
-
59
- export function either3<T1, T2, T3>(
60
- d1: Decoder<T1>,
61
- d2: Decoder<T2>,
62
- d3: Decoder<T3>,
63
- ): Decoder<T1 | T2 | T3> {
64
- return either(d1, either(d2, d3));
49
+ // prettier-ignore
50
+ interface EitherDecoderSignatures {
51
+ <A>(a: Decoder<A>): Decoder<A>;
52
+ <A, B>(a: Decoder<A>, b: Decoder<B>): Decoder<A | B>;
53
+ <A, B, C>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>): Decoder<A | B | C>;
54
+ <A, B, C, D>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>): Decoder<A | B | C | D>;
55
+ <A, B, C, D, E>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>): Decoder<A | B | C | D | E>;
56
+ <A, B, C, D, E, F>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>): Decoder<A | B | C | D | E | F>;
57
+ <A, B, C, D, E, F, G>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>, g: Decoder<G>): Decoder<A | B | C | D | E | F | G>;
58
+ <A, B, C, D, E, F, G, H>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>, g: Decoder<G>, h: Decoder<H>): Decoder<A | B | C | D | E | F | G | H>;
59
+ <A, B, C, D, E, F, G, H, I>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>, g: Decoder<G>, h: Decoder<H>, i: Decoder<I>): Decoder<A | B | C | D | E | F | G | H | I>;
65
60
  }
66
61
 
67
- export function either4<T1, T2, T3, T4>(
68
- d1: Decoder<T1>,
69
- d2: Decoder<T2>,
70
- d3: Decoder<T3>,
71
- d4: Decoder<T4>,
72
- ): Decoder<T1 | T2 | T3 | T4> {
73
- return either(d1, either3(d2, d3, d4));
74
- }
75
-
76
- export function either5<T1, T2, T3, T4, T5>(
77
- d1: Decoder<T1>,
78
- d2: Decoder<T2>,
79
- d3: Decoder<T3>,
80
- d4: Decoder<T4>,
81
- d5: Decoder<T5>,
82
- ): Decoder<T1 | T2 | T3 | T4 | T5> {
83
- return either(d1, either4(d2, d3, d4, d5));
84
- }
62
+ function _either(...decoders: $ReadOnlyArray<Decoder<mixed>>): Decoder<mixed> {
63
+ if (decoders.length === 0) {
64
+ throw new Error('Pass at least one decoder to either()');
65
+ }
85
66
 
86
- export function either6<T1, T2, T3, T4, T5, T6>(
87
- d1: Decoder<T1>,
88
- d2: Decoder<T2>,
89
- d3: Decoder<T3>,
90
- d4: Decoder<T4>,
91
- d5: Decoder<T5>,
92
- d6: Decoder<T6>,
93
- ): Decoder<T1 | T2 | T3 | T4 | T5 | T6> {
94
- return either(d1, either5(d2, d3, d4, d5, d6));
95
- }
67
+ return (blob: mixed) => {
68
+ // Collect errors here along the way
69
+ const errors = [];
96
70
 
97
- export function either7<T1, T2, T3, T4, T5, T6, T7>(
98
- d1: Decoder<T1>,
99
- d2: Decoder<T2>,
100
- d3: Decoder<T3>,
101
- d4: Decoder<T4>,
102
- d5: Decoder<T5>,
103
- d6: Decoder<T6>,
104
- d7: Decoder<T7>,
105
- ): Decoder<T1 | T2 | T3 | T4 | T5 | T6 | T7> {
106
- return either(d1, either6(d2, d3, d4, d5, d6, d7));
107
- }
71
+ for (let i = 0; i < decoders.length; i++) {
72
+ const result: DecodeResult<mixed> = decoders[i](blob);
73
+ if (result.ok) {
74
+ return result;
75
+ } else {
76
+ errors.push(result.error);
77
+ }
78
+ }
108
79
 
109
- export function either8<T1, T2, T3, T4, T5, T6, T7, T8>(
110
- d1: Decoder<T1>,
111
- d2: Decoder<T2>,
112
- d3: Decoder<T3>,
113
- d4: Decoder<T4>,
114
- d5: Decoder<T5>,
115
- d6: Decoder<T6>,
116
- d7: Decoder<T7>,
117
- d8: Decoder<T8>,
118
- ): Decoder<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8> {
119
- return either(d1, either7(d2, d3, d4, d5, d6, d7, d8));
80
+ // Decoding all alternatives failed, return the combined error message
81
+ const text =
82
+ EITHER_PREFIX +
83
+ errors.map((err) => nest(summarize(err).join('\n'))).join('\n');
84
+ return err(annotate(blob, text));
85
+ };
120
86
  }
121
87
 
122
- export function either9<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
123
- d1: Decoder<T1>,
124
- d2: Decoder<T2>,
125
- d3: Decoder<T3>,
126
- d4: Decoder<T4>,
127
- d5: Decoder<T5>,
128
- d6: Decoder<T6>,
129
- d7: Decoder<T7>,
130
- d8: Decoder<T8>,
131
- d9: Decoder<T9>,
132
- ): Decoder<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9> {
133
- return either(d1, either8(d2, d3, d4, d5, d6, d7, d8, d9));
134
- }
88
+ export const either: EitherDecoderSignatures = (_either: _Any);
135
89
 
136
90
  export function oneOf<T: Scalar>(constants: $ReadOnlyArray<T>): Decoder<T> {
137
91
  return (blob: mixed) => {
package/core/either.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  import { annotate } from '../annotate.mjs';
2
- import { err, ok, orElse } from '../result.mjs';
2
+ import { err, ok } from '../result.mjs';
3
3
  import { indent, summarize } from '../_utils.mjs';
4
-
4
+ var EITHER_PREFIX = 'Either:\n';
5
5
  /**
6
6
  * Indents and adds a dash in front of this (potentially multiline) string.
7
7
  */
8
+
8
9
  function itemize(s) {
9
10
  return '-' + indent(s).substring(1);
10
11
  }
@@ -36,43 +37,42 @@ function itemize(s) {
36
37
 
37
38
 
38
39
  function nest(errText) {
39
- var EITHER_PREFIX = 'Either:\n';
40
40
  return errText.startsWith(EITHER_PREFIX) ? errText.substr(EITHER_PREFIX.length) : itemize(errText);
41
- }
41
+ } // prettier-ignore
42
+
43
+
44
+ function _either() {
45
+ for (var _len = arguments.length, decoders = new Array(_len), _key = 0; _key < _len; _key++) {
46
+ decoders[_key] = arguments[_key];
47
+ }
48
+
49
+ if (decoders.length === 0) {
50
+ throw new Error('Pass at least one decoder to either()');
51
+ }
42
52
 
43
- export function either(d1, d2) {
44
53
  return function (blob) {
45
- return orElse(d1(blob), function (err1) {
46
- return orElse(d2(blob), function (err2) {
47
- var serr1 = summarize(err1).join('\n');
48
- var serr2 = summarize(err2).join('\n');
49
- var text = ['Either:', nest(serr1), nest(serr2)].join('\n');
50
- return err(annotate(blob, text));
51
- });
52
- });
54
+ // Collect errors here along the way
55
+ var errors = [];
56
+
57
+ for (var _i = 0; _i < decoders.length; _i++) {
58
+ var result = decoders[_i](blob);
59
+
60
+ if (result.ok) {
61
+ return result;
62
+ } else {
63
+ errors.push(result.error);
64
+ }
65
+ } // Decoding all alternatives failed, return the combined error message
66
+
67
+
68
+ var text = EITHER_PREFIX + errors.map(function (err) {
69
+ return nest(summarize(err).join('\n'));
70
+ }).join('\n');
71
+ return err(annotate(blob, text));
53
72
  };
54
73
  }
55
- export function either3(d1, d2, d3) {
56
- return either(d1, either(d2, d3));
57
- }
58
- export function either4(d1, d2, d3, d4) {
59
- return either(d1, either3(d2, d3, d4));
60
- }
61
- export function either5(d1, d2, d3, d4, d5) {
62
- return either(d1, either4(d2, d3, d4, d5));
63
- }
64
- export function either6(d1, d2, d3, d4, d5, d6) {
65
- return either(d1, either5(d2, d3, d4, d5, d6));
66
- }
67
- export function either7(d1, d2, d3, d4, d5, d6, d7) {
68
- return either(d1, either6(d2, d3, d4, d5, d6, d7));
69
- }
70
- export function either8(d1, d2, d3, d4, d5, d6, d7, d8) {
71
- return either(d1, either7(d2, d3, d4, d5, d6, d7, d8));
72
- }
73
- export function either9(d1, d2, d3, d4, d5, d6, d7, d8, d9) {
74
- return either(d1, either8(d2, d3, d4, d5, d6, d7, d8, d9));
75
- }
74
+
75
+ export var either = _either;
76
76
  export function oneOf(constants) {
77
77
  return function (blob) {
78
78
  var winner = constants.find(function (c) {
@@ -1,3 +1,3 @@
1
1
  import { Decoder } from '../_types';
2
2
 
3
- export function instanceOf<T>(klass: new (...args: any) => T): Decoder<T>;
3
+ export function instanceOf<T>(klass: new (...args: readonly any[]) => T): Decoder<T>;
package/core/json.js CHANGED
@@ -7,7 +7,7 @@ var _array = require("./array");
7
7
 
8
8
  var _boolean2 = require("./boolean");
9
9
 
10
- var _mapping = require("./mapping");
10
+ var _object = require("./object");
11
11
 
12
12
  var _either = require("./either");
13
13
 
@@ -20,12 +20,12 @@ var _number = require("./number");
20
20
  var _string = require("./string");
21
21
 
22
22
  var jsonObject = (0, _lazy.lazy)(function () {
23
- return (0, _mapping.dict)(json);
23
+ return (0, _object.dict)(json);
24
24
  });
25
25
  exports.jsonObject = jsonObject;
26
26
  var jsonArray = (0, _lazy.lazy)(function () {
27
27
  return (0, _array.array)(json);
28
28
  });
29
29
  exports.jsonArray = jsonArray;
30
- var json = (0, _either.either6)(_constants.null_, _string.string, _number.number, _boolean2["boolean"], jsonObject, jsonArray);
30
+ var json = (0, _either.either)(_constants.null_, _string.string, _number.number, _boolean2["boolean"], jsonObject, jsonArray);
31
31
  exports.json = json;
package/core/json.js.flow CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { array } from './array';
4
4
  import { boolean } from './boolean';
5
- import { dict } from './mapping';
6
- import { either6 } from './either';
5
+ import { dict } from './object';
6
+ import { either } from './either';
7
7
  import { lazy } from './lazy';
8
8
  import { null_ } from './constants';
9
9
  import { number } from './number';
@@ -18,7 +18,7 @@ export const jsonObject: Decoder<JSONObject> = lazy(() => dict(json));
18
18
 
19
19
  export const jsonArray: Decoder<JSONArray> = lazy(() => array(json));
20
20
 
21
- export const json: Decoder<JSONValue> = either6(
21
+ export const json: Decoder<JSONValue> = either(
22
22
  null_,
23
23
  string,
24
24
  number,
package/core/json.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { array } from './array.mjs';
2
2
  import { boolean as _boolean } from './boolean.mjs';
3
- import { dict } from './mapping.mjs';
4
- import { either6 } from './either.mjs';
3
+ import { dict } from './object.mjs';
4
+ import { either } from './either.mjs';
5
5
  import { lazy } from './lazy.mjs';
6
6
  import { null_ } from './constants.mjs';
7
7
  import { number } from './number.mjs';
@@ -12,4 +12,4 @@ export var jsonObject = lazy(function () {
12
12
  export var jsonArray = lazy(function () {
13
13
  return array(json);
14
14
  });
15
- export var json = either6(null_, string, number, _boolean, jsonObject, jsonArray);
15
+ export var json = either(null_, string, number, _boolean, jsonObject, jsonArray);
package/core/object.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /// <reference lib="es6" />
2
+
1
3
  import { Decoder, DecoderType } from '../_types';
2
4
  import { AllowImplicit } from './_helpers';
3
5
 
@@ -31,3 +33,6 @@ export function inexact<O extends { [key: string]: Decoder<any> }>(
31
33
  [extra: string]: unknown;
32
34
  }
33
35
  >;
36
+
37
+ export function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>>;
38
+ export function dict<T>(decoder: Decoder<T>): Decoder<{ [key: string]: T }>;
package/core/object.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
+ exports.dict = dict;
4
5
  exports.exact = exact;
5
6
  exports.inexact = inexact;
7
+ exports.mapping = mapping;
6
8
  exports.object = object;
7
9
  exports.pojo = void 0;
8
10
 
@@ -87,7 +89,7 @@ function object(mapping) {
87
89
  var rawValue = blob[key];
88
90
  var result = decoder(rawValue);
89
91
 
90
- if (result.type === 'ok') {
92
+ if (result.ok) {
91
93
  var value = result.value;
92
94
 
93
95
  if (value !== undefined) {
@@ -165,7 +167,7 @@ function exact(mapping) {
165
167
  function inexact(mapping) {
166
168
  return (0, _composition.compose)(pojo, function (blob) {
167
169
  var allkeys = new Set(Object.keys(blob));
168
- var decoder = (0, _composition.map)(object(mapping), function (safepart) {
170
+ var decoder = (0, _composition.transform)(object(mapping), function (safepart) {
169
171
  var safekeys = new Set(Object.keys(mapping)); // To account for hard-coded keys that aren't part of the input
170
172
 
171
173
  safekeys.forEach(function (k) {
@@ -187,4 +189,59 @@ function inexact(mapping) {
187
189
  });
188
190
  return decoder(blob);
189
191
  });
192
+ }
193
+ /**
194
+ * Like mapping(), but returns an object rather than a Map instance.
195
+ */
196
+
197
+
198
+ function dict(decoder) {
199
+ return (0, _composition.compose)(pojo, function (blob) {
200
+ var rv = {};
201
+ var errors = null;
202
+ Object.keys(blob).forEach(function (key) {
203
+ var value = blob[key];
204
+ var result = decoder(value);
205
+
206
+ if (result.ok) {
207
+ if (errors === null) {
208
+ rv[key] = result.value;
209
+ }
210
+ } else {
211
+ rv = {}; // Clear the success value so it can get garbage collected early
212
+
213
+ if (errors === null) {
214
+ errors = {};
215
+ }
216
+
217
+ errors[key] = result.error;
218
+ }
219
+ });
220
+
221
+ if (errors !== null) {
222
+ return (0, _result.err)((0, _annotate.merge)((0, _annotate.annotateObject)(blob), errors));
223
+ } else {
224
+ return (0, _result.ok)(rv);
225
+ }
226
+ });
227
+ }
228
+ /**
229
+ * Given an object, will decode a Map of string keys to whatever values.
230
+ *
231
+ * For example, given a decoder for a Person, we can verify a Person lookup
232
+ * table structure (of type Map<string, Person>) like so:
233
+ *
234
+ * mapping(person)
235
+ *
236
+ */
237
+
238
+
239
+ function mapping(decoder) {
240
+ return (0, _composition.transform)(dict(decoder), function (obj) {
241
+ return new Map( // This is effectively Object.entries(obj), but in a way that Flow
242
+ // will know the types are okay
243
+ Object.keys(obj).map(function (key) {
244
+ return [key, obj[key]];
245
+ }));
246
+ });
190
247
  }
@@ -1,10 +1,10 @@
1
1
  // @flow strict
2
2
 
3
3
  import { annotate, annotateObject, merge, updateText } from '../annotate';
4
- import { compose, map } from './composition';
4
+ import { compose, transform } from './composition';
5
5
  import { err, ok } from '../result';
6
6
  import type { Annotation } from '../annotate';
7
- import type { Decoder, DecoderType } from '../_types';
7
+ import type { Decoder, DecodeResult, DecoderType } from '../_types';
8
8
 
9
9
  // $FlowFixMe[unclear-type] (not really an issue) - deliberate use of `any` - not sure how we should get rid of this
10
10
  type AnyDecoder = any;
@@ -94,9 +94,9 @@ export function object<O: { +[field: string]: AnyDecoder, ... }>(
94
94
  Object.keys(mapping).forEach((key) => {
95
95
  const decoder = mapping[key];
96
96
  const rawValue = blob[key];
97
- const result = decoder(rawValue);
97
+ const result: DecodeResult<mixed> = decoder(rawValue);
98
98
 
99
- if (result.type === 'ok') {
99
+ if (result.ok) {
100
100
  const value = result.value;
101
101
  if (value !== undefined) {
102
102
  record[key] = value;
@@ -179,25 +179,81 @@ export function inexact<O: { +[field: string]: AnyDecoder }>(
179
179
  ): Decoder<$ObjMap<O, DecoderType> & { +[string]: mixed }> {
180
180
  return compose(pojo, (blob: {| [string]: mixed |}) => {
181
181
  const allkeys = new Set(Object.keys(blob));
182
- const decoder = map(object(mapping), (safepart: $ObjMap<O, DecoderType>) => {
183
- const safekeys = new Set(Object.keys(mapping));
184
-
185
- // To account for hard-coded keys that aren't part of the input
186
- safekeys.forEach((k) => allkeys.add(k));
187
-
188
- const rv = {};
189
- allkeys.forEach((k) => {
190
- if (safekeys.has(k)) {
191
- const value = safepart[k];
192
- if (value !== undefined) {
193
- rv[k] = value;
182
+ const decoder = transform(
183
+ object(mapping),
184
+ (safepart: $ObjMap<O, DecoderType>) => {
185
+ const safekeys = new Set(Object.keys(mapping));
186
+
187
+ // To account for hard-coded keys that aren't part of the input
188
+ safekeys.forEach((k) => allkeys.add(k));
189
+
190
+ const rv = {};
191
+ allkeys.forEach((k) => {
192
+ if (safekeys.has(k)) {
193
+ const value = safepart[k];
194
+ if (value !== undefined) {
195
+ rv[k] = value;
196
+ }
197
+ } else {
198
+ rv[k] = blob[k];
194
199
  }
195
- } else {
196
- rv[k] = blob[k];
200
+ });
201
+ return rv;
202
+ },
203
+ );
204
+ return decoder(blob);
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Like mapping(), but returns an object rather than a Map instance.
210
+ */
211
+ export function dict<T>(decoder: Decoder<T>): Decoder<{ [string]: T }> {
212
+ return compose(pojo, (blob: { +[key: string]: mixed }) => {
213
+ let rv: { [key: string]: T } = {};
214
+ let errors: { [key: string]: Annotation } | null = null;
215
+
216
+ Object.keys(blob).forEach((key: string) => {
217
+ const value = blob[key];
218
+ const result = decoder(value);
219
+ if (result.ok) {
220
+ if (errors === null) {
221
+ rv[key] = result.value;
222
+ }
223
+ } else {
224
+ rv = {}; // Clear the success value so it can get garbage collected early
225
+ if (errors === null) {
226
+ errors = {};
197
227
  }
198
- });
199
- return rv;
228
+ errors[key] = result.error;
229
+ }
200
230
  });
201
- return decoder(blob);
231
+
232
+ if (errors !== null) {
233
+ return err(merge(annotateObject(blob), errors));
234
+ } else {
235
+ return ok(rv);
236
+ }
202
237
  });
203
238
  }
239
+
240
+ /**
241
+ * Given an object, will decode a Map of string keys to whatever values.
242
+ *
243
+ * For example, given a decoder for a Person, we can verify a Person lookup
244
+ * table structure (of type Map<string, Person>) like so:
245
+ *
246
+ * mapping(person)
247
+ *
248
+ */
249
+ export function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>> {
250
+ return transform(
251
+ dict(decoder),
252
+ (obj) =>
253
+ new Map(
254
+ // This is effectively Object.entries(obj), but in a way that Flow
255
+ // will know the types are okay
256
+ Object.keys(obj).map((key) => [key, obj[key]]),
257
+ ),
258
+ );
259
+ }