Matching errors
Handle tagged-error unions exhaustively or partially with inferred handler return unions.
Exhaustive instance matching
Every TaggedError instance has a .match() method:
const message = error.match({
UserNotFound: (error) => `No user ${error.userId}`,
PermissionDenied: (error) => `Missing permission ${error.permission}`,
});
Every _tag in the receiver union needs a handler. Handler parameters narrow to their exact error class. Different handler outputs produce a return union.
const outcome = error.match({
UserNotFound: () => 404,
PermissionDenied: () => "forbidden" as const,
});
// number | "forbidden"
This composes directly inside a Result error branch without annotating the inferred error:
const response = result.match({
ok: (user) => ({ status: 200, body: user }),
err: (error) =>
error.match({
UserNotFound: () => ({ status: 404, body: null }),
DatabaseUnavailable: () => ({ status: 503, body: null }),
}),
});
Standalone matchError
Use matchError(error, handlers) when working with structurally tagged errors that were not created by TaggedError:
const message = matchError(error, {
UserNotFound: (error) => `No user ${error.userId}`,
PermissionDenied: (error) => `Missing permission ${error.permission}`,
});
The standalone function has the same exhaustive narrowing, return inference, and defect behavior as .match(). If the selected exhaustive handler throws, .match() and matchError throw Panic with the original exception as cause.
match is a reserved TaggedError instance name. The factory rejects payload property types named match, preventing constructor assignment from shadowing the method.
Data-last matching
Annotate variant-specific parameters when the error union is not known until application:
const toMessage = matchError({
UserNotFound: (error: UserNotFound) => `No user ${error.userId}`,
PermissionDenied: (error: PermissionDenied) => error.message,
});
const message = toMessage(error);
Partial matching with identity fallback
matchErrorPartial returns unhandled errors unchanged by default:
const transformed = matchErrorPartial(error, {
UserNotFound: (error) => `No user ${error.userId}`,
});
// string | PermissionDenied
This makes incremental handling explicit instead of discarding the rest of the union.
Custom fallback
const message = matchErrorPartial(
error,
{ UserNotFound: (error) => `No user ${error.userId}` },
(unhandled) => `Unexpected failure: ${unhandled.message}`,
);
Recover selected variants
Pass Result.err as the fallback to preserve unhandled variants while recovering one:
const recovered = result.tryRecover(
matchErrorPartial(
{
UserNotFound: (_error: UserNotFound) => Result.ok(guestUser),
},
Result.err,
),
);
Match at boundaries
Keep domain functions returning typed errors. Match where the application can choose an HTTP status, CLI exit code, user message, retry policy, or compensating action.