0
# Type Validation
1
2
Runtime type validation and type guard generation using pattern matching. Perfect for validating API responses, user input, and unknown data structures.
3
4
## Capabilities
5
6
### Type Guard Generation
7
8
Creates type guard functions from patterns for runtime type checking with compile-time type safety.
9
10
```typescript { .api }
11
/**
12
* Creates a type guard function from a pattern
13
* @param pattern - Pattern to match against
14
* @returns Type guard function that narrows unknown to matched type
15
*/
16
function isMatching<const p extends Pattern<unknown>>(
17
pattern: p
18
): (value: unknown) => value is P.infer<p>;
19
```
20
21
**Usage Examples:**
22
23
```typescript
24
import { isMatching, P } from "ts-pattern";
25
26
// Create type guards
27
const isUser = isMatching({
28
id: P.number,
29
name: P.string,
30
email: P.string.regex(/\S+@\S+\.\S+/)
31
});
32
33
const isApiResponse = isMatching({
34
success: P.boolean,
35
data: P.optional(P.any),
36
error: P.optional(P.string)
37
});
38
39
// Use type guards
40
function processUnknownData(data: unknown) {
41
if (isUser(data)) {
42
// data is now typed as { id: number; name: string; email: string }
43
console.log(`User: ${data.name} (${data.email})`);
44
return data.id;
45
}
46
47
if (isApiResponse(data)) {
48
// data is now typed as the API response interface
49
if (data.success && data.data) {
50
return data.data;
51
} else {
52
throw new Error(data.error || 'Unknown API error');
53
}
54
}
55
56
throw new Error('Invalid data format');
57
}
58
```
59
60
### Direct Pattern Validation
61
62
Validates if a value matches a pattern directly without creating a type guard function.
63
64
```typescript { .api }
65
/**
66
* Validates if a value matches a pattern
67
* @param pattern - Pattern to match against
68
* @param value - Value to validate
69
* @returns Boolean indicating if value matches pattern
70
*/
71
function isMatching<const T, const P extends Pattern<T>>(
72
pattern: P,
73
value: T
74
): value is T & P.infer<P>;
75
```
76
77
**Usage Examples:**
78
79
```typescript
80
// Direct validation
81
const data = { name: "Alice", age: 25, active: true };
82
83
if (isMatching({ name: P.string, age: P.number.gte(18) }, data)) {
84
// data is validated and typed properly
85
console.log(`Adult user: ${data.name}, age ${data.age}`);
86
}
87
88
// Validate API responses
89
async function fetchUserData(userId: number) {
90
const response = await fetch(`/api/users/${userId}`);
91
const data = await response.json();
92
93
if (isMatching({
94
id: P.number,
95
name: P.string,
96
profile: P.optional({
97
avatar: P.string.startsWith('http'),
98
bio: P.string
99
})
100
}, data)) {
101
return data; // properly typed user data
102
}
103
104
throw new Error('Invalid user data format');
105
}
106
```
107
108
### Complex Validation Patterns
109
110
Advanced patterns for validating complex data structures.
111
112
```typescript { .api }
113
// Pattern constraint type for additional properties
114
type PatternConstraint<T> = T extends readonly any[]
115
? P.Pattern<T>
116
: T extends object
117
? P.Pattern<T> & UnknownProperties
118
: P.Pattern<T>;
119
120
// Unknown properties interface for object patterns
121
interface UnknownProperties {
122
[key: string]: unknown;
123
}
124
```
125
126
**Usage Examples:**
127
128
```typescript
129
// Validate nested structures
130
const isNestedConfig = isMatching({
131
database: {
132
host: P.string,
133
port: P.number.between(1, 65535),
134
credentials: P.union(
135
{ type: 'password', username: P.string, password: P.string },
136
{ type: 'token', token: P.string }
137
)
138
},
139
features: P.array(P.string),
140
debug: P.boolean.optional()
141
});
142
143
// Validate arrays with specific patterns
144
const isUserList = isMatching(P.array({
145
id: P.number,
146
name: P.string.minLength(1),
147
roles: P.array(P.union('admin', 'user', 'guest')),
148
lastLogin: P.union(P.instanceOf(Date), P.nullish)
149
}));
150
151
// Validate with custom predicates
152
const isValidEventPayload = isMatching({
153
type: P.string,
154
timestamp: P.when(ts => ts instanceof Date && ts <= new Date()),
155
payload: P.shape(P.any), // allows any object structure
156
metadata: P.optional(P.any)
157
});
158
159
// Use in validation functions
160
function validateUserInput(input: unknown) {
161
if (!isNestedConfig(input)) {
162
throw new Error('Invalid configuration format');
163
}
164
165
// input is now properly typed
166
const { database, features, debug = false } = input;
167
168
if (database.credentials.type === 'password') {
169
// TypeScript knows this is password credentials
170
return connectWithPassword(database.host, database.port,
171
database.credentials.username,
172
database.credentials.password);
173
} else {
174
// TypeScript knows this is token credentials
175
return connectWithToken(database.host, database.port,
176
database.credentials.token);
177
}
178
}
179
```
180
181
### Validation with Error Handling
182
183
Combining validation with proper error handling and user feedback.
184
185
**Usage Examples:**
186
187
```typescript
188
// Validation with detailed error messages
189
function validateAndProcessData<T>(
190
data: unknown,
191
pattern: Pattern<T>,
192
errorMessage: string
193
): T {
194
if (isMatching(pattern, data)) {
195
return data;
196
}
197
198
throw new Error(`${errorMessage}. Received: ${JSON.stringify(data)}`);
199
}
200
201
// Form validation
202
interface UserForm {
203
email: string;
204
password: string;
205
age: number;
206
terms: boolean;
207
}
208
209
const userFormPattern = {
210
email: P.string.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
211
password: P.string.minLength(8).regex(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),
212
age: P.number.int().between(13, 120),
213
terms: P.when((x: boolean) => x === true)
214
};
215
216
function validateUserForm(formData: unknown): UserForm {
217
try {
218
return validateAndProcessData(
219
formData,
220
userFormPattern,
221
'Invalid user form data'
222
);
223
} catch (error) {
224
// Add specific validation error details
225
if (typeof formData === 'object' && formData !== null) {
226
const data = formData as Record<string, unknown>;
227
228
if (!isMatching(P.string.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/), data.email)) {
229
throw new Error('Invalid email format');
230
}
231
232
if (!isMatching(P.string.minLength(8), data.password)) {
233
throw new Error('Password must be at least 8 characters');
234
}
235
236
if (!isMatching(P.number.int().between(13, 120), data.age)) {
237
throw new Error('Age must be between 13 and 120');
238
}
239
240
if (data.terms !== true) {
241
throw new Error('Terms and conditions must be accepted');
242
}
243
}
244
245
throw error;
246
}
247
}
248
249
// API response validation
250
async function safeApiCall<T>(
251
url: string,
252
responsePattern: Pattern<T>
253
): Promise<T> {
254
try {
255
const response = await fetch(url);
256
257
if (!response.ok) {
258
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
259
}
260
261
const data = await response.json();
262
263
if (isMatching(responsePattern, data)) {
264
return data;
265
}
266
267
throw new Error(`Invalid API response format from ${url}`);
268
} catch (error) {
269
console.error('API call failed:', error);
270
throw error;
271
}
272
}
273
274
// Usage
275
const userData = await safeApiCall('/api/user/123', {
276
id: P.number,
277
name: P.string,
278
email: P.string,
279
preferences: P.optional({
280
theme: P.union('light', 'dark'),
281
notifications: P.boolean
282
})
283
});
284
```