/** * Error hierarchy. * * Everything derives from {@link CepisError}, so callers who only care that * *something* went wrong can catch one type. * * Below that, the split is by **the decision the caller has to make**, not by * the HTTP status that caused it: * * - {@link InvalidCEP} and {@link CEPNotFound} are problems with the *request*. * Retrying is pointless — the same CEP will fail again. * - {@link NetworkError}, {@link ServerError} and {@link RateLimited} are * problems with *transport*. Retrying may help, and the client already * retried on its own before throwing any of them. * * That split is why `CEPNotFound` is not "the HTTP 404 error": the client also * throws `InvalidCEP` without making any request at all, when the CEP is * malformed before it ever leaves the machine. * * @module */ import type { Faixa } from "./models.js"; /** * Base of every error this library throws. Catch it to handle any failure. * * `name` is set on every subclass, so an error that crosses a serialisation * boundary (a worker, a log line, a JSON body) still says which one it was. */ export class CepisError extends Error { constructor(message: string, options?: ErrorOptions) { super(message, options); this.name = "CepisError"; } } /** * The CEP is not 8 digits. * * Usually thrown **without** a request ever being made — the client checks the * format before going to the network. It is also what a 400 from the API * becomes, carrying the API's own message. */ export class InvalidCEP extends CepisError { constructor(message: string, options?: ErrorOptions) { super(message, options); this.name = "InvalidCEP"; } } /** * The CEP is well-formed but does not exist in the DNE database. * * Never thrown by {@link Client.lookupMany}, which reports a missing CEP as * `null` at its position instead. * * `faixa` says which municipality the CEP *would* belong to, when the number * falls inside a band some municipality owns. It is context for the failure and * nothing more — this is still a 404, and the CEP still does not exist. */ export class CEPNotFound extends CepisError { /** The 8-digit CEP that was looked up. */ readonly cep: string; /** * The municipality the CEP would have belonged to, or `undefined` when it * falls in a band no municipality owns. * * Read {@link Faixa} before using it: `cep_ini`/`cep_fim` are the CEPs that * exist there, the range between them is sparse, and none of it makes the * looked-up CEP valid. */ readonly faixa?: Faixa; constructor(message: string, cep: string, faixa?: Faixa, options?: ErrorOptions) { super(message, options); this.name = "CEPNotFound"; this.cep = cep; this.faixa = faixa; } } /** * The request never completed: DNS, refused connection, timeout, TLS. * * Thrown only after every configured attempt has failed. The underlying * failure is kept in `cause`. */ export class NetworkError extends CepisError { constructor(message: string, options?: ErrorOptions) { super(message, options); this.name = "NetworkError"; } } /** The API answered 5xx on every attempt. */ export class ServerError extends CepisError { /** The HTTP status seen on the last attempt. */ readonly status: number; constructor(message: string, status: number, options?: ErrorOptions) { super(message, options); this.name = "ServerError"; this.status = status; } } /** * The API — or the edge in front of it — answered 429. * * The client already honoured `Retry-After` across the attempts it made; the * value is exposed here for callers who want to reschedule the work. */ export class RateLimited extends CepisError { /** * What the server asked for, in **milliseconds**, or `undefined` if it did * not say. Already clamped to nothing: it is the raw request, not the wait * the client applied. */ readonly retryAfterMs?: number; constructor(message: string, retryAfterMs?: number, options?: ErrorOptions) { super(message, options); this.name = "RateLimited"; this.retryAfterMs = retryAfterMs; } } /** * A success status came back with a body that cannot be parsed. * * In practice this means something between you and the API is answering * instead of it — a captive Wi-Fi portal, a corporate proxy, an HTML error * page. Useful as a diagnosis: the problem is not in your code. */ export class InvalidResponse extends CepisError { constructor(message: string, options?: ErrorOptions) { super(message, options); this.name = "InvalidResponse"; } }