fulfil_api 0.6.2 → 0.6.3

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: b08b66a980bb816b1424c5bdf4b6f07bdc652a0dc0838715b6176dd8dd3a52ac
4
- data.tar.gz: e659a978bd939c89aafc27bab91d36c3af9daad8c2779f8e87ff7f9dc8203766
3
+ metadata.gz: e5088d4a6e17bfc91b7dbdbd841db0d649bbc7fc16e295fb63a1cf8d4e80436a
4
+ data.tar.gz: 30f89d00763dde2c6d2b1573ded61440097b87904dae062c7f8ee147de9621e0
5
5
  SHA512:
6
- metadata.gz: 1da9f24a29088c2a177717731176b75ba29fcd9db6281ea7834ec2627e76b3b9029fb7f006ab428caba8035e68c39e221f77cf903145058820a2ebbb93e73552
7
- data.tar.gz: de48831aca1ac5ebc8aed263e61ccd0bdb25728240504dbf43c6a91c298f253e8d566141a29c7cab7c67b2d8366704b0f1d4b40f436dd9ea005c582df5f4086e
6
+ metadata.gz: 35aa477f5a29a3597d7567bcb8f6708fb89cb13d20c0f848cb06dd46f381bbff94dbddb36bf6fb95d9e544f01c7e94989f44ab872e1ed891f5ea254dfa358727
7
+ data.tar.gz: de45ced7efb091fc5cfcce224310afab14b233f97197887d94137f2c31007b43b363db424a1478108a8469e1813eeb980a71febe0d4c55f6b6e63d101a57c35b
@@ -5,6 +5,36 @@ require "faraday/net_http_persistent"
5
5
 
6
6
  module FulfilApi
7
7
  class Client
8
+ @connection_cache = {}
9
+ @connection_cache_mutex = Mutex.new
10
+
11
+ class << self
12
+ # Looks up a memoized {Faraday::Connection} for the given cache key, or
13
+ # builds and stores one by yielding the block.
14
+ #
15
+ # The cache is process-wide, so the underlying `net_http_persistent` adapter
16
+ # can actually reuse its TCP/TLS connection pool across requests. Without
17
+ # this, each `FulfilApi.client` call would instantiate a fresh Faraday
18
+ # connection, defeating the purpose of the persistent adapter.
19
+ #
20
+ # @param cache_key [Array] A key uniquely identifying the connection.
21
+ # @yieldreturn [Faraday::Connection] The newly built connection.
22
+ # @return [Faraday::Connection]
23
+ def connection_for(cache_key)
24
+ @connection_cache_mutex.synchronize do
25
+ @connection_cache[cache_key] ||= yield
26
+ end
27
+ end
28
+
29
+ # Clears the memoized connection cache. Intended for use in test suites
30
+ # that need to isolate connection state between tests.
31
+ #
32
+ # @return [void]
33
+ def reset_connection_cache!
34
+ @connection_cache_mutex.synchronize { @connection_cache.clear }
35
+ end
36
+ end
37
+
8
38
  # @param configuration [FulfilApi::Configuration]
9
39
  def initialize(configuration)
10
40
  @configuration = configuration
@@ -56,13 +86,29 @@ module FulfilApi
56
86
  @api_endpoint ||= "https://#{configuration.merchant_id}.fulfil.io"
57
87
  end
58
88
 
89
+ # Returns a {Faraday::Connection} for the current configuration, reusing the
90
+ # memoized connection from the class-level cache whenever possible.
91
+ #
59
92
  # @return [Faraday::Connection]
60
93
  def connection
