Skip to content
better-result
Esc
navigateopen⌘Jpreview
On this page

Tagged errors

Define discriminated Error subclasses with typed properties, guards, JSON output, causes, and generator support.

TaggedError creates real Error subclasses with a literal _tag and typed properties.

Define an error

import { TaggedError } from "better-result";

class UserNotFound extends TaggedError("UserNotFound")<{
  userId: string;
  message: string;
}> {}

const error = new UserNotFound({
  userId: "usr_123",
  message: "User usr_123 was not found",
});

The class has normal Error behavior plus:

  • name === "UserNotFound";
  • _tag === "UserNotFound" as a string literal;
  • readonly userId and other declared properties;
  • toJSON();
  • exhaustive .match() by _tag;
  • UserNotFound.is(value);
  • iterator support for yield*.

Match an error union

const message = error.match({
  UserNotFound: (error) => `No user ${error.userId}`,
  PermissionDenied: (error) => `Missing permission ${error.permission}`,
});

The handler map must cover every variant in the receiver’s error union. Each handler receives its concrete error subtype. If the selected handler throws, .match() throws Panic with that exception as its cause. Use the standalone matchError function for structurally tagged errors or data-last matching.

match is reserved for this method. A TaggedError payload property or incompatible subclass member named match is rejected by TypeScript.

Add a computed message

class RequestFailed extends TaggedError("RequestFailed")<{
  url: string;
  status: number;
  message: string;
}> {
  constructor(args: { url: string; status: number }) {
    super({
      ...args,
      message: `Request to ${args.url} failed with ${args.status}`,
    });
  }
}

Preserve a cause

Declare cause in the property type and pass it to super:

class ParseFailed extends TaggedError("ParseFailed")<{
  input: string;
  cause: unknown;
  message: string;
}> {}

new ParseFailed({ input, cause, message: "Could not parse input" });

Native Error.cause is populated. toJSON() serializes an Error cause to its name, message, and stack.

Guards

if (UserNotFound.is(value)) {
  value.userId;
}

if (TaggedError.is(value)) {
  value._tag;
  value.toJSON();
}

if (isTaggedError(value)) {
  value._tag;
}

TaggedError.is and isTaggedError detect any better-result tagged error. A concrete class’s .is guard detects that class.

Yield directly

const result = Result.gen(function* () {
  if (!user) {
    yield* new UserNotFound({ userId, message: "User not found" });
  }
  return Result.ok(user);
});

Public helper types

  • TaggedErrorClass<Tag> describes the factory’s generic class.
  • TaggedErrorInstance<Tag, Props> describes an instance structurally.
  • AnyTaggedError describes any tagged error with toJSON().

Was this page helpful?