# Custom error classes exported

- **Pattern:** `ab-002358` (`sdk-package-quality.api-surface.error-classes`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002358
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002358)

## Why it matters

When every error a package throws is a plain `Error` with a string message, consumers are forced to parse error messages with regex or `includes()` checks — fragile code that breaks the moment you change wording. ISO 25010 fault-tolerance and testability both suffer: you cannot write a `catch (e) { if (e instanceof NetworkError)` guard without custom error types. For SDK packages that wrap external APIs, distinguishing a 429 from a 500 from a network timeout requires typed errors. Without them, consumers either swallow all errors or crash on recoverable conditions.

## Severity rationale

High because generic `Error` instances force consumers into string-parsing for error discrimination, producing fragile error handling that breaks on any message-text change.

## Remediation

Define and export a typed error hierarchy from `src/errors.ts`, then re-export it from your package entry point.

```ts
// src/errors.ts
export class SdkError extends Error {
  constructor(message: string, public readonly code: string) {
    super(message)
    this.name = 'SdkError'
  }
}

export class ApiError extends SdkError {
  constructor(message: string, public readonly statusCode: number) {
    super(message, 'API_ERROR')
    this.name = 'ApiError'
  }
}

export class RateLimitError extends ApiError {
  constructor(public readonly retryAfter: number) {
    super('Rate limit exceeded', 429)
    this.name = 'RateLimitError'
  }
}
```

```ts
// src/index.ts
export { SdkError, ApiError, RateLimitError } from './errors'
```

Consumers can then guard: `if (e instanceof RateLimitError) { await sleep(e.retryAfter * 1000) }`.

## Detection

- **ID:** `error-classes`
- **Severity:** `high`
- **What to look for:** Enumerate every error thrown by the SDK. For each error, search the codebase for error handling patterns:
  1. Custom error classes that extend `Error` (e.g., `class ApiError extends Error`)
  2. Whether those custom errors are exported from the public API entry point
  3. Whether errors thrown by the package are distinguishable from generic `Error` instances
  4. Whether error objects include useful properties (status code, error code, original error)
  For Python: check for custom exception classes. For Rust: check for custom error enums implementing `std::error::Error`.
- **Pass criteria:** The package defines at least one custom error class (or typed error) that extends the language's base error type, includes useful context (error code, message, cause), and is exported from the public API. Consumers can catch specific errors: `catch (e) { if (e instanceof YourApiError) { ... } }` — 100% of errors thrown by the SDK must be custom Error subclasses with descriptive names. Report: "X error types found, all Y are custom error classes."
- **Fail criteria:** The package throws only generic `Error('message')` or `throw new Error(...)` with no custom types. Consumers cannot programmatically distinguish between error types and must parse error message strings.
- **Skip (N/A) when:** The package is a pure utility library with no async operations, no I/O, and no failure modes beyond invalid arguments (e.g., a math library, a string formatting library). If the package only validates inputs, throwing `TypeError` is acceptable.
- **Detail on fail:** `"All errors thrown are generic Error instances with string messages. The API client throws 'Error: Request failed' for both network errors and 4xx responses. Consumers cannot distinguish between error types without parsing the message string."`
- **Remediation:** Custom error classes let consumers handle failures programmatically. This is especially important for SDK packages that wrap APIs.

  ```ts
  // src/errors.ts:
  export class SdkError extends Error {
    constructor(message: string, public readonly code: string) {
      super(message)
      this.name = 'SdkError'
    }
  }

  export class ApiError extends SdkError {
    constructor(
      message: string,
      public readonly statusCode: number,
      public readonly response?: unknown
    ) {
      super(message, 'API_ERROR')
      this.name = 'ApiError'
    }
  }

  export class RateLimitError extends ApiError {
    constructor(public readonly retryAfter: number) {
      super('Rate limit exceeded', 429)
      this.name = 'RateLimitError'
    }
  }
  ```

  ```ts
  // src/index.ts — export them:
  export { SdkError, ApiError, RateLimitError } from './errors'
  ```

## External references

- iso-25010 reliability.fault-tolerance
- iso-25010 maintainability.testability

Taxons: code-quality, user-experience

HTML version: https://auditbuffet.com/patterns/ab-002358