61
- @connection ||= Faraday.new(
62
- headers: request_headers,
63
- url: api_endpoint,
64
- request: configuration.request_options
65
- ) do |connection|
94
+ self.class.connection_for(connection_cache_key) { build_connection }
95
+ end
96
+
97
+ # Builds a fresh {Faraday::Connection} for the current configuration.
98
+ #
99
+ # The connection carries no credentials — authentication headers are applied
100
+ # per-request in {#request}. This keeps tokens out of any long-lived data
101
+ # structure (cache keys, the connection's default headers) and lets a single
102
+ # connection be shared across configurations that target the same merchant
103
+ # with different access tokens.
104
+ #
105
+ # No `Content-Type` header is set on the connection. The `:json` request
106
+ # middleware adds it on requests that actually have a body, so bodyless
107
+ # verbs (GET, DELETE) are not sent with a misleading content type.
108
+ #
109
+ # @return [Faraday::Connection]
110
+ def build_connection
111
+ Faraday.new(url: api_endpoint, request: configuration.request_options) do |connection|
66
112
  connection.adapter :net_http_persistent # TODO: Allow passing configuration options
67
113
 
68
114
  # Configuration of the request middleware
@@ -74,6 +120,11 @@ module FulfilApi
74
120
  end
75
121
  end
76
122
 
123
+ # @return [Array] The cache key identifying a unique connection.
124
+ def connection_cache_key
125
+ [configuration.merchant_id, configuration.request_options]
126
+ end
127
+
77
128
  # @param relative_path [String] The relative path to the API endpoint.
78
129
  # @return [String] The absolute path for the request to the API endpoint.
79
130
  def expand_relative_path(relative_path)
@@ -98,17 +149,26 @@ module FulfilApi
98
149
  # @param relative_path [String] The relative path to the API endpoint.
99
150
  # @return [Array, Hash, String] The parsed response body.
100
151
  def request(method, relative_path, *args, **kwargs)
101
- connection.send(method.to_sym, expand_relative_path(relative_path), *args, **kwargs).body
152
+ response = connection.send(method.to_sym, expand_relative_path(relative_path), *args, **kwargs) do |req|
153
+ apply_authentication(req)
154
+ end
155
+
156
+ response.body
102
157
  rescue Faraday::Error => e
103
158
  handle_request_error(e)
104
159
  end
105
160
 
106
- # @return [Hash] The HTTP headers for any HTTP request to Fulfil.
107
- def request_headers
108
- default_headers = { "Content-Type" => "application/json" }
109
- return default_headers if configuration.access_token.nil?
161
+ # Applies the configured access token to the request as an HTTP header.
162
+ #
163
+ # Authentication is set per-request rather than on the shared {#connection},
164
+ # so the connection cache does not need to know about credentials.
165
+ #
166
+ # @param request [Faraday::Request] The request being prepared.
167
+ # @return [void]
168
+ def apply_authentication(request)
169
+ return if configuration.access_token.nil?
110
170
 
111
- default_headers.merge(**configuration.access_token.to_http_header)
171
+ configuration.access_token.to_http_header.each { |name, value| request.headers[name] = value }
112
172
  end
113
173
  end
114
174
 
@@ -15,7 +15,21 @@ module FulfilApi
15
15
 
16
16
  # @return [String]
17
17
  def message
18
+ body_message = parsed_body_message
19
+ return "[FulfilApi::Error] #{body_message}" if body_message
20
+
18
21
  "[FulfilApi::Error] #{super}"
19
22
  end
23
+
24
+ private
25
+
26
+ def parsed_body_message
27
+ body = details&.dig(:response_body)
28
+ return unless body
29
+
30
+ JSON.parse(body)
31
+ rescue JSON::ParserError
32
+ nil
33
+ end
20
34
  end
21
35
  end
@@ -16,6 +16,36 @@ module FulfilApi
16
16
 
17
17
  DEFAULT_API_VERSION = "v1"
18
18
 
