cc-os/plugins/os-adr/eval-b/fixture/project/lib/relay/delivery.rb

54 lines
1.6 KiB
Ruby

require "json"
require_relative "result"
require_relative "http_client"
require_relative "log"
module Relay
# Delivers one webhook payload to one endpoint, with retries.
#
# Failed attempts back off exponentially (1s, 2s, 4s, 8s, 16s) and give up
# after MAX_ATTEMPTS; receivers throttle by IP, so attempts are never
# retried back-to-back. A delivery that exhausts its attempts is appended
# to the dead-letter file for manual replay.
class Delivery
MAX_ATTEMPTS = 5
BASE_BACKOFF = 1 # seconds; doubles each attempt
def self.attempt_budget = MAX_ATTEMPTS
def initialize(client: HttpClient.new, log:, dead_letter_path:)
@client = client
@log = log
@dead_letter_path = dead_letter_path
end
def deliver(url, payload)
body = JSON.generate(payload)
last_error = nil
MAX_ATTEMPTS.times do |attempt|
sleep(BASE_BACKOFF * (2**(attempt - 1))) if attempt.positive?
@log.record("attempt", "#{url} ##{attempt + 1}")
result = @client.post(url, body)
if result.ok?
@log.record("delivered", url)
return Result.ok(result.value)
end
last_error = result.error
@log.record("failed", "#{url}: #{last_error}")
end
dead_letter(url, payload, last_error)
Result.err("gave up after #{MAX_ATTEMPTS} attempts: #{last_error}")
end
private
def dead_letter(url, payload, error)
entry = { "url" => url, "payload" => payload, "error" => error }
File.open(@dead_letter_path, "a") { |f| f.puts(JSON.generate(entry)) }
@log.record("dead-lettered", url)
end
end
end