decoders 2.0.0-beta1 → 2.0.0-beta10
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 +30 -0
- package/Decoder.d.ts +27 -0
- package/Decoder.js +199 -0
- package/Decoder.js.flow +224 -0
- package/Decoder.mjs +192 -0
- package/NotSupportedTSVersion.d.ts +1 -0
- package/README.md +105 -961
- package/_utils.d.ts +9 -0
- package/{cjs/_utils.js → _utils.js} +12 -18
- package/{cjs/_utils.js.flow → _utils.js.flow} +15 -19
- package/{es/_utils.js → _utils.mjs} +11 -15
- package/{ts/annotate.d.ts → annotate.d.ts} +25 -21
- package/{cjs/annotate.js → annotate.js} +0 -0
- package/{cjs/annotate.js.flow → annotate.js.flow} +0 -0
- package/{es/annotate.js → annotate.mjs} +0 -0
- package/format.d.ts +4 -0
- package/{cjs/format/inline.js → format.js} +7 -2
- package/{cjs/format/inline.js.flow → format.js.flow} +7 -3
- package/{es/format/inline.js → format.mjs} +5 -2
- package/index.d.ts +40 -0
- package/index.js +90 -0
- package/index.js.flow +44 -0
- package/index.mjs +11 -0
- package/{ts → lib}/_helpers.d.ts +0 -0
- package/lib/arrays.d.ts +37 -0
- package/lib/arrays.js +138 -0
- package/lib/arrays.js.flow +138 -0
- package/lib/arrays.mjs +123 -0
- package/lib/basics.d.ts +14 -0
- package/lib/basics.js +109 -0
- package/lib/basics.js.flow +85 -0
- package/lib/basics.mjs +85 -0
- package/{ts/boolean.d.ts → lib/booleans.d.ts} +1 -1
- package/lib/booleans.js +35 -0
- package/lib/booleans.js.flow +22 -0
- package/lib/booleans.mjs +25 -0
- package/{ts/date.d.ts → lib/dates.d.ts} +1 -1
- package/lib/dates.js +44 -0
- package/lib/dates.js.flow +40 -0
- package/lib/dates.mjs +34 -0
- package/{ts → lib}/json.d.ts +1 -1
- package/lib/json.js +55 -0
- package/lib/json.js.flow +50 -0
- package/lib/json.mjs +40 -0
- package/{ts/number.d.ts → lib/numbers.d.ts} +2 -1
- package/lib/numbers.js +52 -0
- package/lib/numbers.js.flow +49 -0
- package/lib/numbers.mjs +42 -0
- package/{ts/object.d.ts → lib/objects.d.ts} +9 -4
- package/lib/objects.js +240 -0
- package/lib/objects.js.flow +246 -0
- package/lib/objects.mjs +223 -0
- package/lib/strings.d.ts +13 -0
- package/lib/strings.js +101 -0
- package/lib/strings.js.flow +90 -0
- package/lib/strings.mjs +82 -0
- package/lib/unions.d.ts +78 -0
- package/lib/unions.js +161 -0
- package/lib/unions.js.flow +158 -0
- package/lib/unions.mjs +145 -0
- package/lib/utilities.d.ts +10 -0
- package/lib/utilities.js +94 -0
- package/lib/utilities.js.flow +84 -0
- package/lib/utilities.mjs +79 -0
- package/package.json +79 -29
- package/result.d.ts +16 -0
- package/result.js +34 -0
- package/result.js.flow +26 -0
- package/result.mjs +27 -0
- package/cjs/_guard.js +0 -26
- package/cjs/_guard.js.flow +0 -20
- package/cjs/_types.js +0 -1
- package/cjs/_types.js.flow +0 -20
- package/cjs/format/index.js +0 -12
- package/cjs/format/index.js.flow +0 -4
- package/cjs/format/short.js +0 -10
- package/cjs/format/short.js.flow +0 -8
- package/cjs/index.js +0 -120
- package/cjs/index.js.flow +0 -63
- package/cjs/result.js +0 -172
- package/cjs/result.js.flow +0 -166
- package/cjs/stdlib/array.js +0 -108
- package/cjs/stdlib/array.js.flow +0 -103
- package/cjs/stdlib/boolean.js +0 -44
- package/cjs/stdlib/boolean.js.flow +0 -29
- package/cjs/stdlib/composition.js +0 -56
- package/cjs/stdlib/composition.js.flow +0 -43
- package/cjs/stdlib/constants.js +0 -69
- package/cjs/stdlib/constants.js.flow +0 -46
- package/cjs/stdlib/date.js +0 -46
- package/cjs/stdlib/date.js.flow +0 -40
- package/cjs/stdlib/describe.js +0 -26
- package/cjs/stdlib/describe.js.flow +0 -17
- package/cjs/stdlib/dispatch.js +0 -62
- package/cjs/stdlib/dispatch.js.flow +0 -58
- package/cjs/stdlib/either.js +0 -117
- package/cjs/stdlib/either.js.flow +0 -151
- package/cjs/stdlib/fail.js +0 -21
- package/cjs/stdlib/fail.js.flow +0 -12
- package/cjs/stdlib/instanceOf.js +0 -19
- package/cjs/stdlib/instanceOf.js.flow +0 -20
- package/cjs/stdlib/json.js +0 -31
- package/cjs/stdlib/json.js.flow +0 -28
- package/cjs/stdlib/lazy.js +0 -16
- package/cjs/stdlib/lazy.js.flow +0 -15
- package/cjs/stdlib/mapping.js +0 -67
- package/cjs/stdlib/mapping.js.flow +0 -54
- package/cjs/stdlib/number.js +0 -40
- package/cjs/stdlib/number.js.flow +0 -34
- package/cjs/stdlib/object.js +0 -194
- package/cjs/stdlib/object.js.flow +0 -203
- package/cjs/stdlib/optional.js +0 -54
- package/cjs/stdlib/optional.js.flow +0 -41
- package/cjs/stdlib/string.js +0 -98
- package/cjs/stdlib/string.js.flow +0 -82
- package/cjs/stdlib/tuple.js +0 -173
- package/cjs/stdlib/tuple.js.flow +0 -220
- package/es/_guard.js +0 -15
- package/es/_types.js +0 -0
- package/es/format/index.js +0 -2
- package/es/format/short.js +0 -4
- package/es/index.js +0 -37
- package/es/result.js +0 -139
- package/es/stdlib/array.js +0 -91
- package/es/stdlib/boolean.js +0 -28
- package/es/stdlib/composition.js +0 -42
- package/es/stdlib/constants.js +0 -46
- package/es/stdlib/date.js +0 -28
- package/es/stdlib/describe.js +0 -16
- package/es/stdlib/dispatch.js +0 -51
- package/es/stdlib/either.js +0 -90
- package/es/stdlib/fail.js +0 -11
- package/es/stdlib/instanceOf.js +0 -8
- package/es/stdlib/json.js +0 -15
- package/es/stdlib/lazy.js +0 -11
- package/es/stdlib/mapping.js +0 -54
- package/es/stdlib/number.js +0 -25
- package/es/stdlib/object.js +0 -175
- package/es/stdlib/optional.js +0 -38
- package/es/stdlib/string.js +0 -76
- package/es/stdlib/tuple.js +0 -155
- package/ts/_guard.d.ts +0 -7
- package/ts/_types.d.ts +0 -16
- package/ts/_utils.d.ts +0 -13
- package/ts/array.d.ts +0 -5
- package/ts/constants.d.ts +0 -11
- package/ts/describe.d.ts +0 -3
- package/ts/dispatch.d.ts +0 -8
- package/ts/either.d.ts +0 -61
- package/ts/fail.d.ts +0 -3
- package/ts/index.d.ts +0 -42
- package/ts/inline.d.ts +0 -3
- package/ts/instanceOf.d.ts +0 -3
- package/ts/lazy.d.ts +0 -3
- package/ts/mapping.d.ts +0 -4
- package/ts/optional.d.ts +0 -5
- package/ts/result.d.ts +0 -39
- package/ts/short.d.ts +0 -3
- package/ts/string.d.ts +0 -7
- package/ts/tuple.d.ts +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,45 @@
|
|
|
1
1
|
## v2.0.0-beta
|
|
2
2
|
|
|
3
|
+
This is a breaking change, which brings numerous benefits including speed, bundle size,
|
|
4
|
+
and simplicity. Please see the [migration guide](./MIGRATING-v2.md) for instructions on
|
|
5
|
+
how to adjust your code.
|
|
6
|
+
|
|
3
7
|
Potentially breaking changes:
|
|
4
8
|
|
|
5
9
|
- Drop support for all Node versions below 12.x
|
|
6
10
|
- Drop support for Flow versions below 0.142.0
|
|
11
|
+
- Drop support for TypeScript versions below 4.1.0
|
|
7
12
|
- Drop all package dependencies
|
|
13
|
+
- Removed decoders:
|
|
14
|
+
- `guard()` no longer exists, see
|
|
15
|
+
[migration instructions](./MIGRATING-v2.md#guards-are-no-longer-a-thing)
|
|
16
|
+
- `eitherN()` (now simply `either()`, see
|
|
17
|
+
[migration instructions](./MIGRATING-v2.md#eitherN-is-now-simply-either))
|
|
18
|
+
- `tupleN()` (now simply `tuple()`, see
|
|
19
|
+
[migration instructions](./MIGRATING-v2.md#tupleN-is-now-simply-tuple))
|
|
20
|
+
- Renamed decoders:
|
|
21
|
+
- `map()` → `transform()` - see
|
|
22
|
+
[migration instructions](./MIGRATING-v2.md#map-is-now-transform)
|
|
23
|
+
- `dispatch()` → `taggedUnion()` - see
|
|
24
|
+
[migration instructions](./MIGRATING-v2.md#dispatch-is-now-taggedUnion)
|
|
25
|
+
- Decoders that have changed:
|
|
26
|
+
- API of `predicate()` has changed - see
|
|
27
|
+
[migration instructions](./MIGRATING-v2.md#predicate-is-now-a-first-class-citizen)
|
|
28
|
+
- API of `url` decoder has changed - see
|
|
29
|
+
[migration instructions](./MIGRATING-v2.md#url-decoder-has-changed)
|
|
8
30
|
|
|
9
31
|
New features:
|
|
10
32
|
|
|
11
33
|
- Include ES modules in published NPM builds (yay tree-shaking! 🍃)
|
|
12
34
|
- Much smaller total bundle size
|
|
35
|
+
- New decoders:
|
|
36
|
+
- [`always`](https://nvie.com/decoders/api#always)
|
|
37
|
+
- [`never`](https://nvie.com/decoders/api#never)
|
|
38
|
+
- [`prep()`](https://nvie.com/decoders/api#prep)
|
|
39
|
+
- [`set()`](https://nvie.com/decoders/api#set)
|
|
40
|
+
- [`uuidv1`](https://nvie.com/decoders/api#uuidv1)
|
|
41
|
+
- [`uuidv4`](https://nvie.com/decoders/api#uuidv4)
|
|
42
|
+
- [`uuid`](https://nvie.com/decoders/api#uuid)
|
|
13
43
|
- Better error messages for nested `either`s
|
|
14
44
|
- Guard API now has a simpler way to specify formatters
|
|
15
45
|
|
package/Decoder.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Annotation } from './annotate';
|
|
2
|
+
import { Result } from './result';
|
|
3
|
+
|
|
4
|
+
export type Scalar = string | number | boolean | symbol | undefined | null;
|
|
5
|
+
|
|
6
|
+
export type DecodeResult<T> = Result<T, Annotation>;
|
|
7
|
+
export type DecodeFn<T, I = unknown> = (
|
|
8
|
+
blob: I,
|
|
9
|
+
ok: (value: T) => DecodeResult<T>,
|
|
10
|
+
err: (msg: string | Annotation) => DecodeResult<T>,
|
|
11
|
+
) => DecodeResult<T>;
|
|
12
|
+
|
|
13
|
+
export interface Decoder<T> {
|
|
14
|
+
decode(blob: unknown): DecodeResult<T>;
|
|
15
|
+
verify(blob: unknown, formatterFn?: (ann: Annotation) => string): T;
|
|
16
|
+
refine<N extends T>(predicate: (value: T) => value is N, msg: string): Decoder<N>;
|
|
17
|
+
refine(predicate: (value: T) => boolean, msg: string): Decoder<T>;
|
|
18
|
+
reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>;
|
|
19
|
+
transform<V>(transformFn: (value: T) => V): Decoder<V>;
|
|
20
|
+
describe(message: string): Decoder<T>;
|
|
21
|
+
then<V>(nextDecodeFn: DecodeFn<V, T>): Decoder<V>;
|
|
22
|
+
peek<V>(nextDecodeFn: DecodeFn<V, [unknown, T]>): Decoder<V>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type DecoderType<T> = T extends Decoder<infer V> ? V : never;
|
|
26
|
+
|
|
27
|
+
export function define<T>(fn: DecodeFn<T>): Decoder<T>;
|
package/Decoder.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.define = define;
|
|
5
|
+
|
|
6
|
+
var _annotate = require("./annotate");
|
|
7
|
+
|
|
8
|
+
var _format = require("./format");
|
|
9
|
+
|
|
10
|
+
var _result = require("./result");
|
|
11
|
+
|
|
12
|
+
function noThrow(fn) {
|
|
13
|
+
return function (t) {
|
|
14
|
+
try {
|
|
15
|
+
var v = fn(t);
|
|
16
|
+
return (0, _result.ok)(v);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return (0, _result.err)((0, _annotate.annotate)(t, e instanceof Error ? e.message : String(e)));
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Defines a new `Decoder<T>`, by implementing a custom acceptance function.
|
|
24
|
+
* The function receives three arguments:
|
|
25
|
+
*
|
|
26
|
+
* 1. `blob` - the raw/unknown input (aka your external data)
|
|
27
|
+
* 2. `ok` - Call `ok(value)` to accept the input and return `value`
|
|
28
|
+
* 3. `err` - Call `err(message)` to reject the input and use "message" in the
|
|
29
|
+
* annotation
|
|
30
|
+
*
|
|
31
|
+
* The expected return value should be a `DecodeResult<T>`, which can be
|
|
32
|
+
* obtained by returning the result from the provided `ok` or `err` helper
|
|
33
|
+
* functions.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
function define(decodeFn) {
|
|
38
|
+
/**
|
|
39
|
+
* Validates the raw/untrusted/unknown input and either accepts or rejects
|
|
40
|
+
* it.
|
|
41
|
+
*
|
|
42
|
+
* Contrasted with `.verify()`, calls to `.decode()` will never fail and
|
|
43
|
+
* instead return a result type.
|
|
44
|
+
*/
|
|
45
|
+
function decode(blob) {
|
|
46
|
+
return decodeFn(blob, _result.ok, function (msg) {
|
|
47
|
+
return (0, _result.err)(typeof msg === 'string' ? (0, _annotate.annotate)(blob, msg) : msg);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Verified the (raw/untrusted/unknown) input and either accepts or rejects
|
|
52
|
+
* it. When accepted, returns the decoded `T` value directly. Otherwise
|
|
53
|
+
* fail with a runtime error.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
function verify(blob, formatter) {
|
|
58
|
+
if (formatter === void 0) {
|
|
59
|
+
formatter = _format.formatInline;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var result = decode(blob);
|
|
63
|
+
|
|
64
|
+
if (result.ok) {
|
|
65
|
+
return result.value;
|
|
66
|
+
} else {
|
|
67
|
+
var _err = new Error('\n' + formatter(result.error));
|
|
68
|
+
|
|
69
|
+
_err.name = 'Decoding error';
|
|
70
|
+
throw _err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Accepts any value the given decoder accepts, and on success, will call
|
|
75
|
+
* the given function **on the decoded result**. If the transformation
|
|
76
|
+
* function throws an error, the whole decoder will fail using the error
|
|
77
|
+
* message as the failure reason.
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
function transform(transformFn) {
|
|
82
|
+
return then(noThrow(transformFn));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Adds an extra predicate to a decoder. The new decoder is like the
|
|
86
|
+
* original decoder, but only accepts values that also meet the
|
|
87
|
+
* predicate.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
function refine(predicateFn, errmsg) {
|
|
92
|
+
return reject(function (value) {
|
|
93
|
+
return predicateFn(value) ? // Don't reject
|
|
94
|
+
null : // Reject with the given error message
|
|
95
|
+
errmsg;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Chain together the current decoder with another.
|
|
100
|
+
*
|
|
101
|
+
* First, the current decoder must accept the input. If so, it will pass
|
|
102
|
+
* the successfully decoded result to the given ``next`` function to
|
|
103
|
+
* further decide whether or not the value should get accepted or rejected.
|
|
104
|
+
*
|
|
105
|
+
* The argument to `.then()` is a decoding function, just like one you
|
|
106
|
+
* would pass to `define()`. The key difference with `define()` is that
|
|
107
|
+
* `define()` must always assume an ``unknown`` input, whereas with
|
|
108
|
+
* a `.then()` call the provided ``next`` function will receive a ``T`` as
|
|
109
|
+
* its input. This will allow the function to make a stronger assumption
|
|
110
|
+
* about its input.
|
|
111
|
+
*
|
|
112
|
+
* If it helps, you can think of `define(nextFn)` as equivalent to
|
|
113
|
+
* `unknown.then(nextFn)`.
|
|
114
|
+
*
|
|
115
|
+
* This is an advanced, low-level, decoder. It's not recommended to reach
|
|
116
|
+
* for this low-level construct when implementing custom decoders. Most
|
|
117
|
+
* cases can be covered by `.transform()` or `.refine()`.
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
function then(next) {
|
|
122
|
+
return define(function (blob, ok, err) {
|
|
123
|
+
var result = decode(blob);
|
|
124
|
+
return result.ok ? next(result.value, ok, err) : result;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Adds an extra predicate to a decoder. The new decoder is like the
|
|
129
|
+
* original decoder, but only accepts values that aren't rejected by the
|
|
130
|
+
* given function.
|
|
131
|
+
*
|
|
132
|
+
* The given function can return `null` to accept the decoded value, or
|
|
133
|
+
* return a specific error message to reject.
|
|
134
|
+
*
|
|
135
|
+
* Unlike `.refine()`, you can use this function to return a dynamic error
|
|
136
|
+
* message.
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
function reject(rejectFn) {
|
|
141
|
+
return then(function (value, ok, err) {
|
|
142
|
+
var errmsg = rejectFn(value);
|
|
143
|
+
return errmsg === null ? ok(value) : err(typeof errmsg === 'string' ? (0, _annotate.annotate)(value, errmsg) : errmsg);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Uses the given decoder, but will use an alternative error message in
|
|
148
|
+
* case it rejects. This can be used to simplify or shorten otherwise
|
|
149
|
+
* long or low-level/technical errors.
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
function describe(message) {
|
|
154
|
+
return define(function (blob, _, err) {
|
|
155
|
+
// Decode using the given decoder...
|
|
156
|
+
var result = decode(blob);
|
|
157
|
+
|
|
158
|
+
if (result.ok) {
|
|
159
|
+
return result;
|
|
160
|
+
} else {
|
|
161
|
+
// ...but in case of error, annotate this with the custom given
|
|
162
|
+
// message instead
|
|
163
|
+
return err((0, _annotate.annotate)(result.error, message));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* WARNING: This is an EXPERIMENTAL API that will likely change in the
|
|
169
|
+
* future. Please DO NOT rely on it.
|
|
170
|
+
*
|
|
171
|
+
* Chain together the current decoder with another, but also pass along
|
|
172
|
+
* the original input.
|
|
173
|
+
*
|
|
174
|
+
* This is like `.then()`, but instead of this function receiving just
|
|
175
|
+
* the decoded result ``T``, it also receives the original input.
|
|
176
|
+
*
|
|
177
|
+
* This is an advanced, low-level, decoder.
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
function peek_UNSTABLE(next) {
|
|
182
|
+
return define(function (blob, ok, err) {
|
|
183
|
+
var result = decode(blob);
|
|
184
|
+
return result.ok ? next([blob, result.value], ok, err) : result;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return Object.freeze({
|
|
189
|
+
verify: verify,
|
|
190
|
+
decode: decode,
|
|
191
|
+
transform: transform,
|
|
192
|
+
refine: refine,
|
|
193
|
+
reject: reject,
|
|
194
|
+
describe: describe,
|
|
195
|
+
then: then,
|
|
196
|
+
// EXPERIMENTAL - please DO NOT rely on this method
|
|
197
|
+
peek_UNSTABLE: peek_UNSTABLE
|
|
198
|
+
});
|
|
199
|
+
}
|
package/Decoder.js.flow
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
|
|
3
|
+
import { annotate } from './annotate';
|
|
4
|
+
import { formatInline } from './format';
|
|
5
|
+
import { err as makeErr, ok as makeOk } from './result';
|
|
6
|
+
import type { Annotation } from './annotate';
|
|
7
|
+
import type { Result } from './result';
|
|
8
|
+
|
|
9
|
+
export type Scalar = string | number | boolean | symbol | void | null;
|
|
10
|
+
|
|
11
|
+
export type DecodeResult<T> = Result<T, Annotation>;
|
|
12
|
+
export type DecodeFn<T, I = mixed> = (
|
|
13
|
+
blob: I,
|
|
14
|
+
ok: (value: T) => DecodeResult<T>,
|
|
15
|
+
err: (msg: string | Annotation) => DecodeResult<T>,
|
|
16
|
+
) => DecodeResult<T>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Helper type to return the "type" of a Decoder.
|
|
20
|
+
*
|
|
21
|
+
* You can use it on types:
|
|
22
|
+
*
|
|
23
|
+
* DecoderType<Decoder<string>> // => string
|
|
24
|
+
* DecoderType<Decoder<number[]>> // => number[]
|
|
25
|
+
*
|
|
26
|
+
* Or on "values", by using the `typeof` keyword:
|
|
27
|
+
*
|
|
28
|
+
* DecoderType<typeof array(string)> // => string[]
|
|
29
|
+
* DecoderType<typeof truthy> // => boolean
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
export type DecoderType<D> = $Call<<T>(Decoder<T>) => T, D>;
|
|
33
|
+
|
|
34
|
+
export type Decoder<T> = {|
|
|
35
|
+
decode(blob: mixed): DecodeResult<T>,
|
|
36
|
+
verify(blob: mixed, formatterFn?: (Annotation) => string): T,
|
|
37
|
+
refine(predicateFn: (value: T) => boolean, errmsg: string): Decoder<T>,
|
|
38
|
+
reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>,
|
|
39
|
+
transform<V>(transformFn: (value: T) => V): Decoder<V>,
|
|
40
|
+
describe(message: string): Decoder<T>,
|
|
41
|
+
then<V>(next: DecodeFn<V, T>): Decoder<V>,
|
|
42
|
+
|
|
43
|
+
// Experimental APIs (please don't rely on these yet)
|
|
44
|
+
peek_UNSTABLE<V>(next: DecodeFn<V, [mixed, T]>): Decoder<V>,
|
|
45
|
+
|};
|
|
46
|
+
|
|
47
|
+
function noThrow<T, V>(fn: (value: T) => V): (T) => DecodeResult<V> {
|
|
48
|
+
return (t) => {
|
|
49
|
+
try {
|
|
50
|
+
const v = fn(t);
|
|
51
|
+
return makeOk(v);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return makeErr(annotate(t, e instanceof Error ? e.message : String(e)));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Defines a new `Decoder<T>`, by implementing a custom acceptance function.
|
|
60
|
+
* The function receives three arguments:
|
|
61
|
+
*
|
|
62
|
+
* 1. `blob` - the raw/unknown input (aka your external data)
|
|
63
|
+
* 2. `ok` - Call `ok(value)` to accept the input and return `value`
|
|
64
|
+
* 3. `err` - Call `err(message)` to reject the input and use "message" in the
|
|
65
|
+
* annotation
|
|
66
|
+
*
|
|
67
|
+
* The expected return value should be a `DecodeResult<T>`, which can be
|
|
68
|
+
* obtained by returning the result from the provided `ok` or `err` helper
|
|
69
|
+
* functions.
|
|
70
|
+
*/
|
|
71
|
+
export function define<T>(decodeFn: DecodeFn<T>): Decoder<T> {
|
|
72
|
+
/**
|
|
73
|
+
* Validates the raw/untrusted/unknown input and either accepts or rejects
|
|
74
|
+
* it.
|
|
75
|
+
*
|
|
76
|
+
* Contrasted with `.verify()`, calls to `.decode()` will never fail and
|
|
77
|
+
* instead return a result type.
|
|
78
|
+
*/
|
|
79
|
+
function decode(blob: mixed): DecodeResult<T> {
|
|
80
|
+
return decodeFn(blob, makeOk, (msg: Annotation | string) =>
|
|
81
|
+
makeErr(typeof msg === 'string' ? annotate(blob, msg) : msg),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Verified the (raw/untrusted/unknown) input and either accepts or rejects
|
|
87
|
+
* it. When accepted, returns the decoded `T` value directly. Otherwise
|
|
88
|
+
* fail with a runtime error.
|
|
89
|
+
*/
|
|
90
|
+
function verify(blob: mixed, formatter: (Annotation) => string = formatInline): T {
|
|
91
|
+
const result = decode(blob);
|
|
92
|
+
if (result.ok) {
|
|
93
|
+
return result.value;
|
|
94
|
+
} else {
|
|
95
|
+
const err = new Error('\n' + formatter(result.error));
|
|
96
|
+
err.name = 'Decoding error';
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Accepts any value the given decoder accepts, and on success, will call
|
|
103
|
+
* the given function **on the decoded result**. If the transformation
|
|
104
|
+
* function throws an error, the whole decoder will fail using the error
|
|
105
|
+
* message as the failure reason.
|
|
106
|
+
*/
|
|
107
|
+
function transform<V>(transformFn: (T) => V): Decoder<V> {
|
|
108
|
+
return then(noThrow(transformFn));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Adds an extra predicate to a decoder. The new decoder is like the
|
|
113
|
+
* original decoder, but only accepts values that also meet the
|
|
114
|
+
* predicate.
|
|
115
|
+
*/
|
|
116
|
+
function refine(predicateFn: (value: T) => boolean, errmsg: string): Decoder<T> {
|
|
117
|
+
return reject((value) =>
|
|
118
|
+
predicateFn(value)
|
|
119
|
+
? // Don't reject
|
|
120
|
+
null
|
|
121
|
+
: // Reject with the given error message
|
|
122
|
+
errmsg,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Chain together the current decoder with another.
|
|
128
|
+
*
|
|
129
|
+
* First, the current decoder must accept the input. If so, it will pass
|
|
130
|
+
* the successfully decoded result to the given ``next`` function to
|
|
131
|
+
* further decide whether or not the value should get accepted or rejected.
|
|
132
|
+
*
|
|
133
|
+
* The argument to `.then()` is a decoding function, just like one you
|
|
134
|
+
* would pass to `define()`. The key difference with `define()` is that
|
|
135
|
+
* `define()` must always assume an ``unknown`` input, whereas with
|
|
136
|
+
* a `.then()` call the provided ``next`` function will receive a ``T`` as
|
|
137
|
+
* its input. This will allow the function to make a stronger assumption
|
|
138
|
+
* about its input.
|
|
139
|
+
*
|
|
140
|
+
* If it helps, you can think of `define(nextFn)` as equivalent to
|
|
141
|
+
* `unknown.then(nextFn)`.
|
|
142
|
+
*
|
|
143
|
+
* This is an advanced, low-level, decoder. It's not recommended to reach
|
|
144
|
+
* for this low-level construct when implementing custom decoders. Most
|
|
145
|
+
* cases can be covered by `.transform()` or `.refine()`.
|
|
146
|
+
*/
|
|
147
|
+
function then<V>(next: DecodeFn<V, T>): Decoder<V> {
|
|
148
|
+
return define((blob, ok, err) => {
|
|
149
|
+
const result = decode(blob);
|
|
150
|
+
return result.ok ? next(result.value, ok, err) : result;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Adds an extra predicate to a decoder. The new decoder is like the
|
|
156
|
+
* original decoder, but only accepts values that aren't rejected by the
|
|
157
|
+
* given function.
|
|
158
|
+
*
|
|
159
|
+
* The given function can return `null` to accept the decoded value, or
|
|
160
|
+
* return a specific error message to reject.
|
|
161
|
+
*
|
|
162
|
+
* Unlike `.refine()`, you can use this function to return a dynamic error
|
|
163
|
+
* message.
|
|
164
|
+
*/
|
|
165
|
+
function reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T> {
|
|
166
|
+
return then((value, ok, err) => {
|
|
167
|
+
const errmsg = rejectFn(value);
|
|
168
|
+
return errmsg === null
|
|
169
|
+
? ok(value)
|
|
170
|
+
: err(typeof errmsg === 'string' ? annotate(value, errmsg) : errmsg);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Uses the given decoder, but will use an alternative error message in
|
|
176
|
+
* case it rejects. This can be used to simplify or shorten otherwise
|
|
177
|
+
* long or low-level/technical errors.
|
|
178
|
+
*/
|
|
179
|
+
function describe(message: string): Decoder<T> {
|
|
180
|
+
return define((blob, _, err) => {
|
|
181
|
+
// Decode using the given decoder...
|
|
182
|
+
const result = decode(blob);
|
|
183
|
+
if (result.ok) {
|
|
184
|
+
return result;
|
|
185
|
+
} else {
|
|
186
|
+
// ...but in case of error, annotate this with the custom given
|
|
187
|
+
// message instead
|
|
188
|
+
return err(annotate(result.error, message));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* WARNING: This is an EXPERIMENTAL API that will likely change in the
|
|
195
|
+
* future. Please DO NOT rely on it.
|
|
196
|
+
*
|
|
197
|
+
* Chain together the current decoder with another, but also pass along
|
|
198
|
+
* the original input.
|
|
199
|
+
*
|
|
200
|
+
* This is like `.then()`, but instead of this function receiving just
|
|
201
|
+
* the decoded result ``T``, it also receives the original input.
|
|
202
|
+
*
|
|
203
|
+
* This is an advanced, low-level, decoder.
|
|
204
|
+
*/
|
|
205
|
+
function peek_UNSTABLE<V>(next: DecodeFn<V, [mixed, T]>): Decoder<V> {
|
|
206
|
+
return define((blob, ok, err) => {
|
|
207
|
+
const result = decode(blob);
|
|
208
|
+
return result.ok ? next([blob, result.value], ok, err) : result;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return Object.freeze({
|
|
213
|
+
verify,
|
|
214
|
+
decode,
|
|
215
|
+
transform,
|
|
216
|
+
refine,
|
|
217
|
+
reject,
|
|
218
|
+
describe,
|
|
219
|
+
then,
|
|
220
|
+
|
|
221
|
+
// EXPERIMENTAL - please DO NOT rely on this method
|
|
222
|
+
peek_UNSTABLE,
|
|
223
|
+
});
|
|
224
|
+
}
|
package/Decoder.mjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { annotate } from './annotate.mjs';
|
|
2
|
+
import { formatInline } from './format.mjs';
|
|
3
|
+
import { err as makeErr, ok as makeOk } from './result.mjs';
|
|
4
|
+
|
|
5
|
+
function noThrow(fn) {
|
|
6
|
+
return function (t) {
|
|
7
|
+
try {
|
|
8
|
+
var v = fn(t);
|
|
9
|
+
return makeOk(v);
|
|
10
|
+
} catch (e) {
|
|
11
|
+
return makeErr(annotate(t, e instanceof Error ? e.message : String(e)));
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Defines a new `Decoder<T>`, by implementing a custom acceptance function.
|
|
17
|
+
* The function receives three arguments:
|
|
18
|
+
*
|
|
19
|
+
* 1. `blob` - the raw/unknown input (aka your external data)
|
|
20
|
+
* 2. `ok` - Call `ok(value)` to accept the input and return `value`
|
|
21
|
+
* 3. `err` - Call `err(message)` to reject the input and use "message" in the
|
|
22
|
+
* annotation
|
|
23
|
+
*
|
|
24
|
+
* The expected return value should be a `DecodeResult<T>`, which can be
|
|
25
|
+
* obtained by returning the result from the provided `ok` or `err` helper
|
|
26
|
+
* functions.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export function define(decodeFn) {
|
|
31
|
+
/**
|
|
32
|
+
* Validates the raw/untrusted/unknown input and either accepts or rejects
|
|
33
|
+
* it.
|
|
34
|
+
*
|
|
35
|
+
* Contrasted with `.verify()`, calls to `.decode()` will never fail and
|
|
36
|
+
* instead return a result type.
|
|
37
|
+
*/
|
|
38
|
+
function decode(blob) {
|
|
39
|
+
return decodeFn(blob, makeOk, function (msg) {
|
|
40
|
+
return makeErr(typeof msg === 'string' ? annotate(blob, msg) : msg);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Verified the (raw/untrusted/unknown) input and either accepts or rejects
|
|
45
|
+
* it. When accepted, returns the decoded `T` value directly. Otherwise
|
|
46
|
+
* fail with a runtime error.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
function verify(blob, formatter) {
|
|
51
|
+
if (formatter === void 0) {
|
|
52
|
+
formatter = formatInline;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var result = decode(blob);
|
|
56
|
+
|
|
57
|
+
if (result.ok) {
|
|
58
|
+
return result.value;
|
|
59
|
+
} else {
|
|
60
|
+
var _err = new Error('\n' + formatter(result.error));
|
|
61
|
+
|
|
62
|
+
_err.name = 'Decoding error';
|
|
63
|
+
throw _err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Accepts any value the given decoder accepts, and on success, will call
|
|
68
|
+
* the given function **on the decoded result**. If the transformation
|
|
69
|
+
* function throws an error, the whole decoder will fail using the error
|
|
70
|
+
* message as the failure reason.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
function transform(transformFn) {
|
|
75
|
+
return then(noThrow(transformFn));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Adds an extra predicate to a decoder. The new decoder is like the
|
|
79
|
+
* original decoder, but only accepts values that also meet the
|
|
80
|
+
* predicate.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
function refine(predicateFn, errmsg) {
|
|
85
|
+
return reject(function (value) {
|
|
86
|
+
return predicateFn(value) ? // Don't reject
|
|
87
|
+
null : // Reject with the given error message
|
|
88
|
+
errmsg;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Chain together the current decoder with another.
|
|
93
|
+
*
|
|
94
|
+
* First, the current decoder must accept the input. If so, it will pass
|
|
95
|
+
* the successfully decoded result to the given ``next`` function to
|
|
96
|
+
* further decide whether or not the value should get accepted or rejected.
|
|
97
|
+
*
|
|
98
|
+
* The argument to `.then()` is a decoding function, just like one you
|
|
99
|
+
* would pass to `define()`. The key difference with `define()` is that
|
|
100
|
+
* `define()` must always assume an ``unknown`` input, whereas with
|
|
101
|
+
* a `.then()` call the provided ``next`` function will receive a ``T`` as
|
|
102
|
+
* its input. This will allow the function to make a stronger assumption
|
|
103
|
+
* about its input.
|
|
104
|
+
*
|
|
105
|
+
* If it helps, you can think of `define(nextFn)` as equivalent to
|
|
106
|
+
* `unknown.then(nextFn)`.
|
|
107
|
+
*
|
|
108
|
+
* This is an advanced, low-level, decoder. It's not recommended to reach
|
|
109
|
+
* for this low-level construct when implementing custom decoders. Most
|
|
110
|
+
* cases can be covered by `.transform()` or `.refine()`.
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
function then(next) {
|
|
115
|
+
return define(function (blob, ok, err) {
|
|
116
|
+
var result = decode(blob);
|
|
117
|
+
return result.ok ? next(result.value, ok, err) : result;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Adds an extra predicate to a decoder. The new decoder is like the
|
|
122
|
+
* original decoder, but only accepts values that aren't rejected by the
|
|
123
|
+
* given function.
|
|
124
|
+
*
|
|
125
|
+
* The given function can return `null` to accept the decoded value, or
|
|
126
|
+
* return a specific error message to reject.
|
|
127
|
+
*
|
|
128
|
+
* Unlike `.refine()`, you can use this function to return a dynamic error
|
|
129
|
+
* message.
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
function reject(rejectFn) {
|
|
134
|
+
return then(function (value, ok, err) {
|
|
135
|
+
var errmsg = rejectFn(value);
|
|
136
|
+
return errmsg === null ? ok(value) : err(typeof errmsg === 'string' ? annotate(value, errmsg) : errmsg);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Uses the given decoder, but will use an alternative error message in
|
|
141
|
+
* case it rejects. This can be used to simplify or shorten otherwise
|
|
142
|
+
* long or low-level/technical errors.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
function describe(message) {
|
|
147
|
+
return define(function (blob, _, err) {
|
|
148
|
+
// Decode using the given decoder...
|
|
149
|
+
var result = decode(blob);
|
|
150
|
+
|
|
151
|
+
if (result.ok) {
|
|
152
|
+
return result;
|
|
153
|
+
} else {
|
|
154
|
+
// ...but in case of error, annotate this with the custom given
|
|
155
|
+
// message instead
|
|
156
|
+
return err(annotate(result.error, message));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* WARNING: This is an EXPERIMENTAL API that will likely change in the
|
|
162
|
+
* future. Please DO NOT rely on it.
|
|
163
|
+
*
|
|
164
|
+
* Chain together the current decoder with another, but also pass along
|
|
165
|
+
* the original input.
|
|
166
|
+
*
|
|
167
|
+
* This is like `.then()`, but instead of this function receiving just
|
|
168
|
+
* the decoded result ``T``, it also receives the original input.
|
|
169
|
+
*
|
|
170
|
+
* This is an advanced, low-level, decoder.
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
function peek_UNSTABLE(next) {
|
|
175
|
+
return define(function (blob, ok, err) {
|
|
176
|
+
var result = decode(blob);
|
|
177
|
+
return result.ok ? next([blob, result.value], ok, err) : result;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return Object.freeze({
|
|
182
|
+
verify: verify,
|
|
183
|
+
decode: decode,
|
|
184
|
+
transform: transform,
|
|
185
|
+
refine: refine,
|
|
186
|
+
reject: reject,
|
|
187
|
+
describe: describe,
|
|
188
|
+
then: then,
|
|
189
|
+
// EXPERIMENTAL - please DO NOT rely on this method
|
|
190
|
+
peek_UNSTABLE: peek_UNSTABLE
|
|
191
|
+
});
|
|
192
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"Package `decoders` requires TypeScript >= 4.1.0"
|