19
+ @connection_cache = {}
20
+ @connection_cache_mutex = Mutex.new
21
+
22
+ class << self
23
+ # Looks up a memoized {Faraday::Connection} for the given cache key, or
24
+ # builds and stores one by yielding the block.
25
+ #
26
+ # The cache is process-wide, so the underlying `net_http_persistent` adapter
27
+ # can actually reuse its TCP/TLS connection pool across requests. Without
28
+ # this, each `FulfilApi.tpl_client` call would instantiate a fresh Faraday
29
+ # connection, defeating the purpose of the persistent adapter.
30
+ #
31
+ # @param cache_key [Array] A key uniquely identifying the connection.
32
+ # @yieldreturn [Faraday::Connection] The newly built connection.
33
+ # @return [Faraday::Connection]
34
+ def connection_for(cache_key)
35
+ @connection_cache_mutex.synchronize do
36
+ @connection_cache[cache_key] ||= yield
37
+ end
38
+ end
39
+
40
+ # Clears the memoized connection cache. Intended for use in test suites
41
+ # that need to isolate connection state between tests.
42
+ #
43
+ # @return [void]
44
+ def reset_connection_cache!
45
+ @connection_cache_mutex.synchronize { @connection_cache.clear }
46
+ end
47
+ end
48
+
19
49
  # @param configuration [FulfilApi::Configuration]
20
50
  def initialize(configuration)
21
51
  @configuration = configuration
@@ -77,10 +107,23 @@ module FulfilApi
77
107
  @api_endpoint ||= "https://#{merchant_id}.fulfil.io"
78
108
  end
79
109
 
110
+ # Returns a {Faraday::Connection} for the current configuration, reusing the
111
+ # memoized connection from the class-level cache whenever possible.
112
+ #
80
113
  # @return [Faraday::Connection]
81
114
  def connection
82
- @connection ||= Faraday.new(
83
- headers: request_headers,
115
+ self.class.connection_for(connection_cache_key) { build_connection }
116
+ end
117
+
118
+ # Builds a fresh {Faraday::Connection} for the current configuration.
119
+ #
120
+ # The connection carries no credentials — the bearer token is applied
121
+ # per-request in {#request}, keeping it out of long-lived data structures
122
+ # such as cache keys or default connection headers.
123
+ #
124
+ # @return [Faraday::Connection]
125
+ def build_connection
126
+ Faraday.new(
84
127
  url: api_endpoint,
85
128
  request: configuration.request_options
86
129
  ) do |connection|
@@ -95,6 +138,11 @@ module FulfilApi
95
138
  end
96
139
  end
97
140
 
141
+ # @return [Array] The cache key identifying a unique connection.
142
+ def connection_cache_key
143
+ [merchant_id, api_version, configuration.request_options]
144
+ end
145
+
98
146
  # @param relative_path [String] The relative path to the API endpoint.
99
147
  # @return [String] The absolute path for the request to the API endpoint.
100
148
  def expand_relative_path(relative_path)
@@ -119,16 +167,24 @@ module FulfilApi
119
167
  # @param relative_path [String] The relative path to the API endpoint.
120
168
  # @return [Array, Hash, String] The parsed response body.
121
169
  def request(method, relative_path, *args, **kwargs)
122
- connection.send(method.to_sym, expand_relative_path(relative_path), *args, **kwargs).body
170
+ response = connection.send(method.to_sym, expand_relative_path(relative_path), *args, **kwargs) do |req|
171
+ apply_authentication(req)
172
+ end
173
+
174
+ response.body
123
175
  rescue Faraday::Error => e
124
176
  handle_request_error(e)
125
177
  end
126
178
 
127
- # @return [Hash] The HTTP headers for any HTTP request to the 3PL API.
128
- def request_headers
129
- {
130
- "Authorization" => "Bearer #{auth_token}"
131
- }
179
+ # Applies the configured bearer token to the request as an HTTP header.
180
+ #
181
+ # Authentication is set per-request rather than on the shared {#connection},
182
+ # so the connection cache does not need to know about credentials.
183
+ #
184
+ # @param request [Faraday::Request] The request being prepared.
185
+ # @return [void]
186
+ def apply_authentication(request)
187
+ request.headers["Authorization"] = "Bearer #{auth_token}"
132
188
  end
133
189
  end
134
190
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FulfilApi
4
- VERSION = "0.6.2"
4
+ VERSION = "0.6.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fulfil_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Vermaas
@@ -125,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
127
  requirements: []
128
- rubygems_version: 4.0.3
128
+ rubygems_version: 4.0.6
129
129
  specification_version: 4
130
130
  summary: A HTTP client to interact the Fulfil.io API
131
131
  test_files: []