summaryrefslogtreecommitdiffstats
path: root/src/Client.php
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:31:30 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:31:30 -0300
commit67276c6444a70b61030ab3ee52b84dc5014af0e8 (patch)
treeac0246ed287c6aec57e335403b20b23214ec4eb0 /src/Client.php
parent64764fb9fac0227f8e0443e7c7519d73aec275b9 (diff)
downloadphp-0.2.0.tar.gz
Carry the municipality of a not-found CEP on the exceptionHEADv0.2.0main
The API now sends an optional faixa block on the 404 of GET /{cep}, naming the municipality the CEP would have belonged to. That failing CEP is rarely random: it is usually someone who knows the city and guessed the number, most often the -000 that 957 municipalities never received. Knowing the lookup failed "in Areal/RJ" is the difference between a message a user can act on and a bare "CEP not found". It stays an error. Faixa hangs off CepNotFoundException, never off Address, and parsing is deliberately forgiving: a missing, empty or malformed block yields null rather than turning a clean not-found into a parse failure. Its cep_ini/cep_fim are the CEPs that exist, not the bounds the Correios publish, and the interval between them is sparse -- hence ceps, and hence the docblocks insisting the pair is not a list of valid CEPs.
Diffstat (limited to 'src/Client.php')
-rw-r--r--src/Client.php36
1 files changed, 32 insertions, 4 deletions
diff --git a/src/Client.php b/src/Client.php
index 4e24b7f..5fd19b1 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -30,7 +30,7 @@ namespace Cepis;
*/
final class Client
{
- public const VERSION = '0.1.0';
+ public const VERSION = '0.2.0';
/**
* Where the API lives, unless `baseUrl` says otherwise.
@@ -171,6 +171,12 @@ final class Client
* the API's own message: the format already passed local validation, so
* the server knows something this client does not.
*
+ * A 404 is surfaced as {@see CepNotFoundException}, which may carry a
+ * {@see Faixa} naming the municipality the CEP would have belonged to. It
+ * is still a 404 either way: the `faixa` is context for the error, never an
+ * address, and a CEP that has one is just as absent from the DNE as one
+ * that does not.
+ *
* @throws InvalidCepException the CEP is not 8 digits (no request is made).
* @throws CepNotFoundException well-formed, but not in the database.
* @throws RateLimitedException 429 after every attempt.
@@ -184,9 +190,13 @@ final class Client
[$status, $body] = $this->request('/' . $number);
if ($status === 404) {
- throw new CepNotFoundException(
- sprintf('CEP %s does not exist in the DNE database', $number),
- );
+ $faixa = self::errorFaixa($body);
+ $message = sprintf('CEP %s does not exist in the DNE database', $number);
+ if ($faixa !== null) {
+ $message .= sprintf(' (the range covering it is %s)', $faixa->label());
+ }
+
+ throw new CepNotFoundException($message, $faixa);
}
if ($status === 400) {
throw new InvalidCepException(
@@ -390,6 +400,24 @@ final class Client
}
/**
+ * Pull the optional `faixa` block out of a 404 body, or `null`.
+ *
+ * Nothing in here throws, by design. The body of a not-found is extra
+ * information on top of an answer the caller already has, so a truncated
+ * response, an HTML error page from a proxy or a `faixa` in a shape this
+ * release does not know must all end the same way: no `faixa`, and the same
+ * {@see CepNotFoundException} that would have been thrown anyway. Note the
+ * plain `json_decode` — {@see Client::decodeJsonObject()} is deliberately
+ * not used here, because its whole job is to throw.
+ */
+ private static function errorFaixa(string $body): ?Faixa
+ {
+ $data = json_decode($body, true);
+
+ return is_array($data) ? Faixa::fromArray($data['faixa'] ?? null) : null;
+ }
+
+ /**
* Parse the `Retry-After` header in both RFC 9110 forms.
*
* It may arrive as a number of seconds (`"120"`) or as an HTTP date