lago-ruby-client 1.44.0 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e05c7adf2d8d5d2dd6d5e8e9263908f415013356761245b1f22878b0fd0c7a2
4
- data.tar.gz: d89d43c70f055b2363ef08cb063d5910f1335257846292400b4e1f88e0ab882f
3
+ metadata.gz: b0d737a1ba08ae4f9d2a731af4cf55918555cfe91e1606fbef6126b830090e82
4
+ data.tar.gz: 06b6d2a32667abe5872075cb96081aedff4d0d07fa49e0a4158f594ee66f4803
5
5
  SHA512:
6
- metadata.gz: fdab377ffb956c42aec999bd2ca63fa4ceeb895f656497175b955b93d2d2adfa60e2b088ff3deedbf039fef3ca01fb4ecb2d98aa2afb27d74bcf9f9710737ace
7
- data.tar.gz: 0e52ca90f164179ac5e28c90e48a37ef1d0f57ffe47f35ba105df18857ed4035a4969fa9aff93de0291143bfd0bb5e2d23efb052b5db7e17cebd3502cbf46f9a
6
+ metadata.gz: fbb748d95cb04580ecba3e5340e5587c72f4b20ce629fefc06eeb8435c074cbde11e8874407bcdf3f0f32680e961ca553b1b6ae929d695357e2bf2d21ca13041
7
+ data.tar.gz: a0ef70eeb95aff16cef4492c9b2ba989323b7bae8cbc26b50a9cb3e098cd49ea54bee30fc83e6f4912f226eb5bfb538a3bb3e3f3c6829ec592b2e50bee474778
@@ -53,13 +53,15 @@ module Lago
53
53
  API_PATH = 'api/v1/'
54
54
 
55
55
  class Client
56
- attr_reader :api_key, :api_url, :use_ingest_service, :ingest_api_url
56
+ attr_reader :api_key, :api_url, :use_ingest_service, :ingest_api_url, :max_retries, :retry_on_rate_limit
57
57
 
58
- def initialize(api_key: nil, api_url: nil, use_ingest_service: false, ingest_api_url: nil)
58
+ def initialize(api_key: nil, api_url: nil, use_ingest_service: false, ingest_api_url: nil, **options)
59
59
  @api_key = api_key
60
60
  @api_url = api_url
61
61
  @use_ingest_service = use_ingest_service
62
62
  @ingest_api_url = ingest_api_url
63
+ @max_retries = options.fetch(:max_retries, 3)
64
+ @retry_on_rate_limit = options.fetch(:retry_on_rate_limit, true)
63
65
  end
64
66
 
65
67
  def base_api_url
@@ -6,84 +6,90 @@ module Lago
6
6
  module Api
7
7
  class Connection
8
8
  RESPONSE_SUCCESS_CODES = [200, 201, 202, 204].freeze
9
+ DEFAULT_MAX_RETRIES = 3
10
+ INITIAL_BACKOFF = 1
11
+ BACKOFF_MULTIPLIER = 2
12
+ MAX_RETRY_DELAY = 20
9
13
 
10
- def initialize(api_key, uri)
14
+ def initialize(api_key, uri, max_retries: DEFAULT_MAX_RETRIES, retry_on_rate_limit: true)
11
15
  @api_key = api_key
12
16
  @uri = uri
17
+ @max_retries = max_retries
18
+ @retry_on_rate_limit = retry_on_rate_limit
13
19
  end
14
20
 
15
21
  def post(body, path = uri.path)
16
- response = http_client.send_request(
17
- 'POST',
18
- path,
19
- prepare_payload(body),
20
- headers
21
- )
22
-
23
- handle_response(response)
22
+ execute_request do
23
+ http_client.send_request(
24
+ 'POST',
25
+ path,
26
+ prepare_payload(body),
27
+ headers
28
+ )
29
+ end
24
30
  end
25
31
 
26
32
  def put(path = uri.path, identifier:, body:)
27
33
  uri_path = identifier.nil? ? path : "#{path}/#{CGI.escapeURIComponent(identifier)}"
28
- response = http_client.send_request(
29
- 'PUT',
30
- uri_path,
31
- prepare_payload(body),
32
- headers
33
- )
34
-
35
- handle_response(response)
34
+ execute_request do
35
+ http_client.send_request(
36
+ 'PUT',
37
+ uri_path,
38
+ prepare_payload(body),
39
+ headers
40
+ )
41
+ end
36
42
  end
37
43
 
38
44
  def patch(path = uri.path, identifier:, body:)
39
45
  uri_path = identifier.nil? ? path : "#{path}/#{CGI.escapeURIComponent(identifier)}"
40
- response = http_client.send_request(
41
- 'PATCH',
42
- uri_path,
43
- prepare_payload(body),
44
- headers
45
- )
46
-
47
- handle_response(response)
46
+ execute_request do
47
+ http_client.send_request(
48
+ 'PATCH',
49
+ uri_path,
50
+ prepare_payload(body),
51
+ headers
52
+ )
53
+ end
48
54
  end
