Skip to content

Status-Discriminated Unions

expect-status is designed for APIs that emit status-discriminated unions - response types where the HTTP status code is a discriminator field that determines the shape of the body.

A status-discriminated union is a TypeScript type where the status field determines which body shape is possible:

type CreateOrgResponse =
| { status: 201; body: { id: string; name: string } }
| { status: 409; body: { message: string; organisationId: string } }
| { status: 422; body: { message: string; field: string } };

This pattern is common in modern OpenAPI codegens:

  • ts-rest - emits status-discriminated unions by default
  • orval - can be configured to emit this pattern
  • hey-api - supports this pattern
  • openapi-fetch (openapi-typescript) - supports this pattern

Status-discriminated unions provide:

  • Type safety - TypeScript can narrow the body type based on the status
  • Explicit error handling - all possible error branches are visible in the type
  • No runtime checks - the type system enforces correctness at compile time

expect-status takes a status-discriminated union and a success status, then:

  1. Narrows the type to the matching branch
  2. Returns the body of that branch
  3. Throws an error if the status doesn’t match
type Response =
| { status: 200; body: { id: string } }
| { status: 404; body: { message: string } };
const body = await expectStatus(200, response);
// ^? { id: string } - TypeScript knows this is the 200 branch

By default, expect-status reads from status and body fields. If your API uses different field names, configure them:

const expectStatus = createExpectStatus({
statusField: "statusCode",
bodyField: "data",
});
type Response =
| { statusCode: 200; data: { id: string } }
| { statusCode: 404; data: { message: string } };

See Custom Field Names for more details.

expect-status is intentionally focused on numeric HTTP-style status codes. This allows it to offer:

  • Class-range matchers ('4xx', '5xx')
  • HTTP-aware exhaustive checking
  • Status-to-class mapping utilities

For non-numeric discriminators (string tags like { tag: 'success' }, GraphQL __typename), use a general pattern-matching library like ts-pattern.