Class: Attio::HttpClient Private

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

Overview

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

HTTP client for making API requests to Attio

This class handles the low-level HTTP communication with the Attio API, including request execution, response parsing, and error handling.

Since:

  • 1.0.0

Defined Under Namespace

Classes: ConnectionError, TimeoutError

Constant Summary collapse

DEFAULT_TIMEOUT =

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.

Since:

  • 1.0.0

30

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url:, headers: {}, timeout: DEFAULT_TIMEOUT, rate_limiter: nil) ⇒ HttpClient

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 a new instance of HttpClient.

Since:

  • 1.0.0



18
19
20
21
22
23
# File 'lib/attio/http_client.rb', line 18

def initialize(base_url:, headers: {}, timeout: DEFAULT_TIMEOUT, rate_limiter: nil)
  @base_url = base_url
  @headers = headers
  @timeout = timeout
  @rate_limiter = rate_limiter
end

Instance Attribute Details

#base_urlObject (readonly)

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.

Since:

  • 1.0.0



16
17
18
# File 'lib/attio/http_client.rb', line 16

def base_url
  @base_url
end

#headersObject (readonly)

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.

Since:

  • 1.0.0



16
17
18
# File 'lib/attio/http_client.rb', line 16

def headers
  @headers
end

#rate_limiterObject (readonly)

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.

Since:

  • 1.0.0



16
17
18
# File 'lib/attio/http_client.rb', line 16

def rate_limiter
  @rate_limiter
end

#timeoutObject (readonly)

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.

Since:

  • 1.0.0



16
17
18
# File 'lib/attio/http_client.rb', line 16

def timeout
  @timeout
end

Instance Method Details

#delete(path, params = 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.

Since:

  • 1.0.0



45
46
47
48
49
50
51
# File 'lib/attio/http_client.rb', line 45

def delete(path, params = nil)
  if params
    execute_request(:delete, path, body: params.to_json)
  else
    execute_request(:delete, path)
  end
end

#error_class_for_status(status) ⇒ Object (private)

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.

Since:

  • 1.0.0



115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/attio/http_client.rb', line 115

private def error_class_for_status(status)
  error_map = {
    401 => AuthenticationError,
    404 => NotFoundError,
    422 => ValidationError,
    429 => RateLimitError,
  }
  return error_map[status] if error_map.key?(status)
  return ServerError if (500..599).cover?(status)

  Error
end

#execute_request(method, path, options = {}) ⇒ Object (private)

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.

Since:

  • 1.0.0



53
54
55
56
57
58
# File 'lib/attio/http_client.rb', line 53

private def execute_request(method, path, options = {})
  # Use rate limiter if available
  return @rate_limiter.execute { perform_request(method, path, options) } if @rate_limiter

  perform_request(method, path, options)
end

#extract_rate_limit_headers(response) ⇒ Object (private)

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.

Since:

  • 1.0.0



150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/attio/http_client.rb', line 150

private def extract_rate_limit_headers(response)
  headers = {}
  response.headers.each do |key, value|
    case key.downcase
    when "x-ratelimit-limit"
      headers["x-ratelimit-limit"] = value
    when "x-ratelimit-remaining"
      headers["x-ratelimit-remaining"] = value
    when "x-ratelimit-reset"
      headers["x-ratelimit-reset"] = value
    end
  end
  headers
end

#extract_retry_after(response) ⇒ Object (private)

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.

Since:

  • 1.0.0



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/attio/http_client.rb', line 165

private def extract_retry_after(response)
  retry_after = response.headers["retry-after"] || response.headers["Retry-After"]
  return nil unless retry_after

  # Try parsing as integer (seconds) first
  parsed = retry_after.to_i
  # If to_i returns 0 but the string isn't "0", it means parsing failed
  return parsed if parsed > 0 || retry_after == "0"

  # If not a valid integer, could be HTTP date, default to 60 seconds
  60
rescue StandardError
  # If not an integer, could be HTTP date, default to 60 seconds
  60
end

#get(path, params = 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.

Since:

  • 1.0.0



25
26
27
28
29
30
31
# File 'lib/attio/http_client.rb', line 25

def get(path, params = nil)
  if params && !params.empty?
    execute_request(:get, path, params: params)
  else
    execute_request(:get, path)
  end
end

#handle_connection_error(response) ⇒ Object (private)

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.

Raises:

Since:

  • 1.0.0



93
94
95
96
97
# File 'lib/attio/http_client.rb', line 93

private def handle_connection_error(response)
  raise TimeoutError, "Request timed out" if response.timed_out?

  raise ConnectionError, "Connection failed: #{response.return_message}"
end

#handle_error_response(response) ⇒ Object (private)

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.

Raises:

  • (error_class)

Since:

  • 1.0.0



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/attio/http_client.rb', line 99

private def handle_error_response(response)
  error_class = error_class_for_status(response.code)
  message = parse_error_message(response)

  # Handle rate limit errors specially
  if response.code == 429
    retry_after = extract_retry_after(response)
    raise RateLimitError.new(message, retry_after: retry_after, response: response, code: response.code)
  end

  # Add status code to message for generic errors
  message = "Request failed with status #{response.code}: #{message}" if error_class == Error

  raise error_class, message
end

#handle_response(response) ⇒ Object (private)

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.

Since:

  • 1.0.0



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/attio/http_client.rb', line 80

private def handle_response(response)
  return handle_connection_error(response) if response.code == 0

  if (200..299).cover?(response.code)
    result = parse_json(response.body)
    # Add headers to result for rate limiter to process
    result["_headers"] = extract_rate_limit_headers(response) if @rate_limiter
    return result
  end

  handle_error_response(response)
end

#parse_error_message(response) ⇒ Object (private)

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.

Since:

  • 1.0.0



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/attio/http_client.rb', line 136

private def parse_error_message(response)
  body = begin
    parse_json(response.body)
  rescue StandardError
    response.body
  end

  if body.is_a?(Hash)
    body["error"] || body["message"] || body.to_s
  else
    body.to_s
  end
end

#parse_json(body) ⇒ Object (private)

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.

Since:

  • 1.0.0



128
129
130
131
132
133
134
# File 'lib/attio/http_client.rb', line 128

private def parse_json(body)
  return {} if body.nil? || body.empty?

  JSON.parse(body)
rescue JSON::ParserError => e
  raise Error, "Invalid JSON response: #{e.message}"
end

#patch(path, 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.

Since:

  • 1.0.0



37
38
39
# File 'lib/attio/http_client.rb', line 37

def patch(path, body = {})
  execute_request(:patch, path, body: body.to_json)
end

#perform_request(method, path, options = {}) ⇒ Object (private)

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.

Since:

  • 1.0.0



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/attio/http_client.rb', line 60

private def perform_request(method, path, options = {})
  url = "#{base_url}/#{path}"

  request_options = {
    method: method,
    headers: headers.merge("Content-Type" => "application/json"),
    timeout: timeout,
    connecttimeout: timeout,
    # SSL/TLS security settings
    ssl_verifypeer: true,
    ssl_verifyhost: 2,
    followlocation: false, # Prevent following redirects for security
  }.merge(options)

  request = Typhoeus::Request.new(url, request_options)
  response = request.run

  handle_response(response)
end

#post(path, 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.

Since:

  • 1.0.0



33
34
35
# File 'lib/attio/http_client.rb', line 33

def post(path, body = {})
  execute_request(:post, path, body: body.to_json)
end

#put(path, 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.

Since:

  • 1.0.0



41
42
43
# File 'lib/attio/http_client.rb', line 41

def put(path, body = {})
  execute_request(:put, path, body: body.to_json)
end