Module: WhatsAppNotifier::ErrorCode
- Defined in:
- lib/whatsapp_notifier/error_code.rb
Overview
Stable, machine-readable classification of a failed send.
Before 0.9.0 every send failure surfaced as error_code :delivery_exception, so hosts had to fingerprint Result#error_message text to decide whether a retry was safe. These codes replace that. They are part of the public API and will not be renamed:
:auth_required session logged out / service refused upfront —
nothing was sent
:not_on_whatsapp the number has no WhatsApp account
:invalid_phone the number is malformed
:recipient_unresolved the service could not resolve the recipient id
(wedged session) — nothing was sent
:service_unreachable the TCP connection never opened — nothing was sent
:timeout the request went out but the answer never came, so
the outcome is UNKNOWN — a retry may double-send
:rate_limited the service throttled the send
:delivery_exception unclassified; the pre-0.9.0 catch-all, kept as the
fallback so hosts keying on it keep working
The first four plus :service_unreachable are "definitely not sent"; only :timeout and :delivery_exception leave the outcome ambiguous.
Constant Summary collapse
- UNCLASSIFIED =
:delivery_exception- ALL =
%i[ auth_required not_on_whatsapp invalid_phone recipient_unresolved service_unreachable timeout rate_limited delivery_exception ].freeze
- STATUS_CODES =
HTTP status → code, for a non-2xx answer from the service. Checked first: a status is exact where the body text is a guess. 422 is deliberately absent — the service 422s a malformed REQUEST (a host bug), not a bad number, so it falls through to the unclassified catch-all.
{ 401 => :auth_required, 403 => :auth_required, 429 => :rate_limited }.freeze
- EXCEPTION_CODES =
Exception class name → code. Matched on the name rather than the constant so this file never has to require net/http or resolve Errno.
{ "Errno::ECONNREFUSED" => :service_unreachable, "Errno::ECONNRESET" => :service_unreachable, "Errno::EHOSTUNREACH" => :service_unreachable, "Errno::ENETUNREACH" => :service_unreachable, "SocketError" => :service_unreachable, "Net::OpenTimeout" => :service_unreachable, "Net::ReadTimeout" => :timeout, "Net::WriteTimeout" => :timeout, "Timeout::Error" => :timeout }.freeze
- SERVICE_CODES =
Codes a service (or a third-party adapter) may hand us directly, folded into the vocabulary above. Several spellings map to one code because the Bun service has renamed its own codes across versions.
{ "auth_required" => :auth_required, "unauthenticated" => :auth_required, "unauthorized" => :auth_required, "session_expired" => :auth_required, "not_on_whatsapp" => :not_on_whatsapp, "number_not_on_whatsapp" => :not_on_whatsapp, "number_not_registered" => :not_on_whatsapp, "invalid_phone" => :invalid_phone, "invalid_number" => :invalid_phone, "recipient_unresolved" => :recipient_unresolved, "service_unreachable" => :service_unreachable, "timeout" => :timeout, "rate_limited" => :rate_limited, "throttled" => :rate_limited }.freeze
- MESSAGE_CODES =
Free-text fingerprints, the last resort: whatsapp-web.js failures reach us as an opaque 500 whose body is the library's own message, so text is all there is. Order matters — the first match wins.
[ [/\bno lid\b/i, :recipient_unresolved], [/not registered|not on whatsapp|not a valid whatsapp/i, :not_on_whatsapp], [/invalid wid|wid error|invalid (phone|number)/i, :invalid_phone], [/not authenticated|pair via qr|logged out|session expired|unauthorized/i, :auth_required], [/connection refused|failed to open tcp|network is unreachable|no route to host/i, :service_unreachable], [/execution expired|timed out|timeout/i, :timeout], [/rate limit|too many requests/i, :rate_limited] ].freeze
- CODE_SHAPE =
Bound on what a service-supplied code may look like before we pass it through unrecognized: an identifier, not arbitrary text.
/\A[a-z0-9_]{1,64}\z/
Class Method Summary collapse
-
.from_exception(exception) ⇒ Object
Classify the exception a transport raised.
-
.from_message(text) ⇒ Object
Classify a failure the transport reported without raising (a 200 body with success: false).
-
.normalize(value) ⇒ Object
Fold a code the service supplied into the vocabulary.
Class Method Details
.from_exception(exception) ⇒ Object
Classify the exception a transport raised. ServiceError carries the HTTP status, which beats guessing from the message.
102 103 104 105 106 107 |
# File 'lib/whatsapp_notifier/error_code.rb', line 102 def from_exception(exception) status_code = status_code_for(exception) return status_code if status_code EXCEPTION_CODES[exception.class.name] || (exception.) end |
.from_message(text) ⇒ Object
Classify a failure the transport reported without raising (a 200 body with success: false).
111 112 113 114 115 |
# File 'lib/whatsapp_notifier/error_code.rb', line 111 def (text) haystack = text.to_s MESSAGE_CODES.each { |pattern, code| return code if pattern.match?(haystack) } UNCLASSIFIED end |
.normalize(value) ⇒ Object
Fold a code the service supplied into the vocabulary. Returns nil for a blank code so callers can fall back to their own classification; an unrecognized but identifier-shaped code passes through so a newer service can introduce one without a gem release.
121 122 123 124 125 126 |
# File 'lib/whatsapp_notifier/error_code.rb', line 121 def normalize(value) text = value.to_s.strip.downcase return nil if text.empty? SERVICE_CODES[text] || (CODE_SHAPE.match?(text) ? text.to_sym : UNCLASSIFIED) end |