fulfil_api 0.6.3 → 0.7.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: e5088d4a6e17bfc91b7dbdbd841db0d649bbc7fc16e295fb63a1cf8d4e80436a
4
- data.tar.gz: 30f89d00763dde2c6d2b1573ded61440097b87904dae062c7f8ee147de9621e0
3
+ metadata.gz: c2dd30142d140730335b3f184bc28c0fd897af19246a8794ec5d416b1a335b2a
4
+ data.tar.gz: b81436ad499dfdd14ea84f5651d4670395907ad8090ab4e9eed432c6195da86c
5
5
  SHA512:
6
- metadata.gz: 35aa477f5a29a3597d7567bcb8f6708fb89cb13d20c0f848cb06dd46f381bbff94dbddb36bf6fb95d9e544f01c7e94989f44ab872e1ed891f5ea254dfa358727
7
- data.tar.gz: de45ced7efb091fc5cfcce224310afab14b233f97197887d94137f2c31007b43b363db424a1478108a8469e1813eeb980a71febe0d4c55f6b6e63d101a57c35b
6
+ metadata.gz: 6dee8c45eea430b8f6e7bfa82b03c3d737dd424cdda363c74680f4acedc9e896c28cfc3bc746e35d33035a863ffed9f995877b267a59e0afae0f2e67f7047d20
7
+ data.tar.gz: 5873899ac0f65bb6a9e1ecbd74aebd1f45806cee1faed5716c1649e158d83396de15139a93da9347b8b20ba080172c7e2cfa59aca36b6f89a89a4a57207e3167
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ - Re-enable Ruby's built-in retry for idempotent requests on the persistent connection, which the `net_http_persistent` adapter disables by forcing `max_retries` to `0`. This recovers stale keep-alive sockets transparently instead of surfacing them as read timeouts.
4
+ - Add a `connection_options` configuration option to tune the persistent connection (`max_retries`, `idle_timeout`, `pool_size`).
5
+ - `FulfilApi.with_config` now merges the temporary options over the active configuration instead of replacing it, so a block inherits credentials and other unspecified settings rather than resetting them to their defaults.
6
+
3
7
  ## [0.1.0] - 2024-08-10
4
8
 
5
9
  - Initial release
data/README.md CHANGED
@@ -41,6 +41,8 @@ end
41
41
 
42
42
  #### Using a Dynamic Configuration
43
43
 
44
+ `with_config` temporarily applies options **on top of the currently active configuration** (per thread) and reverts when the block returns. The options you pass are merged over the active config, so you only need to specify what changes — credentials and other settings are inherited rather than reset to their defaults.
45
+
44
46
  ```ruby
45
47
  FulfilApi.with_config(
46
48
  access_token: FulfilApi::AccessToken.new(ENV["FULFIL_API_KEY"]),
@@ -50,6 +52,17 @@ FulfilApi.with_config(
50
52
  end
51
53
  ```
52
54
 
55
+ This makes it easy to use different settings in contexts with different constraints. For example, a web request bound by a 30s timeout can keep tight defaults globally, while a background job (which has more time) overrides just the timeouts and retries without re-passing credentials:
56
+
57
+ ```ruby
58
+ FulfilApi.with_config(
59
+ request_options: { open_timeout: 5, read_timeout: 60, write_timeout: 30 },
60
+ connection_options: { max_retries: 3, idle_timeout: 10 }
61
+ ) do
62
+ # Long-running work against the Fulfil API
63
+ end
64
+ ```
65
+
53
66
  #### Available Configuration Options
54
67
 
55
68
  The following configuration options are (currently) available throught both configuration methods:
@@ -60,7 +73,16 @@ The following configuration options are (currently) available throught both conf
60
73
 
61
74
  - `merchant_id` (`String`): The `merchant_id` is the subdomain that the Fulfil instance is hosted on. This configuration option is required to be able to query Fulfil's API endpoints.
