Class: Poodle::HttpClient

Inherits:
Object
  • Object
show all
Defined in:
lib/poodle/http_client.rb

Overview

HTTP client wrapper for Poodle API communication

Examples:

Basic usage

config = Poodle::Configuration.new(api_key: "your_api_key")
client = Poodle::HttpClient.new(config)
response = client.post("api/v1/send-email", email_data)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ HttpClient

Initialize a new HttpClient

Parameters:



28
29
30
31
# File 'lib/poodle/http_client.rb', line 28

def initialize(config)
  @config = config
  @connection = build_connection
end

Instance Attribute Details

#configConfiguration (readonly)

Returns the configuration object.

Returns:



23
24
25
# File 'lib/poodle/http_client.rb', line 23

def config
  @config
end

Instance Method Details

#build_connectionFaraday::Connection (private)

Build the Faraday connection

Returns:

  • (Faraday::Connection)

    the configured connection



126
127
128
129
130
131
132
133
# File 'lib/poodle/http_client.rb', line 126

def build_connection
  Faraday.new do |conn|
    configure_connection_middleware(conn)
    configure_connection_timeouts(conn)
    configure_connection_headers(conn)
    configure_custom_options(conn)
  end
end

#configure_connection_headers(conn) ⇒ Object (private)

Configure connection headers

Parameters:

  • conn (Faraday::Connection)

    the connection



155
156
157
158
159
160
# File 'lib/poodle/http_client.rb', line 155

def configure_connection_headers(conn)
  conn.headers["Authorization"] = "Bearer #{@config.api_key}"
  conn.headers["Content-Type"] = "application/json"
  conn.headers["Accept"] = "application/json"
  conn.headers["User-Agent"] = @config.user_agent
end

#configure_connection_middleware(conn) ⇒ Object (private)

Configure connection middleware

Parameters:

  • conn (Faraday::Connection)

    the connection



138
139
140
141
142
# File 'lib/poodle/http_client.rb', line 138

def configure_connection_middleware(conn)
  conn.request :json
  conn.response :json, content_type: /\bjson$/
  conn.adapter Faraday.default_adapter
end

#configure_connection_timeouts(conn) ⇒ Object (private)

Configure connection timeouts

Parameters:

  • conn (Faraday::Connection)

    the connection



147
148
149
150
# File 'lib/poodle/http_client.rb', line 147

def configure_connection_timeouts(conn)
  conn.options.timeout = @config.timeout
  conn.options.open_timeout = @config.connect_timeout
end

#configure_custom_options(conn) ⇒ Object (private)

Configure custom HTTP options

Parameters:

  • conn (Faraday::Connection)

    the connection



165
166
167
168
169
# File 'lib/poodle/http_client.rb', line 165

def configure_custom_options(conn)
  @config.http_options.each do |key, value|
    conn.options[key] = value
  end
end

#extract_error_message(response) ⇒ String (private)

Extract error message from response

Parameters:

  • response (Faraday::Response)

    the HTTP response

Returns:

  • (String)

    the error message



288
289
290
291
292
293
# File 'lib/poodle/http_client.rb', line 288

def extract_error_message(response)
  body = response.body
  return "HTTP #{response.status} error" unless body.is_a?(Hash)

  body["message"] || body["error"] || "HTTP #{response.status} error"
end

#extract_validation_errors(body) ⇒ Hash (private)

Extract validation errors from response body

Parameters:

  • body (Hash)

    the response body

Returns:

  • (Hash)

    field-specific validation errors



299
300
301
302
303
304
305
306
307
# File 'lib/poodle/http_client.rb', line 299

def extract_validation_errors(body)
  return {} unless body.is_a?(Hash)

  errors = body["errors"] || body["validation_errors"] || {}
  return {} unless errors.is_a?(Hash)

  # Convert string values to arrays for consistency
  errors.transform_values { |v| Array(v) }
end

#get(endpoint, params = {}, headers = {}) ⇒ Hash

Send a GET request

