Class: DigiwinDsp::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/digiwin_dsp/client.rb

Constant Summary collapse

RETRY_STATUSES =
[429, 500, 502, 503, 504].freeze
RETRY_MAX =

Exponential backoff: ~0.5s, ~1s, ~2s between attempts, ±50% jitter so multiple Rails workers retrying the same upstream blip don’t synchronize into a thundering herd against DSP.

3
RETRY_INTERVAL =
0.5
RETRY_BACKOFF_FACTOR =
2
RETRY_INTERVAL_RANDOMNESS =
0.5
USER_AGENT =
"digiwin_dsp/#{VERSION} (Faraday/#{Faraday::VERSION})".freeze
STATUS_ERROR_MAP =
{
  400 => ValidationError,
  401 => AuthenticationError,
  403 => AuthenticationError,
  409 => DuplicateRequestError,
  429 => RateLimitError
}.freeze
ENVELOPE_FAILURE_MAP =

Patterns DSP returns in the response body’s ‘Message` field on Status=Failure. The Chinese substrings are verbatim DSP responses — see docs/dsp-api-spec.md. Live DSP prepends the offending form_no to Message (e.g. “ORDER-123:Duplicated:訂單不可重複”), so we substring-match rather than anchor with A. Order matters: more-specific patterns first.

[
  [/Duplicated:/, DuplicateRequestError], # order already exists
  [/Processing:資料處理中/, RateLimitError], # transient; retry later
  [/Processing:取消訂單處理中/, ValidationError], # cancel in flight
  [/WrongStatus:/, ValidationError], # bad payload
  [/系統異常:/, ServerError] # DSP internal error
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(configuration: DigiwinDsp.configuration, authenticator: nil) ⇒ Client

Returns a new instance of Client.



39
40
41
42
# File 'lib/digiwin_dsp/client.rb', line 39

def initialize(configuration: DigiwinDsp.configuration, authenticator: nil)
  @configuration = configuration
  @authenticator = authenticator || Authenticator.new(configuration)
end

Instance Method Details

#post(path, body, idempotency_key: nil, headers: {}) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/digiwin_dsp/client.rb', line 44

def post(path, body, idempotency_key: nil, headers: {})
  @configuration.validate!
  sanitize_request_headers!(idempotency_key, headers)
  response = connection.post(normalize_path(path)) do |req|
    req.headers["X-Idempotency-Key"] = idempotency_key if idempotency_key
    headers.each { |k, v| req.headers[k] = v }
    req.body = body
  end
  handle_response(response)
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
  raise NetworkError, e.message
end