summaryrefslogtreecommitdiffstats
path: root/cepis/errors.lua
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 12:42:08 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 12:42:08 -0300
commit7fd555fcaf4304cb0dff3e8f5661565c3618299e (patch)
tree61199793c3046d73b97e9822895ede2a33034d41 /cepis/errors.lua
downloadlua-7fd555fcaf4304cb0dff3e8f5661565c3618299e.tar.gz
Initial importv0.1.0
Diffstat (limited to 'cepis/errors.lua')
-rw-r--r--cepis/errors.lua115
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