Parameters:

  • endpoint (String)

    the API endpoint

  • params (Hash) (defaults to: {})

    query parameters

  • headers (Hash) (defaults to: {})

    additional headers

Returns:

  • (Hash)

    the parsed response data

Raises:



51
52
53
# File 'lib/poodle/http_client.rb', line 51

def get(endpoint, params = {}, headers = {})
  request(:get, endpoint, params, headers)
end

#handle_connection_failed_error(error) ⇒ Object (private)

Handle connection failed errors

Parameters:

  • error (Faraday::ConnectionFailed)

    the connection error

Raises:



112
113
114
115
116
117
118
119
120
121
# File 'lib/poodle/http_client.rb', line 112

def handle_connection_failed_error(error)
  if error.message.include?("SSL") || error.message.include?("certificate")
    raise NetworkError.ssl_error(error.message)
  elsif error.message.include?("resolve") || error.message.include?("DNS")
    host = URI.parse(@config.base_url).host
    raise NetworkError.dns_resolution_failed(host)
  else
    raise NetworkError.connection_failed(@config.base_url, original_error: error)
  end
end

#handle_error_response(response) ⇒ Object (private)

Handle error responses

Parameters:

  • response (Faraday::Response)

    the HTTP response

Raises:



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/poodle/http_client.rb', line 194

def handle_error_response(response)
  case response.status
  when 400
    handle_validation_error(response)
  when 401
    raise AuthenticationError.invalid_api_key
  when 402
    handle_payment_error(response)
  when 403
    handle_forbidden_error(response)
  when 422
    handle_validation_error(response, status_code: 422)
  when 429
    raise RateLimitError.from_headers(response.headers)
  when 500..599
    handle_server_error(response)
  else
    raise NetworkError.http_error(response.status, extract_error_message(response))
  end
end

#handle_forbidden_error(response) ⇒ Object (private)

Handle forbidden errors (403)

Parameters:

  • response (Faraday::Response)

    the HTTP response

Raises:



251
252
253
254
255
256
257
258
259
260
261
# File 'lib/poodle/http_client.rb', line 251

def handle_forbidden_error(response)
  body = response.body || {}
  message = extract_error_message(response)

  raise ForbiddenError.insufficient_permissions unless message.include?("suspended")

  # Extract suspension details if available
  reason = body["reason"] || "unknown"
  rate = body["rate"]
  raise ForbiddenError.(reason, rate)
end

#handle_payment_error(response) ⇒ Object (private)

Handle payment errors (402)

Parameters:

  • response (Faraday::Response)

    the HTTP response

Raises:



232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/poodle/http_client.rb', line 232

def handle_payment_error(response)
  message = extract_error_message(response)

  case message
  when /subscription.*expired/i
    raise PaymentError.subscription_expired
  when /trial.*limit/i
    raise PaymentError.trial_limit_reached
  when /monthly.*limit/i
    raise PaymentError.monthly_limit_reached
  else
    raise PaymentError, message
  end
end

#handle_response(response) ⇒ Hash (private)

Handle HTTP response

Parameters:

  • response (Faraday::Response)

    the HTTP response

Returns:

  • (Hash)

    the parsed response data

Raises:



176
177
178
179
180
# File 'lib/poodle/http_client.rb', line 176

def handle_response(response)
  return response.body || {} if success_response?(response)

  handle_error_response(response)
end

#handle_server_error(response) ⇒ Object (private)

Handle server errors (5xx)

Parameters:

  • response (Faraday::Response)

    the HTTP response

Raises:



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/poodle/http_client.rb', line 267

def handle_server_error(response)
  message = extract_error_message(response)

  case response.status
  when 500
    raise ServerError.internal_server_error(message)
  when 502
    raise ServerError.bad_gateway(message)
  when 503
    raise ServerError.service_unavailable(message)
  when 504
    raise ServerError.gateway_timeout(message)
  else
    raise ServerError.new(message, status_code: response.status)
  end
end

