Skip to content
markpaper

src/rest/errors.ts

v0.2.1 · 1.8 KB

Download file
// Error classes of the REST read path. A caller must tell "the exchange answered" (HTTP error, a
// known outcome) from "the request may or may not have arrived" (transport) and from "the response
// cannot be trusted" (read/parse). For reads all three mean "not read"; for writes (signer module)
// the distinction decides whether a retry is safe.

/** The exchange answered with a non-2xx status. 4xx is never retried (429 included; see the knowledge base). */
export class LighterHttpError extends Error {
  readonly status: number;
  readonly path: string;
  /** Response body, truncated. */
  readonly body: string;
  constructor(status: number, path: string, body = '') {
    super(`HTTP ${status} on ${path.split('?')[0]}`);
    this.name = 'LighterHttpError';
    this.status = status;
    this.path = path;
    this.body = body.slice(0, 500);
  }
}

/** Network failure or timeout after all retries. */
export class LighterTransportError extends Error {
  readonly path: string;
  readonly attempts: number;
  constructor(path: string, attempts: number, message: string, options?: { cause?: unknown }) {
    super(`${path.split('?')[0]} failed after ${attempts} attempt(s): ${message}`, options);
    this.name = 'LighterTransportError';
    this.path = path;
    this.attempts = attempts;
  }
}

/** A 2xx response whose body cannot be trusted as a whole (fail-closed decoding). */
export class LighterReadError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'LighterReadError';
  }
}

/** True for HTTP 5xx, timeouts and network errors: worth a retry on a read. */
export function isRetryableReadError(err: unknown): boolean {
  if (err instanceof LighterHttpError) return err.status >= 500;
  if (err instanceof LighterReadError) return false;
  return true;
}
All files