62
75
 
63
- - `request_options` (`Hash`): The `request_options` are the configuration options for the HTTP client. See [https://lostisland.github.io/faraday/#/customization/request-options](https://lostisland.github.io/faraday/#/customization/request-options) in `faraday`.
76
+ - `request_options` (`Hash`): The `request_options` are the per-request timeout options for the HTTP client. See [https://lostisland.github.io/faraday/#/customization/request-options](https://lostisland.github.io/faraday/#/customization/request-options) in `faraday`.
77
+
78
+ > **NOTE:** With the persistent (keep-alive) adapter there is no single whole-request `timeout`; Faraday resolves `read_timeout`, `open_timeout`, and `write_timeout` independently. `read_timeout` is the value that governs a slow or stalled response.
79
+
80
+ - `connection_options` (`Hash`): Tuning for the persistent (keep-alive) connection. Supported keys:
81
+ - `max_retries` (default `1`): Re-enables Ruby's built-in retry for **idempotent** requests (`GET`/`HEAD`/`PUT`/`DELETE`/`OPTIONS`). The `net_http_persistent` adapter disables this by forcing it to `0`, which makes a keep-alive socket the server has already dropped surface as a read timeout instead of being retried transparently on a fresh socket. `POST` is never auto-retried, so this is side-effect safe. Set to `0` to restore the adapter's default behaviour.
82
+ - `idle_timeout` (`Integer`, optional): Seconds a pooled socket may sit idle before it is recycled. Lower this towards your server's keep-alive window to shrink the stale-socket window for non-idempotent requests.
83
+ - `pool_size` (`Integer`, optional): Maximum number of concurrent connections kept in the pool.
84
+
85
+ > **NOTE:** When retries are enabled, the worst-case time for a request is roughly `(max_retries + 1) × read_timeout`. On platforms with a hard request cap (e.g. Heroku's 30s router limit), keep `read_timeout` low enough that this product stays under the cap.
64
86
 
65
87
  ### Querying the Fulfil API
66
88
 
@@ -109,7 +109,9 @@ module FulfilApi
109
109
  # @return [Faraday::Connection]
110
110
  def build_connection
111
111
  Faraday.new(url: api_endpoint, request: configuration.request_options) do |connection|
112
- connection.adapter :net_http_persistent # TODO: Allow passing configuration options
112
+ connection.adapter(:net_http_persistent, **adapter_options) do |http|
113
+ configure_persistent_connection(http)
114
+ end
113
115
 
114
116
  # Configuration of the request middleware
115
117
  connection.request :json
@@ -120,9 +122,40 @@ module FulfilApi
120
122
  end
121
123
  end
122
124
 
125
+ # Initialization options for the persistent adapter. Only `pool_size` is
126
+ # accepted here; `idle_timeout` and `max_retries` are applied to the live
127
+ # Net::HTTP::Persistent instance in {#configure_persistent_connection}.
128
+ #
129
+ # @return [Hash]
130
+ def adapter_options
131
+ options = {}
132
+ options[:pool_size] = configuration.connection_options[:pool_size] if configuration.connection_options[:pool_size]
133
+ options
134
+ end
135
+
136
+ # Tunes the underlying Net::HTTP::Persistent connection.
137
+ #
138
+ # The `net_http_persistent` adapter forces `max_retries` to 0 on every
139
+ # request, which disables Ruby's built-in retry for idempotent requests.
140
+ # Restoring it lets a stale keep-alive socket — one the server has already
141
+ # closed — be retried transparently on a fresh socket instead of surfacing
142
+ # as a read timeout. The config block runs after the adapter zeroes the
143
+ # value, so this takes effect.
144
+ #
145
+ # @param http [Net::HTTP::Persistent] The live persistent connection.
146
+ # @return [void]
147
+ def configure_persistent_connection(http)
148
+ if configuration.connection_options[:idle_timeout]
149
+ http.idle_timeout = configuration.connection_options[:idle_timeout]
150
+ end
151
+ return if configuration.connection_options[:max_retries].nil?
152
+
153
+ http.max_retries = configuration.connection_options[:max_retries]
154
+ end
155
+
123
156
  # @return [Array] The cache key identifying a unique connection.
124
157
  def connection_cache_key
125
- [configuration.merchant_id, configuration.request_options]
158
+ [configuration.merchant_id, configuration.request_options, configuration.connection_options]
126
159
  end
127
160
 
128
161
  # @param relative_path [String] The relative path to the API endpoint.
@@ -7,10 +7,23 @@ module FulfilApi
7
7
  # to these settings.
8
8
  class Configuration
9
9
  attr_accessor :access_token, :api_version, :merchant_id, :request_options, :tpl
10
+ attr_reader :connection_options
10
11
 
11
12
  DEFAULT_API_VERSION = "v2"
12
13
  DEFAULT_REQUEST_OPTIONS = { open_timeout: 1, read_timeout: 5, write_timeout: 5, timeout: 5 }.freeze
13
14
 
15
+ # Tuning for the persistent (keep-alive) HTTP connection.
16
+ #
17
+ # `max_retries` re-enables Ruby's built-in retry for idempotent requests
18
+ # (GET/HEAD/PUT/DELETE/OPTIONS). The `net_http_persistent` adapter forces
19
+ # it to 0, which means a keep-alive socket the server has already dropped
20
+ # surfaces as a read timeout instead of being transparently retried on a
21
+ # fresh socket. POST is never auto-retried, so this is side-effect safe.
22
+ #
23
+ # `idle_timeout` and `pool_size` are passed through to the underlying
24
+ # Net::HTTP::Persistent connection when set.
25
+ DEFAULT_CONNECTION_OPTIONS = { max_retries: 1 }.freeze
26
+
14
27
  # Initializes the configuration with optional settings.
15
28
  #
16
29
  # @param options [Hash, nil] An optional list of configuration options.
@@ -25,6 +38,16 @@ module FulfilApi
25
38
  set_default_options
26
39
  end
27
40
 
41
+ # Merges the provided connection options over the defaults so that, for
42
+ # example, setting only `idle_timeout` still keeps the default
43
+ # `max_retries`. Assigning `nil` resets to the defaults.
44
+ #
45
+ # @param options [Hash, nil] The connection options to apply.
46
+ # @return [void]
47
+ def connection_options=(options)
48
+ @connection_options = DEFAULT_CONNECTION_OPTIONS.merge(options || {})
49
+ end
50
+
28
51
  private
29
52
 
30
53
  # Sets the default options for the gem configuration.
@@ -36,6 +59,7 @@ module FulfilApi
36
59
  def set_default_options
37
60
  self.api_version = DEFAULT_API_VERSION if api_version.nil?
38
61
  self.request_options = DEFAULT_REQUEST_OPTIONS if request_options.nil?
62
+ self.connection_options = nil if connection_options.nil?
39
63
  end
40
64
  end
41
65
 
@@ -75,15 +99,23 @@ module FulfilApi
75
99
  end
76
100
  end
77
101
 
78
- # Temporarily applies the provided configuration options within a block,
79
- # and then reverts to the original configuration after the block executes.
102
+ # Temporarily applies the provided configuration options on top of the
103
+ # currently active configuration, and then reverts after the block executes.
104
+ #
105
+ # The temporary options are merged over a copy of the active configuration, so
106
+ # a block only needs to specify what it overrides — credentials and other
107
+ # settings (`access_token`, `merchant_id`, ...) are inherited rather than
108
+ # reset to their defaults.
80
109
  #
81
110
  # @param temporary_options [Hash] A hash of temporary configuration options.
82
111
  # @yield Executes the block with the temporary configuration.
83
112
  # @return [void]
84
113
  def self.with_config(temporary_options)
85
- original_configuration = configuration.dup
86
- self.configuration = temporary_options
114
+ original_configuration = configuration
115
+
116
+ self.configuration = original_configuration.dup.tap do |config|
117
+ temporary_options.each { |key, value| config.public_send(:"#{key}=", value) }
118
+ end
87
119
 
88
120
  yield
89
121
  ensure
@@ -123,11 +123,10 @@ module FulfilApi
123
123
  #
124
124
  # @return [Faraday::Connection]
125
125
  def build_connection
126
- Faraday.new(
127
- url: api_endpoint,
128
- request: configuration.request_options
129
- ) do |connection|
130
- connection.adapter :net_http_persistent
126
+ Faraday.new(url: api_endpoint, request: configuration.request_options) do |connection|
127
+ connection.adapter(:net_http_persistent, **adapter_options) do |http|
128
+ configure_persistent_connection(http)
129
+ end
131
130
 
132
131
  # Configuration of the request middleware
133
132
  connection.request :json
@@ -138,9 +137,40 @@ module FulfilApi
138
137
  end
139
138
  end
140
139
 
140
+ # Initialization options for the persistent adapter. Only `pool_size` is
141
+ # accepted here; `idle_timeout` and `max_retries` are applied to the live
142
+ # Net::HTTP::Persistent instance in {#configure_persistent_connection}.
143
+ #
144
+ # @return [Hash]
145
+ def adapter_options
146
+ options = {}
147
+ options[:pool_size] = configuration.connection_options[:pool_size] if configuration.connection_options[:pool_size]
148
+ options
149
+ end
150
+
151
+ # Tunes the underlying Net::HTTP::Persistent connection.
152
+ #
153
+ # The `net_http_persistent` adapter forces `max_retries` to 0 on every
154
+ # request, which disables Ruby's built-in retry for idempotent requests.
155
+ # Restoring it lets a stale keep-alive socket — one the server has already
156
+ # closed — be retried transparently on a fresh socket instead of surfacing
157
+ # as a read timeout. The config block runs after the adapter zeroes the
158
+ # value, so this takes effect.
159
+ #
160
+ # @param http [Net::HTTP::Persistent] The live persistent connection.
161
+ # @return [void]
162
+ def configure_persistent_connection(http)
163
+ if configuration.connection_options[:idle_timeout]
164
+ http.idle_timeout = configuration.connection_options[:idle_timeout]
165
+ end
166
+ return if configuration.connection_options[:max_retries].nil?
167
+
168
+ http.max_retries = configuration.connection_options[:max_retries]
169
+ end
170
+
141
171
  # @return [Array] The cache key identifying a unique connection.
142
172
  def connection_cache_key
143
- [merchant_id, api_version, configuration.request_options]
173
+ [merchant_id, api_version, configuration.request_options, configuration.connection_options]
144
174
  end
145
175
 
146
176
  # @param relative_path [String] The relative path to the API endpoint.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FulfilApi
4
- VERSION = "0.6.3"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fulfil_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Vermaas
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-06-29 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: activesupport
@@ -111,6 +112,7 @@ metadata:
111
112
  source_code_uri: https://www.github.com/codeturebv/fulfil_api
112
113
  changelog_uri: https://www.github.com/codeturebv/fulfil_api/blob/main/CHANGELOG.md
113
114
  rubygems_mfa_required: 'true'
115
+ post_install_message:
114
116
  rdoc_options: []
115
117
  require_paths:
116
118
  - lib
@@ -125,7 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
127
  - !ruby/object:Gem::Version
126
128
  version: '0'
127
129
  requirements: []
128
- rubygems_version: 4.0.6
130
+ rubygems_version: 3.5.11
131
+ signing_key:
129
132
  specification_version: 4
130
133
  summary: A HTTP client to interact the Fulfil.io API
131
134
  test_files: []