diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:31:55 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:31:55 -0300 |
| commit | 4ebb31f26efed74f8fcb049fe27ddf7cf5388a08 (patch) | |
| tree | f21190d570ea0e33f730f96c89288b0b2c6656a9 /src/client.ts | |
| parent | 986a16c34049187a6c1f39f38bfa59e327093494 (diff) | |
| download | typescript-4ebb31f26efed74f8fcb049fe27ddf7cf5388a08.tar.gz | |
A CEP that fails is rarely a random number: it is someone who knows the
city and guessed the digits, and the classic guess is the -000 that 957
municipalities never received. The API now answers the 404 with the
municipality that band belongs to, which is enough to tell the user
"Areal/RJ, but not that number" instead of a bare failure.
It stays an error. Faixa hangs off CEPNotFound rather than becoming a
result, cep_ini/cep_fim are documented as the CEPs that exist there and
not as a range to iterate, and an absent or unreadable block yields
undefined so that context can never cost the caller the clean not-found
it was already getting.
Diffstat (limited to 'src/client.ts')
| -rw-r--r-- | src/client.ts | 55 |
1 files changed, 44 insertions, 11 deletions
diff --git a/src/client.ts b/src/client.ts index c7dac15..3e48d7a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -19,7 +19,7 @@ import { RateLimited, ServerError, } from "./errors.js"; -import { Address, Health, type RawObject } from "./models.js"; +import { Address, Faixa, Health, type RawObject } from "./models.js"; import { VERSION } from "./version.js"; /** Where the public API lives. */ @@ -235,6 +235,19 @@ export class Client { * message: the format already passed local validation, so the server knows * something this client does not. * + * A CEP that does not exist throws {@link CEPNotFound}, which may carry a + * {@link Faixa} naming the municipality the number would belong to: + * + * ```ts + * try { + * await client.lookup("25845000"); + * } catch (error) { + * if (error instanceof CEPNotFound && error.faixa) { + * error.faixa.localidade; // "Areal" — still a 404, still no address + * } + * } + * ``` + * * @throws {InvalidCEP} the CEP is not 8 digits — no request is made. * @throws {CEPNotFound} well-formed, but not in the database. * @throws {RateLimited} 429 after every attempt. @@ -247,7 +260,13 @@ export class Client { const reply = await this.request(`/${number}`); if (reply.status === 404) { - throw new CEPNotFound(`CEP ${number} does not exist in the DNE database`, number); + const faixa = Faixa.fromJson(errorBody(reply.body)?.faixa); + const belongs = faixa === undefined ? "" : ` — that band belongs to ${faixa.label}`; + throw new CEPNotFound( + `CEP ${number} does not exist in the DNE database${belongs}`, + number, + faixa, + ); } if (reply.status === 400) { throw new InvalidCEP(errorMessage(reply.body, `CEP ${number} rejected`)); @@ -417,17 +436,31 @@ export class Client { } } -function errorMessage(body: string, fallback: string): string { +/** + * The body of an error response as an object, or `undefined` when it is not + * one. + * + * Never throws. An error response is already the bad news; a proxy answering + * with HTML in its place must not upgrade that into a second, different + * failure. + */ +function errorBody(body: string): RawObject | undefined { + let data: unknown; try { - const data: unknown = JSON.parse(body); - if (typeof data === "object" && data !== null && !Array.isArray(data)) { - const message = (data as RawObject).mensagem; - if (typeof message === "string" && message !== "") { - return message; - } - } + data = JSON.parse(body); } catch { - return fallback; + return undefined; + } + if (typeof data !== "object" || data === null || Array.isArray(data)) { + return undefined; + } + return data as RawObject; +} + +function errorMessage(body: string, fallback: string): string { + const message = errorBody(body)?.mensagem; + if (typeof message === "string" && message !== "") { + return message; } return fallback; } |