#handle_validation_error(response, status_code: 400) ⇒ Object (private)

Handle validation errors (400, 422)

Parameters:

  • response (Faraday::Response)

    the HTTP response

  • status_code (Integer) (defaults to: 400)

    the HTTP status code

Raises:



220
221
222
223
224
225
226
# File 'lib/poodle/http_client.rb', line 220

def handle_validation_error(response, status_code: 400)
  body = response.body || {}
  message = extract_error_message(response)
  errors = extract_validation_errors(body)

  raise ValidationError.new(message, errors: errors, status_code: status_code)
end

#log_request(method, url, data) ⇒ Object (private)

Log HTTP request for debugging

Parameters:

  • method (Symbol)

    the HTTP method

  • url (String)

    the request URL

  • data (Hash)

    the request data



314
315
316
317
# File 'lib/poodle/http_client.rb', line 314

def log_request(method, url, data)
  puts "[Poodle] #{method.upcase} #{url}"
  puts "[Poodle] Request: #{data.to_json}" unless data.empty?
end

#log_response(response) ⇒ Object (private)

Log HTTP response for debugging

Parameters:

  • response (Faraday::Response)

    the HTTP response



322
323
324
325
# File 'lib/poodle/http_client.rb', line 322

def log_response(response)
  puts "[Poodle] Response: #{response.status}"
  puts "[Poodle] Body: #{response.body}" if response.body
end

#perform_request(method, url, data, headers) ⇒ Faraday::Response (private)

Perform the actual HTTP request

Parameters:

  • method (Symbol)

    the HTTP method

  • url (String)

    the request URL

  • data (Hash)

    the request data

  • headers (Hash)

    additional headers

Returns:

  • (Faraday::Response)

    the HTTP response



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/poodle/http_client.rb', line 91

def perform_request(method, url, data, headers)
  case method
  when :post
    @connection.post(url) do |req|
      req.headers.update(headers)
      req.body = data.to_json
    end
  when :get
    @connection.get(url) do |req|
      req.headers.update(headers)
      req.params = data
    end
  else
    raise ArgumentError, "Unsupported HTTP method: #{method}"
  end
end

#post(endpoint, data = {}, headers = {}) ⇒ Hash

Send a POST request

Parameters:

  • endpoint (String)

    the API endpoint

  • data (Hash) (defaults to: {})

    the request data

  • headers (Hash) (defaults to: {})

    additional headers

Returns:

  • (Hash)

    the parsed response data

Raises:



40
41
42
# File 'lib/poodle/http_client.rb', line 40

def post(endpoint, data = {}, headers = {})
  request(:post, endpoint, data, headers)
end

#request(method, endpoint, data = {}, headers = {}) ⇒ Hash (private)

Send an HTTP request

Parameters:

  • method (Symbol)

    the HTTP method

  • endpoint (String)

    the API endpoint

  • data (Hash) (defaults to: {})

    the request data or query parameters

  • headers (Hash) (defaults to: {})

    additional headers

Returns:

  • (Hash)

    the parsed response data

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/poodle/http_client.rb', line 65

def request(method, endpoint, data = {}, headers = {})
  url = @config.url_for(endpoint)

  log_request(method, url, data) if @config.debug?

  response = perform_request(method, url, data, headers)

  log_response(response) if @config.debug?

  handle_response(response)
rescue Faraday::TimeoutError => e
  puts "TimeoutError: #{e.message}"
  raise NetworkError.connection_timeout(@config.timeout)
rescue Faraday::ConnectionFailed => e
  handle_connection_failed_error(e)
rescue Faraday::Error => e
  raise NetworkError.connection_failed(@config.base_url, original_error: e)
end

#success_response?(response) ⇒ Boolean (private)

Check if response is successful

Parameters:

  • response (Faraday::Response)

    the HTTP response

Returns:

  • (Boolean)

    true if successful



186
187
188
# File 'lib/poodle/http_client.rb', line 186

def success_response?(response)
  (200..299).cover?(response.status)
end