Module: ActiveProject::Connections::HttpClient

Includes:
Base
Included in:
Pagination
Defined in:
lib/active_project/connections/http_client.rb

Constant Summary collapse

DEFAULT_HEADERS =
{
  "Content-Type" => "application/json",
  "Accept" => "application/json",
  "User-Agent" => -> { ActiveProject.user_agent }
}.freeze
DEFAULT_RETRY_OPTS =

Default retry configuration - can be overridden in adapter configs

{
  max: 3,                           # Maximum number of retries
  interval: 0.5,                    # Initial delay between retries (seconds)
  backoff_factor: 2,                # Exponential backoff multiplier
  retry_statuses: [ 429, 500, 502, 503, 504 ], # HTTP status codes to retry
  exceptions: [
    Faraday::TimeoutError,
    Faraday::ConnectionFailed,
    Faraday::SSLError
  ]
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Base

#parse_link_header

Instance Attribute Details

#connectionObject (readonly)

Returns the value of attribute connection.



12
13
14
# File 'lib/active_project/connections/http_client.rb', line 12

def connection
  @connection
end

#last_responseObject (readonly)

Returns the value of attribute last_response.



12
13
14
# File 'lib/active_project/connections/http_client.rb', line 12

def last_response
  @last_response
end

Instance Method Details

#build_connection(base_url:, auth_middleware:, extra_headers: {}, retry_options: {}) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/active_project/connections/http_client.rb', line 33

def build_connection(base_url:, auth_middleware:, extra_headers: {}, retry_options: {})
  @base_url = base_url

  # Merge custom retry options with defaults
  final_retry_opts = DEFAULT_RETRY_OPTS.merge(retry_options)

  @connection = Faraday.new(url: base_url) do |conn|
    auth_middleware.call(conn)                 # Let the adapter sprinkle its secret sauce here.
    conn.request  :retry, **final_retry_opts   # Intelligent retry with configurable options
    conn.response :raise_error                 # Yes, we want the failure loud and flaming.
    default_adapter = ENV.fetch("AP_DEFAULT_ADAPTER", "net_http").to_sym
    conn.adapter default_adapter
    conn.headers.merge!(DEFAULT_HEADERS.transform_values { |v| v.respond_to?(:call) ? v.call : v })
    conn.headers.merge!(extra_headers)         # Add your weird little header tweaks here.
    yield conn if block_given?                 # Optional: make it worse with your own block.
  end
end

#request(method, path, body: nil, query: nil, headers: {}) ⇒ Object

Sends the HTTP request like a brave little toaster.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/active_project/connections/http_client.rb', line 52

def request(method, path, body: nil, query: nil, headers: {})
  raise "HTTP connection not initialised" unless connection # You forgot to plug it in. Classic.

  json_body = if body.is_a?(String)
                body
  else
                (body ? JSON.generate(body) : nil)
  end
  response  = connection.run_request(method, path, json_body, headers) do |req|
    req.params.update(query) if query&.any?
  end

  @last_response = response

  return nil if response.status == 204 || response.body.to_s.empty?

  JSON.parse(response.body)
rescue Faraday::Error => e
  raise translate_http_error(e)
rescue JSON::ParserError => e
  raise ActiveProject::ApiError.new("Non-JSON response from #{path}",
                                    original_error: e,
                                    status_code: response&.status,
                                    response_body: response&.body)
end