summaryrefslogtreecommitdiffstats
path: root/src/client.ts
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:31:55 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:31:55 -0300
commit4ebb31f26efed74f8fcb049fe27ddf7cf5388a08 (patch)
treef21190d570ea0e33f730f96c89288b0b2c6656a9 /src/client.ts
parent986a16c34049187a6c1f39f38bfa59e327093494 (diff)
downloadtypescript-4ebb31f26efed74f8fcb049fe27ddf7cf5388a08.tar.gz
Carry the municipality of a CEP that does not existHEADv0.2.0main
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.ts55
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;
}