SyncErrors.transient?/1 only had explicit clauses for the {:http_error, ...}
shape, but vendor API clients return {:unexpected_status, status, body}. A 502
was retried only by accident via the catch-all, and a 4xx in that shape would
have been retried 20 times. Add explicit {:unexpected_status, ...} clauses: 5xx
is transient (Oban backs off and retries), 4xx is permanent.
32 lines
1.5 KiB
Elixir
32 lines
1.5 KiB
Elixir
defmodule Towerops.Workers.SyncErrors do
|
|
@moduledoc """
|
|
Classifier for vendor-sync error reasons.
|
|
|
|
Sync workers (UISP, Preseem, cnMaestro, etc.) call third-party APIs that
|
|
can fail for permanent reasons (bad credentials, missing resources) or
|
|
transient ones (network blips, rate limits, 5xx). Permanent failures
|
|
should not be retried — they will keep failing until config is fixed.
|
|
Transient failures should retry so data doesn't go stale waiting on the
|
|
next cron cycle.
|
|
"""
|
|
|
|
@doc """
|
|
Returns `true` if the error reason is likely transient and worth retrying.
|
|
|
|
Unknown error shapes default to transient: stale monitoring data is the
|
|
bigger risk than an extra retry on a permanent failure.
|
|
"""
|
|
@spec transient?(term()) :: boolean()
|
|
def transient?(:unauthorized), do: false
|
|
def transient?(:forbidden), do: false
|
|
def transient?(:not_found), do: false
|
|
def transient?(:invalid_credentials), do: false
|
|
def transient?(:bad_request), do: false
|
|
def transient?({:http_error, status, _}) when status in 400..499, do: false
|
|
def transient?({:http_error, status}) when status in 400..499, do: false
|
|
def transient?({:unexpected_status, status, _}) when status in 400..499, do: false
|
|
def transient?({:unexpected_status, status}) when status in 400..499, do: false
|
|
def transient?({:unexpected_status, status, _}) when status in 500..599, do: true
|
|
def transient?({:unexpected_status, status}) when status in 500..599, do: true
|
|
def transient?(_), do: true
|
|
end
|