Class: Unitpost::HttpClient
- Inherits:
-
Object
- Object
- Unitpost::HttpClient
- Defined in:
- lib/unitpost/http_client.rb
Overview
HTTP transport — the one place that talks to the Unitpost API.
Hand-written (not generated): auth, the required User-Agent, idempotency, query building, JSON encode/decode, and turning a non-2xx response into a typed Unitpost::Error. Resource methods (resources.rb) call through here.
Constant Summary collapse
- SDK_VERSION =
Unitpost::VERSION
- DEFAULT_BASE_URL =
The live API host. Matches the OpenAPI servers.url and the app's own brand.ts FALLBACK_APP_ORIGIN. SINGLE HOST: the API lives on the same origin as the app (www.unitpost.com) under /api/v1 — no api. subdomain. Override per-client with base_url / UNITPOST_BASE_URL.
"https://www.unitpost.com"
Instance Method Summary collapse
-
#initialize(api_key: nil, base_url: nil, timeout: 30, headers: {}, adapter: nil, stubs: nil, max_retries: 2, retry_base_delay: 0.5, max_retry_delay: 20.0, sleeper: nil) ⇒ HttpClient
constructor
A new instance of HttpClient.
-
#request(http_method, path, query: nil, body: nil, idempotency_key: nil) ⇒ Object
Make a request, retrying transient failures (429, 5xx, timeouts, network errors) with backoff.
Constructor Details
#initialize(api_key: nil, base_url: nil, timeout: 30, headers: {}, adapter: nil, stubs: nil, max_retries: 2, retry_base_delay: 0.5, max_retry_delay: 20.0, sleeper: nil) ⇒ HttpClient
Returns a new instance of HttpClient.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/unitpost/http_client.rb', line 23 def initialize(api_key: nil, base_url: nil, timeout: 30, headers: {}, adapter: nil, stubs: nil, max_retries: 2, retry_base_delay: 0.5, max_retry_delay: 20.0, sleeper: nil) key = api_key || ENV.fetch("UNITPOST_API_KEY", nil) if key.nil? || key.empty? raise Unitpost::Error.new( code: "missing_api_key", message: "No API key provided. Pass Unitpost::Client.new(api_key:) or set the UNITPOST_API_KEY environment variable. Create a key at https://www.unitpost.com → Settings → API keys. Full reference: https://www.unitpost.com/docs", status: 0 ) end @api_key = key base = (base_url || ENV["UNITPOST_BASE_URL"] || DEFAULT_BASE_URL).sub(%r{/\z}, "") @prefix = "/api/#{Unitpost::Generated::API_VERSION}" @extra_headers = headers # Automatic retry policy for transient failures (429, 5xx, timeouts, # network errors). max_retries: 0 disables it. See SECURITY.md §6. @max_retries = [max_retries, 0].max @retry_base_delay = retry_base_delay @max_retry_delay = max_retry_delay @sleeper = sleeper || ->(seconds) { sleep(seconds) } @conn = Faraday.new(url: base) do |f| f..timeout = timeout if stubs f.adapter(:test, stubs) else f.adapter(adapter || Faraday.default_adapter) end end end |
Instance Method Details
#request(http_method, path, query: nil, body: nil, idempotency_key: nil) ⇒ Object
Make a request, retrying transient failures (429, 5xx, timeouts, network errors) with backoff. A 429/503 Retry-After is honored (clamped to max_retry_delay); otherwise exponential backoff with full jitter. 4xx (other than 429) is never retried. A request is only retried when it's safe to replay: a GET, or a write carrying an Idempotency-Key (deduped server-side). A keyless write is never auto-retried, so a lost response can't double-send.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/unitpost/http_client.rb', line 61 def request(http_method, path, query: nil, body: nil, idempotency_key: nil) retry_safe = http_method == "GET" || !idempotency_key.nil? attempt = 0 loop do result, retry_after = attempt_request( http_method, path, query: query, body: body, idempotency_key: idempotency_key ) err = result.error retryable = !err.nil? && (err.status.zero? || retryable_status?(err.status)) return result if err.nil? || !retryable || !retry_safe || attempt >= @max_retries @sleeper.call(backoff_delay(attempt, retry_after)) attempt += 1 end end |