Class: Quolle::Client
- Inherits:
-
Object
- Object
- Quolle::Client
- Defined in:
- lib/quolle/client.rb
Overview
Constant Summary collapse
- DEFAULT_BASE_URL =
"https://api.quolle.com"
Instance Attribute Summary collapse
- #emails ⇒ Quolle::Resources::Emails readonly
- #webhooks ⇒ Quolle::Resources::Webhooks readonly
Instance Method Summary collapse
-
#initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: 30, max_retries: 3) ⇒ Client
constructor
A new instance of Client.
-
#request(method, path, body: nil, headers: {}) ⇒ Hash
Perform an authenticated request and return the parsed JSON body.
Constructor Details
#initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: 30, max_retries: 3) ⇒ Client
Returns a new instance of Client.
35 36 37 38 39 40 41 42 43 44 |
# File 'lib/quolle/client.rb', line 35 def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: 30, max_retries: 3) @api_key = api_key || ENV["QUOLLE_API_KEY"] raise ArgumentError, "A Quolle API key is required (pass api_key: or set QUOLLE_API_KEY)" if @api_key.nil? || @api_key.empty? @base_url = base_url.sub(%r{/+\z}, "") @timeout = timeout @max_retries = max_retries @emails = Resources::Emails.new(self) @webhooks = Resources::Webhooks.new(self) end |
Instance Attribute Details
#emails ⇒ Quolle::Resources::Emails (readonly)
27 28 29 |
# File 'lib/quolle/client.rb', line 27 def emails @emails end |
#webhooks ⇒ Quolle::Resources::Webhooks (readonly)
29 30 31 |
# File 'lib/quolle/client.rb', line 29 def webhooks @webhooks end |
Instance Method Details
#request(method, path, body: nil, headers: {}) ⇒ Hash
Perform an authenticated request and return the parsed JSON body.
Transient failures (HTTP 429 and 5xx, plus network errors) are retried with exponential backoff, honoring Retry-After on 429. To avoid double-sending, a POST is only retried on a 5xx/network error when an Idempotency-Key is present; a 429 (never processed) is always safe to retry.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/quolle/client.rb', line 55 def request(method, path, body: nil, headers: {}) uri = URI.parse("#{@base_url}#{path}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == "https" http.open_timeout = @timeout http.read_timeout = @timeout klass = { "GET" => Net::HTTP::Get, "POST" => Net::HTTP::Post, "DELETE" => Net::HTTP::Delete }.fetch(method.upcase) idempotent = %w[GET DELETE].include?(method.upcase) has_idem = headers.keys.any? { |k| k.to_s.downcase == "idempotency-key" } last_error = nil (0..@max_retries).each do |attempt| req = klass.new(uri.request_uri) req["Authorization"] = "Bearer #{@api_key}" req["Content-Type"] = "application/json" req["Accept"] = "application/json" req["User-Agent"] = "quolle-ruby/#{Quolle::VERSION}" headers.each { |k, v| req[k] = v } req.body = JSON.generate(body) unless body.nil? begin resp = http.request(req) rescue StandardError => e last_error = Quolle::Error.new("Network error: #{e.}") if attempt < @max_retries && (idempotent || has_idem) sleep(backoff(attempt, nil)) next end raise last_error end parsed = resp.body.to_s.empty? ? {} : (JSON.parse(resp.body) rescue {}) status = resp.code.to_i if (200..299).cover?(status) return parsed end = parsed.delete("error") || "Request failed with status #{status}" last_error = Quolle::Error.new(, status_code: status, data: parsed) retryable = status == 429 || ((500..504).cover?(status) && (idempotent || has_idem)) if attempt < @max_retries && retryable sleep(backoff(attempt, resp["Retry-After"])) next end raise last_error end raise(last_error || Quolle::Error.new("Request failed")) end |