49
55
 
50
56
  def get(path = uri.path, identifier:)
51
57
  uri_path = identifier.nil? ? path : "#{path}/#{CGI.escapeURIComponent(identifier)}"
52
- response = http_client.send_request(
53
- 'GET',
54
- uri_path,
55
- prepare_payload(nil),
56
- headers
57
- )
58
-
59
- handle_response(response)
58
+ execute_request do
59
+ http_client.send_request(
60
+ 'GET',
61
+ uri_path,
62
+ prepare_payload(nil),
63
+ headers
64
+ )
65
+ end
60
66
  end
61
67
 
62
68
  def destroy(path = uri.path, identifier:, options: nil)
63
69
  uri_path = path
64
70
  uri_path += "/#{CGI.escapeURIComponent(identifier)}" if identifier
65
71
  uri_path += "?#{URI.encode_www_form(options)}" unless options.nil?
66
- response = http_client.send_request(
67
- 'DELETE',
68
- uri_path,
69
- prepare_payload(nil),
70
- headers
71
- )
72
-
73
- handle_response(response)
72
+ execute_request do
73
+ http_client.send_request(
74
+ 'DELETE',
75
+ uri_path,
76
+ prepare_payload(nil),
77
+ headers
78
+ )
79
+ end
74
80
  end
75
81
 
76
82
  def get_all(options, path = uri.path)
77
83
  uri_path = options.empty? ? path : "#{path}?#{URI.encode_www_form(options)}"
78
84
 
79
- response = http_client.send_request(
80
- 'GET',
81
- uri_path,
82
- prepare_payload(nil),
83
- headers
84
- )
85
-
86
- handle_response(response)
85
+ execute_request do
86
+ http_client.send_request(
87
+ 'GET',
88
+ uri_path,
89
+ prepare_payload(nil),
90
+ headers
91
+ )
92
+ end
87
93
  end
88
94
 
89
95
  private
@@ -98,14 +104,35 @@ module Lago
98
104
  }
99
105
  end
100
106
 
101
- def handle_response(response)
102
- raise_error(response) unless RESPONSE_SUCCESS_CODES.include?(response.code.to_i)
107
+ def execute_request(retry_count = 0, &block)
108
+ response = yield
109
+ handle_response(response, retry_count, block)
110
+ end
103
111
 
104
- response.body.empty? || JSON.parse(response.body)
112
+ def handle_response(response, retry_count, block)
113
+ code = response.code.to_i
114
+
115
+ if code == 429 && @retry_on_rate_limit && retry_count < @max_retries
116
+ handle_rate_limit(response, retry_count, block)
117
+ elsif !RESPONSE_SUCCESS_CODES.include?(code)
118
+ raise_error(response)
119
+ else
120
+ parse_response_body(response)
121
+ end
105
122
  rescue JSON::ParserError
106
123
  response.body
107
124
  end
108
125
 
126
+ def handle_rate_limit(response, retry_count, block)
127
+ reset_seconds = extract_reset_seconds(response, retry_count)
128
+ sleep(reset_seconds)
129
+ execute_request(retry_count + 1, &block)
130
+ end
131
+
132
+ def parse_response_body(response)
133
+ response.body.empty? || JSON.parse(response.body)
134
+ end
135
+
109
136
  def http_client
110
137
  http_client = Net::HTTP.new(uri.hostname, uri.port)
111
138
  http_client.use_ssl = true if uri.scheme == 'https'
@@ -120,7 +147,42 @@ module Lago
120
147
  end
121
148
 
122
149
  def raise_error(response)
123
- raise Lago::Api::HttpError.new(response.code.to_i, response.body, uri)
150
+ code = response.code.to_i
151
+
152
+ raise Lago::Api::HttpError.new(code, response.body, uri) unless code == 429
153
+
154
+ limit, remaining, reset = parse_rate_limit_headers(response)
155
+ raise Lago::Api::RateLimitError.new(
156
+ code,
157
+ response.body,
158
+ uri,
159
+ limit:,
160
+ remaining:,
161
+ reset:
162
+ )
163
+ end
164
+
165
+ def parse_rate_limit_headers(response)
166
+ limit = response['x-ratelimit-limit']&.to_i
167
+ remaining = response['x-ratelimit-remaining']&.to_i
168
+ reset = response['x-ratelimit-reset']&.to_i
169
+
170
+ [limit, remaining, reset]
171
+ end
172
+
173
+ def extract_reset_seconds(response, retry_count)
174
+ delay = if response['x-ratelimit-reset']
175
+ [response['x-ratelimit-reset'].to_i, INITIAL_BACKOFF].max
176
+ else
177
+ # Exponential backoff if header is missing
178
+ calculate_backoff(retry_count)
179
+ end
180
+
181
+ [delay, MAX_RETRY_DELAY].min
182
+ end
183
+
184
+ def calculate_backoff(retry_count)
185
+ INITIAL_BACKOFF * (BACKOFF_MULTIPLIER**retry_count)
124
186
  end
