erp_integration 0.54.0 → 0.56.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 +4 -4
- data/.ruby-version +1 -1
- data/README.md +44 -0
- data/lib/erp_integration/configuration.rb +22 -0
- data/lib/erp_integration/fulfil/client.rb +15 -2
- data/lib/erp_integration/middleware/api_keys_rotation.rb +9 -2
- data/lib/erp_integration/middleware/logger.rb +3 -1
- data/lib/erp_integration/rate_limiter.rb +66 -0
- data/lib/erp_integration/version.rb +1 -1
- data/lib/erp_integration.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55974d4e2c7b76ca461070fe996522e0b7fa06391760549f8959000ed07b2a9e
|
4
|
+
data.tar.gz: 43683efd68525709be0fcba1537051830f52d7c0663779a7ab5b4c24b0a6e2e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0943cc3b1047ef17259a1eeb039fdd65ee74cb3ebc6911ff431162f60058b89d8e26e3f742028b261c2979c304aab4b1a0fd155e5d72fd0f0063a3a3afa351f
|
7
|
+
data.tar.gz: 4a06481fb232e9ad506c0d038af4114169546d622eecec0e6d5b03877e6f85648777c278728f87f6cae52c3e695f382ec34e4c1a2adcfadf0d9af23fc354374f
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.2.2
|
data/README.md
CHANGED
@@ -44,10 +44,13 @@ end
|
|
44
44
|
To set up API key rotation, configure multiple keys.
|
45
45
|
Every time a Fulfil client receives a `404` or `429`, it will rotate an API key.
|
46
46
|
|
47
|
+
You can set the `api_key_rotation_threshold` option to automatically change the API key when requests are running low. If you don't want to use the `x-ratelimit-remaining` response header for rotation, set the `api_key_rotation_threshold` to 0 or ignore it. The minimum threshold is 1, meaning the key will rotate when there are 0 remaining requests.
|
48
|
+
|
47
49
|
```erb
|
48
50
|
# config/initializers/erp_integration.rb
|
49
51
|
ErpIntegration.configure do |config|
|
50
52
|
config.fulfil_api_keys = ['<your-api-key1>', '<your-api-key2>']
|
53
|
+
config.api_key_rotation_threshold = 1
|
51
54
|
...
|
52
55
|
end
|
53
56
|
```
|
@@ -61,6 +64,47 @@ ErpIntegration::SalesOrder.api_keys_pool = '<your-api-key>'
|
|
61
64
|
ErpIntegration::SalesOrder.api_keys_pool = ['<your-api-key1>', '<your-api-key2>']
|
62
65
|
```
|
63
66
|
|
67
|
+
### Rate limiting
|
68
|
+
|
69
|
+
To manage the number of requests made to avoid rate limiting by the API provider, you can configure rate limiters in the ERP Integration gem. Each rate limiter should implement the `api_key_fragment` and `within_limit` methods.
|
70
|
+
|
71
|
+
### Setting Up Rate Limiters
|
72
|
+
You can configure multiple rate limiters to be used for HTTP operations on the client.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# config/initializers/erp_integration.rb
|
76
|
+
ErpIntegration.configure do |config|
|
77
|
+
config.rate_limiters = [
|
78
|
+
MyRateLimiter.new(api_key_fragment: 'key1'),
|
79
|
+
MyRateLimiter.new(api_key_fragment: 'key2')
|
80
|
+
]
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
Each rate limiter must implement the following methods:
|
85
|
+
- `api_key_fragment`: This method should return a string that is used to identify the rate limiter by the API key.
|
86
|
+
- `within_limit`: This method should `yield` to the block if the rate limit is not exceeded.
|
87
|
+
|
88
|
+
### Example Rate Limiter
|
89
|
+
Here is an example implementation of a rate limiter:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class MyRateLimiter
|
93
|
+
def initialize(api_key_fragment:)
|
94
|
+
@api_key_fragment = api_key_fragment
|
95
|
+
end
|
96
|
+
|
97
|
+
def api_key_fragment
|
98
|
+
@api_key_fragment
|
99
|
+
end
|
100
|
+
|
101
|
+
def within_limit
|
102
|
+
# Implement your rate limiting logic here
|
103
|
+
yield
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
64
108
|
### Supported Query Methods
|
65
109
|
|
66
110
|
After configuring the gem, one can easily query all the available ERP resources from the connected third-parties. In all cases, the API will return a collection of resources.
|
@@ -22,6 +22,12 @@ module ErpIntegration
|
|
22
22
|
# @return [String] The base URL for Fulfil.
|
23
23
|
attr_accessor :fulfil_base_url
|
24
24
|
|
25
|
+
# The `api_key_rotation_threshold` is used by the `ApiKeysRotation` middleware
|
26
|
+
# it will rotate the API key when the remaining requests are less than the threshold.
|
27
|
+
#
|
28
|
+
# Set the `api_key_rotation_threshold` to 0, it you don't want to rotate by the remaining requests.
|
29
|
+
attr_reader :api_key_rotation_threshold
|
30
|
+
|
25
31
|
# Allows configuring an adapter for the `BillOfMaterial` resource. When
|
26
32
|
# none is configured, it will default to Fulfil.
|
27
33
|
# @return [Symbol] The configured adapter for the bill_of_material.
|
@@ -172,6 +178,10 @@ module ErpIntegration
|
|
172
178
|
end
|
173
179
|
end
|
174
180
|
|
181
|
+
def api_key_rotation_threshold=(threshold)
|
182
|
+
@api_key_rotation_threshold = threshold || 0
|
183
|
+
end
|
184
|
+
|
175
185
|
def carrier_adapter
|
176
186
|
@carrier_adapter || :fulfil
|
177
187
|
end
|
@@ -299,6 +309,18 @@ module ErpIntegration
|
|
299
309
|
def product_option_adapter
|
300
310
|
@product_option_adapter || :fulfil
|
301
311
|
end
|
312
|
+
|
313
|
+
# Rate limiters that will be used for HTTP operations on Client
|
314
|
+
# to manage the number of requests made to avoid rate limiting by the API provider.
|
315
|
+
def rate_limiters
|
316
|
+
@rate_limiters ||= []
|
317
|
+
end
|
318
|
+
|
319
|
+
# Sets the rate limiters that will be used
|
320
|
+
# @raise [MissingMethodError] if the rate limiter object doesn't respond to the required methods.
|
321
|
+
def rate_limiters=(rate_limiters)
|
322
|
+
@rate_limiters = (rate_limiters || []).each { |limiter| RateLimiter.validate!(limiter) }
|
323
|
+
end
|
302
324
|
end
|
303
325
|
|
304
326
|
# Returns ERP Integration's configuration.
|
@@ -33,7 +33,7 @@ module ErpIntegration
|
|
33
33
|
# Custom error handling for the error response
|
34
34
|
faraday.use ErpIntegration::Middleware::ErrorHandling
|
35
35
|
# Custom middleware for rotating API keys
|
36
|
-
faraday.use ErpIntegration::Middleware::ApiKeysRotation,
|
36
|
+
faraday.use ErpIntegration::Middleware::ApiKeysRotation, rotation_options
|
37
37
|
# Custom middleware for logging requests and responses
|
38
38
|
faraday.use ErpIntegration::Middleware::Logger, @logger if @logger
|
39
39
|
|
@@ -48,7 +48,9 @@ module ErpIntegration
|
|
48
48
|
raise ErpIntegration::Error, 'The Fulfil API key and/or base URL are missing.'
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
rate_limiter.within_limit do
|
52
|
+
connection.public_send(action_name, "api/#{version}/#{path}", options).body
|
53
|
+
end
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
@@ -64,12 +66,23 @@ module ErpIntegration
|
|
64
66
|
api_keys_pool.current_key
|
65
67
|
end
|
66
68
|
|
69
|
+
def rate_limiter
|
70
|
+
RateLimiter.find_by_api_key(api_key)
|
71
|
+
end
|
72
|
+
|
67
73
|
def default_headers
|
68
74
|
{
|
69
75
|
'Accept': 'application/json',
|
70
76
|
'Content-Type': 'application/json'
|
71
77
|
}
|
72
78
|
end
|
79
|
+
|
80
|
+
def rotation_options
|
81
|
+
{
|
82
|
+
api_keys_pool: api_keys_pool,
|
83
|
+
rotation_theshold: ErpIntegration.config.api_key_rotation_threshold
|
84
|
+
}
|
85
|
+
end
|
73
86
|
end
|
74
87
|
end
|
75
88
|
end
|
@@ -20,14 +20,21 @@ module ErpIntegration
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def on_request(env)
|
23
|
-
env
|
23
|
+
env.request_headers['X-API-KEY'] = options[:api_keys_pool].current_key
|
24
24
|
end
|
25
25
|
|
26
26
|
def on_complete(env)
|
27
|
-
return unless
|
27
|
+
return unless rotate?(env)
|
28
28
|
|
29
29
|
options[:api_keys_pool].rotate_key!
|
30
30
|
end
|
31
|
+
|
32
|
+
def rotate?(env)
|
33
|
+
return true if HTTP_ROTATE_CODES.include?(env.status)
|
34
|
+
return false unless env.response_headers['x-ratelimit-remaining'].present?
|
35
|
+
|
36
|
+
env.response_headers['x-ratelimit-remaining'].to_i < options[:rotation_theshold].to_i
|
37
|
+
end
|
31
38
|
end
|
32
39
|
end
|
33
40
|
end
|
@@ -19,7 +19,7 @@ module ErpIntegration
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def call(env)
|
22
|
-
api_key_fragment = sanitize_api_key(env
|
22
|
+
api_key_fragment = sanitize_api_key(env.request_headers['X-API-KEY']) || 'none'
|
23
23
|
|
24
24
|
if @logger.respond_to?(:tagged)
|
25
25
|
@logger.tagged("API key *#{api_key_fragment}") do
|
@@ -43,6 +43,8 @@ module ErpIntegration
|
|
43
43
|
@formatter.exception(exc) if @formatter.respond_to?(:exception)
|
44
44
|
end
|
45
45
|
|
46
|
+
# @param [String] api_key
|
47
|
+
# @return [String] The last 4 characters of the API key
|
46
48
|
def sanitize_api_key(api_key)
|
47
49
|
api_key[-4..-1] if api_key
|
48
50
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
class RateLimiter
|
5
|
+
class MissingMethodError < StandardError; end
|
6
|
+
# The `api_key_fragment` method should return a string that is used to identify
|
7
|
+
# the rate limiter by the API key.
|
8
|
+
#
|
9
|
+
# The `within_limit` method should yield to the block if the rate limit is not exceeded.
|
10
|
+
REQUIRED_METHODS = %i[api_key_fragment within_limit].freeze
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Validates that the rate limiter object responds to the required methods.
|
14
|
+
# It requires the `api_key_fragment` and `within_limit` methods to be present.
|
15
|
+
#
|
16
|
+
# @raise [MissingMethodError]
|
17
|
+
# @param rate_limiter [Object]
|
18
|
+
def validate!(rate_limiter)
|
19
|
+
REQUIRED_METHODS.each do |method|
|
20
|
+
next if rate_limiter.respond_to?(method)
|
21
|
+
|
22
|
+
raise MissingMethodError, "'#{rate_limiter.class}##{method}' method is required."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finds the rate limiter by the API key.
|
27
|
+
# If the API key is not found, it returns an unlimited rate limiter.
|
28
|
+
#
|
29
|
+
# @param api_key [String]
|
30
|
+
# @return [Object] The rate limiter object.
|
31
|
+
def find_by_api_key(api_key)
|
32
|
+
rate_limiters[api_key] || unlimited
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an unlimited rate limiter.
|
36
|
+
#
|
37
|
+
# @return [RateLimiter::Unlimited] The unlimited rate limiter object.
|
38
|
+
def unlimited
|
39
|
+
@unlimited ||= Unlimited.new
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# The `rate_limiters` hash stores the rate limiter objects found by the API key.
|
45
|
+
#
|
46
|
+
# @return [Hash] The rate limiters hash.
|
47
|
+
def rate_limiters
|
48
|
+
@rate_limiters ||= Hash.new do |h, api_key|
|
49
|
+
h[api_key] = ErpIntegration.config.rate_limiters.find do |rate_limiter|
|
50
|
+
api_key.end_with?(rate_limiter.api_key_fragment)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# {Unlimited} is a rate limiter that allows all requests.
|
57
|
+
# When the rate limiter wasn't found by the API key, it defaults to this class.
|
58
|
+
# Or if the `rate_limiters` option is not set in the configuration.
|
59
|
+
class Unlimited
|
60
|
+
# Executes the block without any restrictions.
|
61
|
+
def within_limit
|
62
|
+
yield
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/erp_integration.rb
CHANGED
@@ -11,6 +11,7 @@ require 'json'
|
|
11
11
|
require_relative 'erp_integration/version'
|
12
12
|
require_relative 'erp_integration/errors'
|
13
13
|
require_relative 'erp_integration/api_keys_pool'
|
14
|
+
require_relative 'erp_integration/rate_limiter'
|
14
15
|
require_relative 'erp_integration/configuration'
|
15
16
|
|
16
17
|
# Middleware
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erp_integration
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.56.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Vermaas
|
@@ -329,6 +329,7 @@ files:
|
|
329
329
|
- lib/erp_integration/purchase_order.rb
|
330
330
|
- lib/erp_integration/purchase_order_line.rb
|
331
331
|
- lib/erp_integration/purchase_request.rb
|
332
|
+
- lib/erp_integration/rate_limiter.rb
|
332
333
|
- lib/erp_integration/resource.rb
|
333
334
|
- lib/erp_integration/resources/errors.rb
|
334
335
|
- lib/erp_integration/resources/persistence.rb
|
@@ -368,7 +369,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
368
369
|
- !ruby/object:Gem::Version
|
369
370
|
version: '0'
|
370
371
|
requirements: []
|
371
|
-
rubygems_version: 3.5.
|
372
|
+
rubygems_version: 3.5.6
|
372
373
|
signing_key:
|
373
374
|
specification_version: 4
|
374
375
|
summary: Connects Mejuri with third-party ERP vendors
|