erp_integration 0.55.0 → 0.57.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: 865d21dfcb8c0caaed6bb6e76151e019424294a04c5a553c68459cbda871278b
4
- data.tar.gz: 5da7b05a69bc74a8dcfa508ebe2b7b544e5902b26398b6da552fd139f20170f1
3
+ metadata.gz: d3ca1dee9c59a3fe44512bb878c063385b9ce338a8fdc4a6ff5b9c8f7167609d
4
+ data.tar.gz: cd1a513db316b7a69ace5ef024f4e64bc8214d4a08fc19c939280bb8217f505b
5
5
  SHA512:
6
- metadata.gz: ea5e4f2a3f0e8bec393964d050ef9db305d1b7859032989d6899750306eba46efba5627e741fc469b95c215a23a42e6f6fcdee7f418f1ddf3da2f276cb201629
7
- data.tar.gz: 71db26f1be6f150c53f34591d7ca46f7f89805fb65b5f44258c6dac86067bb8752feae8a08c11bf5cc4f6c096e7b3749b511583d1e3cf536921463e0d829e385
6
+ metadata.gz: a1c33b241d2bf37cdc487e49079d0fe85500125216799b872ea2921baf5769dbb33c0c57e9679cf09a5aa8ea8b68e0738052275c5b58883a76a12d4221431d19
7
+ data.tar.gz: 622bc6cd1456950c07a1b67ff75ab8c66e5c7e6ece19dc685d592e43adad88ce3aaa0b927e456ecc1344a3e5bebbaf81d529755c644ee4249459aa2e4701fb84
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.2
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
  ```
@@ -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.
@@ -162,16 +168,16 @@ module ErpIntegration
162
168
  # @return [Symbol] The configured adapter for the tracking number.
163
169
  attr_writer :product_option_adapter
164
170
 
165
- # Logger that will be used for HTTP operations on Client
166
- # @return [Logger] The configured logger
167
- attr_accessor :logger
168
-
169
171
  def initialize(**options)
170
172
  options.each_pair do |key, value|
171
173
  public_send("#{key}=", value) if respond_to?("#{key}=")
172
174
  end
173
175
  end
174
176
 
177
+ def api_key_rotation_threshold=(threshold)
178
+ @api_key_rotation_threshold = threshold || 0
179
+ end
180
+
175
181
  def carrier_adapter
176
182
  @carrier_adapter || :fulfil
177
183
  end
@@ -309,7 +315,15 @@ module ErpIntegration
309
315
  # Sets the rate limiters that will be used
310
316
  # @raise [MissingMethodError] if the rate limiter object doesn't respond to the required methods.
311
317
  def rate_limiters=(rate_limiters)
312
- @rate_limiters = rate_limiters.each { |limiter| RateLimiter.validate!(limiter) }
318
+ @rate_limiters = (rate_limiters || []).each { |limiter| RateLimiter.validate!(limiter) }
319
+ end
320
+
321
+ def logger
322
+ @logger || Logger.new(nil)
323
+ end
324
+
325
+ def logger=(logger)
326
+ @logger = Logger.new(logger)
313
327
  end
314
328
  end
315
329
 
@@ -3,6 +3,8 @@
3
3
  module ErpIntegration
4
4
  module Fulfil
5
5
  class Client
6
+ FORMATTER = ErpIntegration::Middleware::Formatter
7
+
6
8
  attr_reader :api_keys_pool, :base_url
7
9
  attr_writer :connection, :faraday_adapter, :rotate_statuses
8
10
 
@@ -33,9 +35,9 @@ module ErpIntegration
33
35
  # Custom error handling for the error response
34
36
  faraday.use ErpIntegration::Middleware::ErrorHandling
35
37
  # Custom middleware for rotating API keys
36
- faraday.use ErpIntegration::Middleware::ApiKeysRotation, api_keys_pool: api_keys_pool
38
+ faraday.use ErpIntegration::Middleware::ApiKeysRotation, rotation_options
37
39
  # Custom middleware for logging requests and responses
38
- faraday.use ErpIntegration::Middleware::Logger, @logger if @logger
40
+ faraday.use ErpIntegration::Middleware::Logger, @logger, formatter: FORMATTER if @logger
39
41
 
40
42
  # Adapter definition should be last in order to make the json parsers be loaded correctly
41
43
  faraday.adapter faraday_adapter
@@ -76,6 +78,13 @@ module ErpIntegration
76
78
  'Content-Type': 'application/json'
77
79
  }
78
80
  end
81
+
82
+ def rotation_options
83
+ {
84
+ api_keys_pool: api_keys_pool,
85
+ rotation_theshold: ErpIntegration.config.api_key_rotation_threshold
86
+ }
87
+ end
79
88
  end
80
89
  end
81
90
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ # The Logger class is a simple wrapper around the logger object.
5
+ class Logger
6
+ delegate :debug, :info, :warn, :error, :fatal, to: :logger, allow_nil: true
7
+
8
+ def initialize(logger = nil)
9
+ @logger = logger
10
+ end
11
+
12
+ # Logs the given tags if the logger is present.
13
+ # If the logger does not respond to `tagged`, it logs the tags as an info message.
14
+ def with_tags(*tags)
15
+ return yield unless logger
16
+
17
+ if logger.respond_to?(:tagged)
18
+ logger.tagged(*tags) { yield }
19
+ else
20
+ logger.info("Requested ERP with #{tags.join(', ')}")
21
+ yield
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :logger
28
+ end
29
+ end
@@ -20,14 +20,21 @@ module ErpIntegration
20
20
  end
21
21
 
22
22
  def on_request(env)
23
- env[:request_headers]['X-API-KEY'] = options[:api_keys_pool].current_key
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 HTTP_ROTATE_CODES.include?(env.status)
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
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ module Middleware
5
+ # The {Formatter} class is a custom formatter for the Faraday logger.
6
+ # It logs the rate limiter headers in the response.
7
+ class Formatter < Faraday::Logging::Formatter
8
+ def response(env)
9
+ super
10
+ public_send(log_level, 'response', &rate_limiter_headers(env.response_headers))
11
+ end
12
+
13
+ # Formats the rate limiter headers.
14
+ # @param headers [Hash]
15
+ def rate_limiter_headers(headers)
16
+ proc do
17
+ [
18
+ limit(headers),
19
+ remaining(headers),
20
+ reset_at(headers)
21
+ ].join(', ')
22
+ end
23
+ end
24
+
25
+ def limit(headers)
26
+ "Rate Limit #{headers['x-ratelimit-limit']}"
27
+ end
28
+
29
+ def remaining(headers)
30
+ "Remaining Limit #{headers['x-ratelimit-remaining']}"
31
+ end
32
+
33
+ def reset_at(headers)
34
+ "Rate Limit Reset at #{Time.at(headers['x-ratelimit-reset'].to_i).utc}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -19,16 +19,10 @@ module ErpIntegration
19
19
  end
20
20
 
21
21
  def call(env)
22
- api_key_fragment = sanitize_api_key(env[:request_headers]['X-API-KEY'])
22
+ api_key_fragment = sanitize_api_key(env.request_headers['X-API-KEY']) || 'none'
23
+ request_uuid = SecureRandom.uuid
23
24
 
24
- if @logger.respond_to?(:tagged)
25
- @logger.tagged("API key *#{api_key_fragment}") do
26
- @formatter.request(env)
27
-
28
- @app.call(env).on_complete { |response| on_complete(response) }
29
- end
30
- else
31
- @logger.info("Requested ERP with API key *#{api_key_fragment}")
25
+ @logger.with_tags(api_key_tag(api_key_fragment), request_uuid) do
32
26
  @formatter.request(env)
33
27
 
34
28
  @app.call(env).on_complete { |response| on_complete(response) }
@@ -48,6 +42,10 @@ module ErpIntegration
48
42
  def sanitize_api_key(api_key)
49
43
  api_key[-4..-1] if api_key
50
44
  end
45
+
46
+ def api_key_tag(api_key_fragment)
47
+ "API key *#{api_key_fragment}"
48
+ end
51
49
  end
52
50
  end
53
51
  end
@@ -7,7 +7,7 @@ module ErpIntegration
7
7
  # the rate limiter by the API key.
8
8
  #
9
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
10
+ REQUIRED_METHODS = %i[api_key_fragment name within_limit].freeze
11
11
 
12
12
  class << self
13
13
  # Validates that the rate limiter object responds to the required methods.
@@ -29,7 +29,7 @@ module ErpIntegration
29
29
  # @param api_key [String]
30
30
  # @return [Object] The rate limiter object.
31
31
  def find_by_api_key(api_key)
32
- rate_limiters[api_key] || unlimited
32
+ new(rate_limiters[api_key] || unlimited)
33
33
  end
34
34
 
35
35
  # Returns an unlimited rate limiter.
@@ -53,11 +53,30 @@ module ErpIntegration
53
53
  end
54
54
  end
55
55
 
56
+ delegate :name, to: :@rate_limiter
57
+
58
+ def initialize(rate_limiter)
59
+ @rate_limiter = rate_limiter
60
+ end
61
+
62
+ def within_limit(&block)
63
+ ErpIntegration.config.logger.with_tags(@rate_limiter.name) do
64
+ @rate_limiter.within_limit(&block)
65
+ end
66
+ end
67
+
56
68
  # {Unlimited} is a rate limiter that allows all requests.
57
69
  # When the rate limiter wasn't found by the API key, it defaults to this class.
58
70
  # Or if the `rate_limiters` option is not set in the configuration.
59
71
  class Unlimited
60
72
  # Executes the block without any restrictions.
73
+
74
+ attr_reader :name
75
+
76
+ def initialize
77
+ @name = 'unlimited'
78
+ end
79
+
61
80
  def within_limit
62
81
  yield
63
82
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ErpIntegration
4
- VERSION = '0.55.0'
4
+ VERSION = '0.57.0'
5
5
  end
@@ -12,12 +12,14 @@ require_relative 'erp_integration/version'
12
12
  require_relative 'erp_integration/errors'
13
13
  require_relative 'erp_integration/api_keys_pool'
14
14
  require_relative 'erp_integration/rate_limiter'
15
+ require_relative 'erp_integration/logger'
15
16
  require_relative 'erp_integration/configuration'
16
17
 
17
18
  # Middleware
18
19
  require_relative 'erp_integration/middleware/api_keys_rotation'
19
20
  require_relative 'erp_integration/middleware/error_handling'
20
21
  require_relative 'erp_integration/middleware/logger'
22
+ require_relative 'erp_integration/middleware/formatter'
21
23
 
22
24
  # HTTP clients
23
25
  require_relative 'erp_integration/fulfil/client'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erp_integration
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.55.0
4
+ version: 0.57.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Vermaas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-15 00:00:00.000000000 Z
11
+ date: 2024-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -317,8 +317,10 @@ files:
317
317
  - lib/erp_integration/gift_card.rb
318
318
  - lib/erp_integration/internal_shipment.rb
319
319
  - lib/erp_integration/location.rb
320
+ - lib/erp_integration/logger.rb
320
321
  - lib/erp_integration/middleware/api_keys_rotation.rb
321
322
  - lib/erp_integration/middleware/error_handling.rb
323
+ - lib/erp_integration/middleware/formatter.rb
322
324
  - lib/erp_integration/middleware/logger.rb
323
325
  - lib/erp_integration/party_address.rb
324
326
  - lib/erp_integration/product.rb
@@ -369,7 +371,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
369
371
  - !ruby/object:Gem::Version
370
372
  version: '0'
371
373
  requirements: []
372
- rubygems_version: 3.2.22
374
+ rubygems_version: 3.5.6
373
375
  signing_key:
374
376
  specification_version: 4
375
377
  summary: Connects Mejuri with third-party ERP vendors