diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-26 12:42:08 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-26 12:42:08 -0300 |
| commit | 7fd555fcaf4304cb0dff3e8f5661565c3618299e (patch) | |
| tree | 61199793c3046d73b97e9822895ede2a33034d41 /cepis/errors.lua | |
| download | lua-7fd555fcaf4304cb0dff3e8f5661565c3618299e.tar.gz | |
Initial importv0.1.0
Diffstat (limited to 'cepis/errors.lua')
| -rw-r--r-- | cepis/errors.lua | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/cepis/errors.lua b/cepis/errors.lua new file mode 100644 index 0000000..0076b95 --- /dev/null +++ b/cepis/errors.lua @@ -0,0 +1,115 @@ +--- Error values returned by the `nil, err` convention. +-- +-- Every failure this library expects to happen is returned, never thrown: +-- `local address, err = client:lookup(cep)`. Only programmer mistakes — a bad +-- option table, a wrong argument type where no sensible value exists — raise +-- through `error()`. +-- +-- An error is a table with a `kind` field, so a caller branches on one string +-- instead of matching messages. The split is by **the decision the caller has +-- to make**, not by the HTTP status that caused it: +-- +-- - `invalid_cep` and `not_found` are problems with the *request*. Retrying is +-- pointless — the same CEP will fail again. +-- - `network_error`, `server_error` and `rate_limited` are problems with +-- *transport*. Retrying may help, and the client already retried on its own +-- before returning any of them. +-- +-- That split is why `not_found` is not "the HTTP 404 error": the client also +-- returns `invalid_cep` without making any request at all, when the CEP is +-- malformed before it ever leaves the machine. +-- +-- @module cepis.errors + +local M = {} + +--- The CEP is not 8 digits. Usually returned without any request being made. +M.INVALID_CEP = "invalid_cep" + +--- The CEP is well-formed but does not exist in the DNE database. +M.NOT_FOUND = "not_found" + +--- The API (or the edge in front of it) answered 429 on every attempt. +M.RATE_LIMITED = "rate_limited" + +--- The API answered 5xx on every attempt. +M.SERVER_ERROR = "server_error" + +--- The request never completed: DNS, refused connection, timeout, TLS. +M.NETWORK_ERROR = "network_error" + +--- 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. +M.INVALID_RESPONSE = "invalid_response" + +--- Every kind, keyed by itself, for validation and for iterating. +M.kinds = { + [M.INVALID_CEP] = true, + [M.NOT_FOUND] = true, + [M.RATE_LIMITED] = true, + [M.SERVER_ERROR] = true, + [M.NETWORK_ERROR] = true, + [M.INVALID_RESPONSE] = true, +} + +local RETRIABLE = { + [M.RATE_LIMITED] = true, + [M.SERVER_ERROR] = true, + [M.NETWORK_ERROR] = true, +} + +--- Metatable of every error this library returns. +-- @type Error +local Error = {} +Error.__index = Error +Error.__name = "cepis.Error" + +function Error.__tostring(self) + return self.message +end + +--- Whether trying the same call again later could plausibly succeed. +-- +-- True for `rate_limited`, `server_error` and `network_error`; false for +-- `invalid_cep`, `not_found` and `invalid_response`. The client has already +-- exhausted its own attempts by the time it returns any of them, so this +-- answers "should I reschedule this work?", not "should I loop right now?". +-- @treturn boolean +function Error:is_retriable() + return RETRIABLE[self.kind] == true +end + +M.Error = Error + +--- Build an error value. +-- +-- @tparam string kind one of the six constants in this module +-- @tparam string message human-readable, already complete +-- @tparam[opt] table fields extra context: `cep`, `status`, `retry_after` +-- @treturn table the error, with `tostring()` yielding `message` +function M.new(kind, message, fields) + local err = setmetatable({}, Error) + if fields then + for key, value in pairs(fields) do + err[key] = value + end + end + err.kind = kind + err.message = message + return err +end + +--- Whether a value is one of this library's errors. +-- +-- Use it to tell a `cepis` error apart from any other value a `nil, err` +-- boundary might hand you. +-- @param value anything +-- @treturn boolean +function M.is(value) + return getmetatable(value) == Error +end + +return M |