0
# Infrastructure Modules (Experimental)
1
2
**⚠️ EXPERIMENTAL:** These modules are experimental infrastructure for error handling and composition in the modern io-ts API.
3
4
Core infrastructure modules that provide error handling and functional composition utilities for the experimental decoder system.
5
6
## DecodeError Module
7
8
Structured error system for representing validation failures with detailed context information.
9
10
### Error Types
11
12
Algebraic data type representing different kinds of decode errors with structured information.
13
14
```typescript { .api }
15
/**
16
* Union type representing all possible decode error variants
17
*/
18
type DecodeError<E> = Leaf<E> | Key<E> | Index<E> | Member<E> | Lazy<E> | Wrap<E>;
19
20
/**
21
* Leaf error - basic validation failure
22
*/
23
interface Leaf<E> {
24
readonly _tag: 'Leaf';
25
readonly actual: unknown;
26
readonly error: E;
27
}
28
29
/**
30
* Key error - object property validation failure
31
*/
32
interface Key<E> {
33
readonly _tag: 'Key';
34
readonly key: string;
35
readonly kind: Kind;
36
readonly errors: FreeSemigroup<DecodeError<E>>;
37
}
38
39
/**
40
* Index error - array element validation failure
41
*/
42
interface Index<E> {
43
readonly _tag: 'Index';
44
readonly index: number;
45
readonly kind: Kind;
46
readonly errors: FreeSemigroup<DecodeError<E>>;
47
}
48
49
/**
50
* Member error - union member validation failure
51
*/
52
interface Member<E> {
53
readonly _tag: 'Member';
54
readonly index: number;
55
readonly errors: FreeSemigroup<DecodeError<E>>;
56
}
57
58
/**
59
* Lazy error - recursive decoder validation failure
60
*/
61
interface Lazy<E> {
62
readonly _tag: 'Lazy';
63
readonly id: string;
64
readonly errors: FreeSemigroup<DecodeError<E>>;
65
}
66
67
/**
68
* Wrap error - custom message wrapper for errors
69
*/
70
interface Wrap<E> {
71
readonly _tag: 'Wrap';
72
readonly error: E;
73
readonly errors: FreeSemigroup<DecodeError<E>>;
74
}
75
76
/**
77
* Kind of property/element validation
78
*/
79
type Kind = 'required' | 'optional';
80
81
/**
82
* Required property/element constant
83
*/
84
const required: 'required';
85
86
/**
87
* Optional property/element constant
88
*/
89
const optional: 'optional';
90
```
91
92
### Error Constructors
93
94
Functions to create different types of decode errors.
95
96
```typescript { .api }
97
/**
98
* Create a leaf error for basic validation failure
99
* @param actual - The actual value that failed validation
100
* @param error - The error message or object
101
*/
102
function leaf<E>(actual: unknown, error: E): DecodeError<E>;
103
104
/**
105
* Create a key error for object property validation failure
106
* @param key - The property key that failed
107
* @param kind - Whether the property is required or optional
108
* @param errors - Nested errors from property validation
109
*/
110
function key<E>(key: string, kind: Kind, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
111
112
/**
113
* Create an index error for array element validation failure
114
* @param index - The array index that failed
115
* @param kind - Whether the element is required or optional
116
* @param errors - Nested errors from element validation
117
*/
118
function index<E>(index: number, kind: Kind, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
119
120
/**
121
* Create a member error for union validation failure
122
* @param index - The union member index that failed
123
* @param errors - Nested errors from member validation
124
*/
125
function member<E>(index: number, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
126
127
/**
128
* Create a lazy error for recursive decoder failure
129
* @param id - Identifier for the recursive decoder
130
* @param errors - Nested errors from recursive validation
131
*/
132
function lazy<E>(id: string, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
133
134
/**
135
* Create a wrap error to add custom message to existing errors
136
* @param error - Custom error message or object
137
* @param errors - The wrapped errors
138
*/
139
function wrap<E>(error: E, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
140
```
141
142
### Error Processing
143
144
Functions for pattern matching and processing decode errors.
145
146
```typescript { .api }
147
/**
148
* Pattern match on DecodeError types
149
* @param patterns - Object with handlers for each error variant
150
*/
151
function fold<E, R>(patterns: {
152
Leaf: (input: unknown, error: E) => R;
153
Key: (key: string, kind: Kind, errors: FreeSemigroup<DecodeError<E>>) => R;
154
Index: (index: number, kind: Kind, errors: FreeSemigroup<DecodeError<E>>) => R;
155
Member: (index: number, errors: FreeSemigroup<DecodeError<E>>) => R;
156
Lazy: (id: string, errors: FreeSemigroup<DecodeError<E>>) => R;
157
Wrap: (error: E, errors: FreeSemigroup<DecodeError<E>>) => R;
158
}): (error: DecodeError<E>) => R;
159
160
/**
161
* Get Semigroup instance for DecodeError accumulation
162
*/
163
function getSemigroup<E = never>(): Semigroup<FreeSemigroup<DecodeError<E>>>;
164
```
165
166
**Usage Examples:**
167
168
```typescript
169
import * as DE from "io-ts/DecodeError";
170
import * as FS from "io-ts/FreeSemigroup";
171
172
// Create basic leaf error
173
const leafError = DE.leaf(42, "Expected string");
174
175
// Create nested object error
176
const keyError = DE.key(
177
"username",
178
DE.required,
179
FS.of(DE.leaf("", "Username cannot be empty"))
180
);
181
182
// Create array validation error
183
const indexError = DE.index(
184
2,
185
DE.required,
186
FS.of(DE.leaf("invalid", "Expected number"))
187
);
188
189
// Pattern match on error types
190
const formatError = DE.fold<string, string>({
191
Leaf: (actual, error) => `Invalid value ${JSON.stringify(actual)}: ${error}`,
192
Key: (key, kind, errors) => `Property '${key}' (${kind}): ${/* format nested errors */}`,
193
Index: (index, kind, errors) => `Element [${index}] (${kind}): ${/* format nested errors */}`,
194
Member: (index, errors) => `Union member ${index}: ${/* format nested errors */}`,
195
Lazy: (id, errors) => `Recursive type '${id}': ${/* format nested errors */}`,
196
Wrap: (error, errors) => `${error}: ${/* format nested errors */}`
197
});
198
199
console.log(formatError(leafError));
200
// Output: "Invalid value 42: Expected string"
201
```
202
203
## FreeSemigroup Module
204
205
Free semigroup implementation for accumulating validation errors in a structured way.
206
207
### FreeSemigroup Type
208
209
Algebraic data type representing a free semigroup structure.
210
211
```typescript { .api }
212
/**
213
* Free semigroup type - either a single value or concatenation of two free semigroups
214
*/
215
type FreeSemigroup<A> = Of<A> | Concat<A>;
216
217
/**
218
* Single value in free semigroup
219
*/
220
interface Of<A> {
221
readonly _tag: 'Of';
222
readonly value: A;
223
}
224
225
/**
226
* Concatenation of two free semigroups
227
*/
228
interface Concat<A> {
229
readonly _tag: 'Concat';
230
readonly left: FreeSemigroup<A>;
231
readonly right: FreeSemigroup<A>;
232
}
233
```
234
235
### Constructors
236
237
Functions to create FreeSemigroup values.
238
239
```typescript { .api }
240
/**
241
* Create FreeSemigroup from single value
242
* @param value - The value to wrap
243
*/
244
function of<A>(value: A): FreeSemigroup<A>;
245
246
/**
247
* Concatenate two FreeSemigroup values
248
* @param left - Left FreeSemigroup
249
* @param right - Right FreeSemigroup
250
*/
251
function concat<A>(left: FreeSemigroup<A>, right: FreeSemigroup<A>): FreeSemigroup<A>;
252
```
253
254
### Processing
255
256
Functions for processing FreeSemigroup structures.
257
258
```typescript { .api }
259
/**
260
* Fold over FreeSemigroup structure
261
* @param onOf - Handler for single values
262
* @param onConcat - Handler for concatenations
263
*/
264
function fold<A, R>(
265
onOf: (value: A) => R,
266
onConcat: (left: FreeSemigroup<A>, right: FreeSemigroup<A>) => R
267
): (fs: FreeSemigroup<A>) => R;
268
269
/**
270
* Get Semigroup instance for FreeSemigroup
271
*/
272
function getSemigroup<A = never>(): Semigroup<FreeSemigroup<A>>;
273
```
274
275
**Usage Examples:**
276
277
```typescript
278
import * as FS from "io-ts/FreeSemigroup";
279
import { pipe } from "fp-ts/function";
280
281
// Create single value
282
const single = FS.of("First error");
283
284
// Create another value
285
const another = FS.of("Second error");
286
287
// Concatenate errors
288
const combined = FS.concat(single, another);
289
290
// Process the structure
291
const collectErrors = FS.fold<string, string[]>(
292
(value) => [value], // Single error becomes array with one element
293
(left, right) => [
294
...collectErrors(left),
295
...collectErrors(right)
296
] // Concatenation becomes merged arrays
297
);
298
299
console.log(collectErrors(combined));
300
// Output: ["First error", "Second error"]
301
302
// Use with Semigroup instance
303
const semigroup = FS.getSemigroup<string>();
304
const result = semigroup.concat(
305
FS.of("Error 1"),
306
semigroup.concat(
307
FS.of("Error 2"),
308
FS.of("Error 3")
309
)
310
);
311
312
console.log(collectErrors(result));
313
// Output: ["Error 1", "Error 2", "Error 3"]
314
```
315
316
## Integration Example
317
318
How DecodeError and FreeSemigroup work together in validation:
319
320
```typescript
321
import * as DE from "io-ts/DecodeError";
322
import * as FS from "io-ts/FreeSemigroup";
323
import * as D from "io-ts/Decoder";
324
325
// Simulate validation of a user object
326
const validateUser = (input: unknown): string[] => {
327
// This would typically be done by a decoder, but shown here for illustration
328
if (typeof input !== 'object' || input === null) {
329
const error = DE.leaf(input, "Expected object");
330
return [formatDecodeError(error)];
331
}
332
333
const obj = input as Record<string, unknown>;
334
const errors: FS.FreeSemigroup<DE.DecodeError<string>>[] = [];
335
336
// Validate name property
337
if (typeof obj.name !== 'string') {
338
errors.push(FS.of(DE.key("name", DE.required, FS.of(DE.leaf(obj.name, "Expected string")))));
339
}
340
341
// Validate age property
342
if (typeof obj.age !== 'number') {
343
errors.push(FS.of(DE.key("age", DE.required, FS.of(DE.leaf(obj.age, "Expected number")))));
344
}
345
346
// Combine all errors
347
if (errors.length === 0) return [];
348
349
const combined = errors.reduce((acc, err) =>
350
acc ? FS.concat(acc, err) : err
351
);
352
353
return combined ? flattenErrors(combined) : [];
354
};
355
356
// Helper to format individual decode errors
357
const formatDecodeError = DE.fold<string, string>({
358
Leaf: (actual, error) => `${error} (got ${JSON.stringify(actual)})`,
359
Key: (key, kind, errors) => `${key}: ${flattenErrors(errors).join(', ')}`,
360
Index: (index, kind, errors) => `[${index}]: ${flattenErrors(errors).join(', ')}`,
361
Member: (index, errors) => `member ${index}: ${flattenErrors(errors).join(', ')}`,
362
Lazy: (id, errors) => `${id}: ${flattenErrors(errors).join(', ')}`,
363
Wrap: (error, errors) => `${error} (${flattenErrors(errors).join(', ')})`
364
});
365
366
// Helper to flatten FreeSemigroup of errors into array
367
const flattenErrors = (fs: FS.FreeSemigroup<DE.DecodeError<string>>): string[] => {
368
return FS.fold<DE.DecodeError<string>, string[]>(
369
(error) => [formatDecodeError(error)],
370
(left, right) => [...flattenErrors(left), ...flattenErrors(right)]
371
)(fs);
372
};
373
374
// Usage
375
console.log(validateUser({ name: 123, age: "thirty" }));
376
// Output: [
377
// "name: Expected string (got 123)",
378
// "age: Expected number (got \"thirty\")"
379
// ]
380
```
381
382
## Advanced Error Tree Processing
383
384
For complex error processing, you can build tree structures and traverse them:
385
386
```typescript
387
import * as DE from "io-ts/DecodeError";
388
import * as FS from "io-ts/FreeSemigroup";
389
390
interface ErrorTree {
391
message: string;
392
path: string[];
393
children: ErrorTree[];
394
}
395
396
const buildErrorTree = (
397
fs: FS.FreeSemigroup<DE.DecodeError<string>>,
398
path: string[] = []
399
): ErrorTree[] => {
400
return FS.fold<DE.DecodeError<string>, ErrorTree[]>(
401
(error) => [
402
DE.fold<string, ErrorTree>({
403
Leaf: (actual, msg) => ({
404
message: msg,
405
path,
406
children: []
407
}),
408
Key: (key, kind, errors) => ({
409
message: `Property '${key}' ${kind === DE.required ? '(required)' : '(optional)'}`,
410
path: [...path, key],
411
children: buildErrorTree(errors, [...path, key])
412
}),
413
Index: (index, kind, errors) => ({
414
message: `Element [${index}] ${kind === DE.required ? '(required)' : '(optional)'}`,
415
path: [...path, `[${index}]`],
416
children: buildErrorTree(errors, [...path, `[${index}]`])
417
}),
418
Member: (index, errors) => ({
419
message: `Union member ${index}`,
420
path: [...path, `<member-${index}>`],
421
children: buildErrorTree(errors, [...path, `<member-${index}>`])
422
}),
423
Lazy: (id, errors) => ({
424
message: `Recursive type '${id}'`,
425
path: [...path, `<${id}>`],
426
children: buildErrorTree(errors, [...path, `<${id}>`])
427
}),
428
Wrap: (msg, errors) => ({
429
message: msg,
430
path,
431
children: buildErrorTree(errors, path)
432
})
433
})(error)
434
],
435
(left, right) => [...buildErrorTree(left, path), ...buildErrorTree(right, path)]
436
)(fs);
437
};
438
439
// This enables rich error reporting with full context paths
440
```