0
# Validation & Error Handling
1
2
Comprehensive error reporting system with detailed context information for debugging validation failures.
3
4
## Capabilities
5
6
### Validation Types
7
8
Core types for validation results and error handling.
9
10
```typescript { .api }
11
/** Validation result type - Either success or array of errors */
12
type Validation<A> = Either<Errors, A>;
13
14
/** Array of validation errors with context information */
15
interface Errors extends Array<ValidationError> {}
16
17
/** Individual validation error with context and optional message */
18
interface ValidationError {
19
/** the offending (sub)value */
20
readonly value: unknown;
21
/** where the error originated */
22
readonly context: Context;
23
/** optional custom error message */
24
readonly message?: string;
25
}
26
27
/** Context information for error path tracking */
28
interface Context extends ReadonlyArray<ContextEntry> {}
29
30
/** Single context entry in the validation path */
31
interface ContextEntry {
32
readonly key: string;
33
readonly type: Decoder<any, any>;
34
/** the input data */
35
readonly actual?: unknown;
36
}
37
```
38
39
### Validation Functions
40
41
Functions for creating validation results.
42
43
```typescript { .api }
44
/**
45
* Create a successful validation result
46
* @param value - The successful value
47
*/
48
function success<T>(value: T): Validation<T>;
49
50
/**
51
* Create a single validation failure
52
* @param value - The failing value
53
* @param context - Context where the failure occurred
54
* @param message - Optional error message
55
*/
56
function failure<T>(value: unknown, context: Context, message?: string): Validation<T>;
57
58
/**
59
* Create a validation failure from multiple errors
60
* @param errors - Array of validation errors
61
*/
62
function failures<T>(errors: Errors): Validation<T>;
63
```
64
65
**Usage Example:**
66
67
```typescript
68
import * as t from "io-ts";
69
70
// Create context for error reporting
71
const context: t.Context = [{ key: '', type: t.string, actual: 42 }];
72
73
// Create validation results
74
const successResult = t.success("hello");
75
const failureResult = t.failure(42, context, "Expected string but got number");
76
const multipleFailures = t.failures([
77
{ value: 42, context, message: "Not a string" },
78
{ value: null, context, message: "Cannot be null" }
79
]);
80
```
81
82
### Context Utilities
83
84
Functions for managing validation context during error reporting.
85
86
```typescript { .api }
87
/**
88
* Create a context entry for error reporting
89
* @param key - Property key or array index
90
* @param decoder - The decoder that failed
91
*/
92
function getContextEntry(key: string, decoder: Decoder<any, any>): ContextEntry;
93
94
/**
95
* Append a new entry to the validation context
96
* @param context - Current context
97
* @param key - Property key or array index
98
* @param decoder - The decoder being applied
99
* @param actual - The actual value being validated
100
*/
101
function appendContext(
102
context: Context,
103
key: string,
104
decoder: Decoder<any, any>,
105
actual?: unknown
106
): Context;
107
```
108
109
### Error Reporters
110
111
Built-in reporters for formatting validation errors.
112
113
```typescript { .api }
114
/** Reporter interface for formatting validation results */
115
interface Reporter<A> {
116
report(validation: Validation<A>): A;
117
}
118
119
/** Path reporter that formats errors as readable strings */
120
const PathReporter: Reporter<Array<string>>;
121
122
/**
123
* @deprecated Use PathReporter instead
124
* Throw reporter that throws on validation failure
125
*/
126
const ThrowReporter: Reporter<any>;
127
```
128
129
**Usage Examples:**
130
131
```typescript
132
import * as t from "io-ts";
133
import { PathReporter } from "io-ts/PathReporter";
134
import { ThrowReporter } from "io-ts/ThrowReporter";
135
136
const User = t.type({
137
name: t.string,
138
age: t.number,
139
email: t.string
140
});
141
142
const invalidData = {
143
name: 123,
144
age: "thirty",
145
email: null
146
};
147
148
const result = User.decode(invalidData);
149
150
if (result._tag === "Left") {
151
// Use PathReporter for readable error messages
152
const errors = PathReporter.failure(result.left);
153
console.log("Validation errors:");
154
errors.forEach(error => console.log(` - ${error}`));
155
156
// Output:
157
// - Invalid value 123 at path: name (expected: string)
158
// - Invalid value "thirty" at path: age (expected: number)
159
// - Invalid value null at path: email (expected: string)
160
}
161
162
// ❌ ThrowReporter usage (deprecated - don't use)
163
// try {
164
// ThrowReporter.report(result);
165
// } catch (error) {
166
// console.error("Validation failed:", error.message);
167
// }
168
```
169
170
## Advanced Error Handling
171
172
### Custom Error Messages
173
174
```typescript
175
import * as t from "io-ts";
176
from { pipe } from "fp-ts/function";
177
import { fold } from "fp-ts/Either";
178
179
const EmailCodec = new t.Type<string, string, unknown>(
180
'Email',
181
(u): u is string => typeof u === 'string' && /\S+@\S+\.\S+/.test(u),
182
(u, c) => {
183
if (typeof u !== 'string') {
184
return t.failure(u, c, 'Must be a string');
185
}
186
if (!/\S+@\S+\.\S+/.test(u)) {
187
return t.failure(u, c, 'Must be a valid email address');
188
}
189
return t.success(u);
190
},
191
t.identity
192
);
193
194
const result = EmailCodec.decode("invalid-email");
195
pipe(
196
result,
197
fold(
198
(errors) => {
199
console.log("Custom error:", errors[0].message);
200
// Output: "Must be a valid email address"
201
},
202
(email) => console.log("Valid email:", email)
203
)
204
);
205
```
206
207
### Error Context Tracking
208
209
```typescript
210
import * as t from "io-ts";
211
import { PathReporter } from "io-ts/PathReporter";
212
213
const NestedData = t.type({
214
users: t.array(t.type({
215
profile: t.type({
216
contact: t.type({
217
email: t.string,
218
phone: t.string
219
})
220
})
221
}))
222
});
223
224
const invalidData = {
225
users: [
226
{
227
profile: {
228
contact: {
229
email: "valid@example.com",
230
phone: 123 // Should be string
231
}
232
}
233
}
234
]
235
};
236
237
const result = NestedData.decode(invalidData);
238
if (result._tag === "Left") {
239
const errors = PathReporter.failure(result.left);
240
console.log(errors);
241
// Output: ["Invalid value 123 at users.0.profile.contact.phone (expected: string)"]
242
}
243
```
244
245
### Working with Either Results
246
247
```typescript
248
import * as t from "io-ts";
249
import { pipe } from "fp-ts/function";
250
import { fold, map, mapLeft } from "fp-ts/Either";
251
252
const User = t.type({
253
name: t.string,
254
age: t.number
255
});
256
257
const processUser = (data: unknown) =>
258
pipe(
259
User.decode(data),
260
mapLeft(errors => ({
261
type: 'VALIDATION_ERROR' as const,
262
errors: errors.map(e => e.message || 'Validation failed')
263
})),
264
map(user => ({
265
type: 'SUCCESS' as const,
266
user: {
267
...user,
268
displayName: user.name.toUpperCase()
269
}
270
})),
271
fold(
272
error => {
273
console.error('Validation failed:', error.errors);
274
return null;
275
},
276
success => {
277
console.log('User processed:', success.user);
278
return success.user;
279
}
280
)
281
);
282
283
// Usage
284
const result1 = processUser({ name: "Alice", age: 30 });
285
// Output: "User processed: { name: 'Alice', age: 30, displayName: 'ALICE' }"
286
287
const result2 = processUser({ name: "Bob" }); // Missing age
288
// Output: "Validation failed: ['Validation failed']"
289
```
290
291
### Error Aggregation
292
293
```typescript
294
import * as t from "io-ts";
295
import { sequenceT } from "fp-ts/Apply";
296
import { either } from "fp-ts/Either";
297
298
// Validate multiple independent values and collect all errors
299
const validateMultiple = (data: {
300
name: unknown;
301
email: unknown;
302
age: unknown;
303
}) => {
304
return sequenceT(either)(
305
t.string.decode(data.name),
306
t.string.decode(data.email),
307
t.number.decode(data.age)
308
);
309
};
310
311
const result = validateMultiple({
312
name: 123, // Invalid
313
email: null, // Invalid
314
age: "thirty" // Invalid
315
});
316
317
if (result._tag === "Left") {
318
console.log("All validation errors collected:", result.left);
319
// All three validation errors are collected together
320
}
321
```
322
323
### Custom Reporter
324
325
```typescript
326
import * as t from "io-ts";
327
328
// Create a custom reporter that formats errors differently
329
const CustomReporter: t.Reporter<{ success: boolean; errors: string[] }> = {
330
report: (validation) => {
331
if (validation._tag === "Right") {
332
return { success: true, errors: [] };
333
} else {
334
return {
335
success: false,
336
errors: validation.left.map(error => {
337
const path = error.context.map(c => c.key).join('.');
338
return `${path}: ${error.message || 'Validation failed'}`;
339
})
340
};
341
}
342
}
343
};
344
345
const User = t.type({ name: t.string, age: t.number });
346
const result = User.decode({ name: 123, age: "thirty" });
347
const report = CustomReporter.report(result);
348
349
console.log(report);
350
// Output: {
351
// success: false,
352
// errors: ["name: Validation failed", "age: Validation failed"]
353
// }
354
```