You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`isString` is typed as `(value?: any): value is string`, so TypeScript infers
43
-
the `beString` return type as `string`.
40
+
`be` takes a
41
+
[user-defined type guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards).
42
+
E.g., lodash’s `isString` is typed as `(value: any): value is string`, so
43
+
TypeScript infers the `beString` return type as `string`.
44
+
45
+
The functions that `be` returns (like `beSting`) are called decoders. A decoder
46
+
is a function that takes an unknown value and returns a value of a proven type.
47
+
48
+
But, it is possible that a decoder cannot return a value of the necessary type,
49
+
e.g., the input is invalid. In those cases, a decoder _throws_. Therefore, you might
50
+
want to wrap all of your decoder invocations in `try/catch`, or...
51
+
52
+
### Or: catching decorators
53
+
54
+
The second base function is `or`: a factory for _catching decorators_. Sounds
55
+
complicated, but it’s actually quite simple:
44
56
45
-
The functions that `be` returns (like `beSting`) are called decoders. A
46
-
decoder is a function that takes an unknown value and returns a value of a proven type.
47
-
48
-
But, it is possible that a decoder cannot return a value of the necessary type
49
-
, e.g., the input is invalid. In those cases, a decoder _throws_. So, you
50
-
might want to wrap all of your decoder invocations in `try/catch`, or...
51
-
52
-
### Or: catching decorators
53
-
54
-
The second base function is `or`: a factory for _catching decorators_
55
-
. Sounds complicated, but it’s actually quite simple:
56
-
57
57
```ts
58
58
import { be, or } from'be-good'
59
59
60
-
const optional =or(undefined) // a catching decorator
61
-
const beString =be(isString) // + a decoder
60
+
const optional =or(undefined) // a catching decorator
61
+
const beString =be(isString) // + a decoder
62
62
const beOptionalString =optional(beString) // = a decorated decoder
63
63
64
-
beOptionalString('Catchers in the Rye') // 'Catchers in the Rye`
65
-
beOptionalString(-1) // undefined
64
+
beOptionalString('Catchers in the Rye') // 'Catchers in the Rye`
65
+
beOptionalString(-1) // undefined
66
66
```
67
67
68
68
To describe what happens above:
69
69
70
70
- you apply `or` to a value (here, `undefined`) and get back a decorator
71
71
- you apply the decorator to the decoder and get back a new decoder
72
72
- if the new decoder is given a valid input value (here, a string), it returns
73
-
that value
73
+
that value
74
74
- otherwise, it returns a fallback (`undefined`)
75
-
76
-
Obviously, the return type of `beOptionalString ` here is not just `string `, but `string | undefined`. On the other hand, nothing stops you from using a fallback of the same type as the expected value:
77
-
78
-
```ts
75
+
76
+
Obviously, the return type of `beOptionalString` here is not just `string`, but
77
+
`string | undefined`. On the other hand, nothing stops you from using a fallback
78
+
of the same type as the expected value:
79
+
80
+
```ts
79
81
const alwaysBeNumber =or(0)(be(isNumber))
80
-
```
82
+
```
81
83
82
-
And sure, you can create one-off decorators on the fly. On the other hand
83
-
, you may want to keep some of them (like the `optional` above) reusable.
84
+
And sure, you can create one-off decorators on the fly. On the other hand, you
85
+
may want to keep some of them (like the `optional` above) reusable across your
86
+
app.
84
87
85
88
### Decoding objects
86
89
87
90
There’s a pretty low-level decoder called `beObject` that simply asserts that
88
-
the value is indeed an object. It’s useful if you’re doing some
89
-
non-standard stuff, like transforming your data instead of simply decoding
90
-
–we’ll cover those scenarios later.
91
+
the value is indeed an object. It’s useful if you’re doing some non-standard
92
+
stuff, like transforming your data instead of simply decoding—we’ll cover those
93
+
scenarios later.
91
94
92
95
For the most scenarios, there’s a more convenient decoder: `beObjectOf`.
// input is an object, has all the fields, hence the decoder returns a Mercenary
125
130
126
131
mercenaryDecoder({
127
-
name: 'Will', fee: 50_000_000, hasGun: true, willTravel: 'No, Will Smith'
132
+
name: 'Will',
133
+
fee: 50_000_000,
134
+
hasGun: true,
135
+
willTravel: 'No, Will Smith'
128
136
})
129
137
// is object, right properties, wrong type, => null
130
138
131
139
mercenaryDecoder({
132
-
name: 'Sara', hasGun: true, willTravel: true
140
+
name: 'Sara',
141
+
hasGun: true,
142
+
willTravel: true
133
143
})
134
144
// is object, missing fields, => null
135
145
@@ -152,27 +162,129 @@ mercenaryDecoder({
152
162
### A note on generics and type inference
153
163
154
164
Actually, you don’t have to write `beObjectOf<Mercenary>`. The decoder return
155
-
type will be inferred from the property decoders you gave to `beObjectOf`:
156
-
e.g. `beObjectOf({ a: beString })` will have type `(x: unknown) => { a
157
-
: string }`. And since TypeScript types are structural, it doesn’t matter
158
-
how the type is called as long as the shape is right.
159
-
160
-
On the other hand, if you make a mistake in a property name or decoder you
161
-
give to `beObjectOf`, TypeScript will fail—somewhere—but the error message
162
-
might point to a place far from where the actual error is, and you’ll spend more time fixing it. So I’d recommend specifying the expected type right inside a decoder (like above), or maybe right outside of it, like this:
163
-
164
-
```ts
165
+
type will be inferred from the property decoders you gave to `beObjectOf`: e.g.
166
+
`beObjectOf({ a: beString })` will have type `(x: unknown) => { a : string }`.
167
+
And since TypeScript types are structural, it doesn’t matter how the type is
168
+
called as long as the shape is right.
169
+
170
+
Then again, if you make a mistake in a property name or decoder you give to
171
+
`beObjectOf`, TypeScript will fail—somewhere—and the error message might point
172
+
to a place far from where the actual error is, and you’ll spend more time fixing
173
+
it. Better specify the expected type right inside a decoder (like above), or
0 commit comments