125
187
  end
126
188
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lago
4
+ module Api
5
+ class RateLimitError < HttpError
6
+ attr_reader :limit, :remaining, :reset
7
+
8
+ def initialize(code, body, uri, **options)
9
+ super(code, body, uri)
10
+ @limit = options[:limit]
11
+ @remaining = options[:remaining]
12
+ @reset = options[:reset]
13
+ end
14
+
15
+ def message
16
+ base_message = "HTTP #{error_code} - URI: #{uri}.\nError: #{error_body}"
17
+ return base_message unless reset
18
+
19
+ "#{base_message}\nRate limit will reset in #{reset} seconds."
20
+ end
21
+ end
22
+ end
23
+ end
@@ -59,7 +59,12 @@ module Lago
59
59
  def connection
60
60
  uri = URI.join(client.base_api_url, api_resource)
61
61
 
62
- Lago::Api::Connection.new(client.api_key, uri)
62
+ Lago::Api::Connection.new(
63
+ client.api_key,
64
+ uri,
65
+ max_retries: client.max_retries,
66
+ retry_on_rate_limit: client.retry_on_rate_limit,
67
+ )
63
68
  end
64
69
  end
65
70
  end
@@ -16,7 +16,12 @@ module Lago
16
16
 
17
17
  def create(params)
18
18
  uri = URI("#{client.base_ingest_api_url}#{api_resource}")
19
- connection = Lago::Api::Connection.new(client.api_key, uri)
19
+ connection = Lago::Api::Connection.new(
20
+ client.api_key,
21
+ uri,
22
+ max_retries: client.max_retries,
23
+ retry_on_rate_limit: client.retry_on_rate_limit,
24
+ )
20
25
 
21
26
  payload = whitelist_params(params)
22
27
  response = connection.post(payload, uri)[root_name]
@@ -8,7 +8,12 @@ module Lago
8
8
  class Nested < Base
9
9
  def initialize(client)
10
10
  super(client)
11
- @connection = Lago::Api::Connection.new(client.api_key, client.base_api_url)
11
+ @connection = Lago::Api::Connection.new(
12
+ client.api_key,
13
+ client.base_api_url,
14
+ max_retries: client.max_retries,
15
+ retry_on_rate_limit: client.retry_on_rate_limit,
16
+ )
12
17
  end
13
18
 
14
19
  def create(*parent_ids, params)
data/lib/lago/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lago
4
- VERSION = '1.44.0'
4
+ VERSION = '1.46.0'
5
5
  end
@@ -11,3 +11,4 @@ require 'lago/version'
11
11
  require 'lago/api/client'
12
12
  require 'lago/api/connection'
13
13
  require 'lago/api/http_error'
14
+ require 'lago/api/rate_limit_error'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lago-ruby-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.44.0
4
+ version: 1.46.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lovro Colic
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-03-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: jwt
@@ -52,6 +51,20 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: benchmark
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
55
68
  - !ruby/object:Gem::Dependency
56
69
  name: bigdecimal
57
70
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +205,20 @@ dependencies:
192
205
  - - "~>"
193
206
  - !ruby/object:Gem::Version
194
207
  version: '13.0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: readline
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ type: :development
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
195
222
  - !ruby/object:Gem::Dependency
196
223
  name: rspec
197
224
  requirement: !ruby/object:Gem::Requirement
@@ -276,7 +303,6 @@ dependencies:
276
303
  - - ">="
277
304
  - !ruby/object:Gem::Version
278
305
  version: '0'
279
- description:
280
306
  email:
281
307
  - lovro@getlago.com
282
308
  executables: []
@@ -287,6 +313,7 @@ files:
287
313
  - lib/lago/api/client.rb
288
314
  - lib/lago/api/connection.rb
289
315
  - lib/lago/api/http_error.rb
316
+ - lib/lago/api/rate_limit_error.rb
290
317
  - lib/lago/api/resources/activity_log.rb
291
318
  - lib/lago/api/resources/add_on.rb
292
319
  - lib/lago/api/resources/api_log.rb
@@ -341,7 +368,6 @@ metadata:
341
368
  homepage_uri: https://www.getlago.com/
342
369
  source_code_uri: https://github.com/getlago/lago-ruby-client
343
370
  documentation_uri: https://doc.getlago.com
344
- post_install_message:
345
371
  rdoc_options: []
346
372
  require_paths:
347
373
  - lib
@@ -356,8 +382,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
356
382
  - !ruby/object:Gem::Version
357
383
  version: '0'
358
384
  requirements: []
359
- rubygems_version: 3.3.27
360
- signing_key:
385
+ rubygems_version: 4.0.6
361
386
  specification_version: 4
362
387
  summary: Lago Rest API client
363
388
  test_files: []