Module: PoliPage::Internal::HTTP Private

Defined in:
lib/poli_page/internal/http.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Pure transport helpers — no I/O, no socket access. Mirrors ‘sdk-node/src/internal/http.ts` (sdk-ruby-plan.md §13 Phase 1).

Constant Summary collapse

STATUS_MAP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{
  400 => PoliPage::ValidationError,
  401 => PoliPage::AuthenticationError,
  403 => PoliPage::PermissionDeniedError,
  404 => PoliPage::NotFoundError,
  410 => PoliPage::GoneError,
  429 => PoliPage::RateLimitError
}.freeze

Class Method Summary collapse

Class Method Details

.build_headers(method:, api_key:, idempotency_key:, user_agent:) ⇒ Hash{String=>String}

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:

  • method (Symbol)

    :get, :post, or :delete

  • api_key (String)
  • idempotency_key (String, nil)

    only used on POST

  • user_agent (String)

Returns:

  • (Hash{String=>String})


29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/poli_page/internal/http.rb', line 29

def build_headers(method:, api_key:, idempotency_key:, user_agent:)
  headers = {
    Constants::HEADER_ACCEPT        => "application/json",
    Constants::HEADER_AUTHORIZATION => "Bearer #{api_key}",
    Constants::HEADER_USER_AGENT    => user_agent
  }
  if method == :post
    headers[Constants::HEADER_CONTENT_TYPE] = "application/json"
    headers[Constants::HEADER_IDEMPOTENCY_KEY] = idempotency_key if idempotency_key
  end
  headers
end

.build_url(base, path) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the joined URL with exactly one slash between the two.

Parameters:

  • base (String)

    e.g. “api.poli.page

  • path (String)

    e.g. “/v1/render”

Returns:

  • (String)

    the joined URL with exactly one slash between the two



20
21
22
# File 'lib/poli_page/internal/http.rb', line 20

def build_url(base, path)
  "#{base.chomp("/")}/#{path.sub(%r{\A/}, "")}"
end

.classify(status:, code:, message:, request_id:) ⇒ PoliPage::Error

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Map an API response (status + parsed code/message) to the most specific ‘PoliPage::Error` subclass (sdk-ruby-plan.md §7).

Parameters:

  • status (Integer)
  • code (String)
  • message (String)
  • request_id (String, nil)

Returns:



108
109
110
111
# File 'lib/poli_page/internal/http.rb', line 108

def classify(status:, code:, message:, request_id:)
  klass = STATUS_MAP.fetch(status, PoliPage::APIError)
  klass.new(message, code: code, status: status, request_id: request_id)
end

.compute_backoff(attempt:, base_delay:, retry_after: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Compute the delay (in seconds) before the next retry attempt. When ‘retry_after` is non-nil, it is returned as-is (server-explicit, no jitter). Otherwise: `base_delay * 2**(attempt-1) * (0.5 + rand)`. `attempt` is 1-based: 1 means the first retry.



93
94
95
96
97
98
# File 'lib/poli_page/internal/http.rb', line 93

def compute_backoff(attempt:, base_delay:, retry_after: nil)
  return retry_after unless retry_after.nil?

  exp = base_delay * (2**(attempt - 1))
  exp * (0.5 + rand)
end

.parse_error_body(body, status) ⇒ Array(String, String)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse a non-2xx response body into a ‘[code, message]` pair. Falls back to `INTERNAL_ERROR` when the body is not parseable JSON. The fallback chain (`code → message → error → ’unknown_error’‘) is ported verbatim from Node `parseErrorBody` (sdk-ruby-plan.md §7.3).

Parameters:

  • body (String)
  • status (Integer)

Returns:

  • (Array(String, String))

    ‘[code, message]`



50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/poli_page/internal/http.rb', line 50

def parse_error_body(body, status)
  parsed = parse_json_or_nil(body)
  unless parsed.is_a?(Hash)
    return ["INTERNAL_ERROR",
            "HTTP #{status}: response body was not valid JSON"]
  end

  # RFC 7807: prefer `detail` (specific reason) over `title` (generic name)
  # over the legacy `message` field; fall back to a canned status string.
  # Code is verbatim from the API — never inferred from message.
  code = parsed.values_at("code", "error").compact.first || "unknown_error"
  message = parsed.values_at("detail", "title", "message").compact.first || "HTTP #{status}"
  [code, message]
end

.parse_json_or_nil(body) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



65
66
67
68
69
# File 'lib/poli_page/internal/http.rb', line 65

def parse_json_or_nil(body)
  JSON.parse(body)
rescue JSON::ParserError, TypeError
  nil
end

.parse_retry_after(header_value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse the ‘Retry-After` response header. Accepts an integer number of seconds or an HTTP-date. Returns the delay in seconds (Ruby’s ‘Kernel#sleep` and `Net::HTTP#*_timeout` units), capped at `RETRY_AFTER_CAP` (30 s). Returns `nil` when the header is missing or unparseable. Mirrors Node `parseRetryAfter` modulo unit (ms → s).



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/poli_page/internal/http.rb', line 76

def parse_retry_after(header_value)
  return nil if header_value.nil? || header_value.empty?

  return header_value.to_i.clamp(0, Constants::RETRY_AFTER_CAP) if /\A\d+\z/.match?(header_value)

  begin
    target = Time.httpdate(header_value)
  rescue ArgumentError
    return nil
  end
  (target - Time.now).floor.clamp(0, Constants::RETRY_AFTER_CAP)
end