nxt_http_client 0.3.4 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89185b814fea114ee2d74dd8e05afd3cf4773cdef8fb908cdf5e954775b4e885
4
- data.tar.gz: 2d940e5c42051c09ca776d57901535a7a3d18bf8facb116b742f04a46b91e2ae
3
+ metadata.gz: 4260cb5f0853f547f8ab196f0c7d4cb66fc34f3c1aba748d3e0836183ee68f18
4
+ data.tar.gz: ef7c3095fe541847fc57eb4ba807f1f9a2e9b3d7413b08980d619805339c4557
5
5
  SHA512:
6
- metadata.gz: 2dea66682adbebf9800a68cbb3f9962d4b17623bc022163b7765fd1b4a889a78fc610dfc12719d137605e9e3b7c98b254e6f8f5ec80eca947318795b66dd48a2
7
- data.tar.gz: 8f04ddb779773c29db1a6f1af0cc73065b4da4e19e7e1a0b43eb5e1c6ec09d94acefb47ca0bdff0880377e2366354a631bc6751adad2ab73c31161108d2fa540
6
+ metadata.gz: 57806fbcb6fe0f44a47c820a24d4036ca6ae9dc24d1c9cbaa746ecff4567e29aba8f4a000885f461fa6dccf1d049e77c1528d5df0ccecdc731ce81e0b95d7795
7
+ data.tar.gz: 97452f30577ad9f0ae35a62be8a82e6c7cf0a99a2d2c35e69d90c6fb0d290f74cdb1c7b787c0ffdfc4d3196255d6f56069f3ebc59e315f83af1bb6a7581c2c19
data/.circleci/config.yml CHANGED
@@ -6,10 +6,10 @@ version: 2
6
6
  jobs:
7
7
  build:
8
8
  docker:
9
- - image: circleci/ruby:2.7.0-node
9
+ - image: circleci/ruby:2.7.4-node
10
10
  - image: circleci/redis:5.0.4
11
11
  environment:
12
- BUNDLER_VERSION: 2.1.4
12
+ BUNDLER_VERSION: 2.3.6
13
13
 
14
14
  working_directory: ~/repo
15
15
 
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ # v1.0.3 2022-02-08
2
+
3
+ - Relax dependency version constraints, allow activesupport < 8
4
+
5
+ # v1.0.2 2021-02-17
6
+
7
+ ### Update NxtHttpClient::Error
8
+ - delegate timed_out? and status_message to response
9
+
10
+ # v1.0.1 2021-02-04
11
+
12
+ ### Update NxtRegistry
13
+ - update nxt_registry to 0.3.8
14
+
15
+ # v1.0.0 2021-01-10
16
+
17
+ ### Breaking changes
18
+ - renamed register_response_handler to _response_handler
19
+ - replace before_fire and after_fire hooks with proper callbacks before, after and around fire
20
+
21
+ ### Updated
22
+ - error now includes more information about request and headers
23
+
1
24
  # v0.3.4 2021-01-05
2
25
 
3
26
  ### Updated
data/Gemfile.lock CHANGED
@@ -1,87 +1,88 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_http_client (0.3.4)
5
- activesupport (~> 6.0)
4
+ nxt_http_client (1.0.3)
5
+ activesupport (< 8.0)
6
6
  nxt_registry
7
7
  typhoeus
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- activesupport (6.1.0)
12
+ activesupport (7.0.1)
13
13
  concurrent-ruby (~> 1.0, >= 1.0.2)
14
14
  i18n (>= 1.6, < 2)
15
15
  minitest (>= 5.1)
16
16
  tzinfo (~> 2.0)
17
- zeitwerk (~> 2.3)
18
- addressable (2.7.0)
17
+ addressable (2.8.0)
19
18
  public_suffix (>= 2.0.2, < 5.0)
20
19
  coderay (1.1.3)
21
- concurrent-ruby (1.1.7)
20
+ concurrent-ruby (1.1.9)
22
21
  crack (0.4.5)
23
22
  rexml
24
- diff-lcs (1.4.4)
25
- ethon (0.12.0)
26
- ffi (>= 1.3.0)
27
- ffi (1.13.1)
23
+ diff-lcs (1.5.0)
24
+ ethon (0.15.0)
25
+ ffi (>= 1.15.0)
26
+ ffi (1.15.5)
28
27
  hashdiff (1.0.1)
29
- i18n (1.8.7)
28
+ i18n (1.9.1)
30
29
  concurrent-ruby (~> 1.0)
31
30
  method_source (1.0.0)
32
- minitest (5.14.2)
33
- nxt_registry (0.3.6)
31
+ minitest (5.15.0)
32
+ nxt_registry (0.3.10)
34
33
  activesupport
35
34
  nxt_vcr_harness (0.1.4)
36
35
  rspec (~> 3.0)
37
36
  vcr (~> 6.0)
38
- pry (0.13.1)
37
+ pry (0.14.1)
39
38
  coderay (~> 1.1)
40
39
  method_source (~> 1.0)
41
40
  public_suffix (4.0.6)
42
- rake (13.0.3)
43
- redis (4.2.5)
44
- rexml (3.2.4)
41
+ rake (13.0.6)
42
+ redis (4.6.0)
43
+ rexml (3.2.5)
45
44
  rspec (3.10.0)
46
45
  rspec-core (~> 3.10.0)
47
46
  rspec-expectations (~> 3.10.0)
48
47
  rspec-mocks (~> 3.10.0)
49
- rspec-core (3.10.1)
48
+ rspec-core (3.10.2)
50
49
  rspec-support (~> 3.10.0)
51
- rspec-expectations (3.10.1)
50
+ rspec-expectations (3.10.2)
52
51
  diff-lcs (>= 1.2.0, < 2.0)
53
52
  rspec-support (~> 3.10.0)
54
- rspec-mocks (3.10.1)
53
+ rspec-mocks (3.10.3)
55
54
  diff-lcs (>= 1.2.0, < 2.0)
56
55
  rspec-support (~> 3.10.0)
57
- rspec-support (3.10.1)
58
- rspec_junit_formatter (0.4.1)
56
+ rspec-support (3.10.3)
57
+ rspec_junit_formatter (0.5.1)
59
58
  rspec-core (>= 2, < 4, != 2.12.0)
59
+ timecop (0.9.4)
60
60
  typhoeus (1.4.0)
61
61
  ethon (>= 0.9.0)
62
62
  tzinfo (2.0.4)
63
63
  concurrent-ruby (~> 1.0)
64
64
  vcr (6.0.0)
65
- webmock (3.11.0)
66
- addressable (>= 2.3.6)
65
+ webmock (3.14.0)
66
+ addressable (>= 2.8.0)
67
67
  crack (>= 0.3.2)
68
68
  hashdiff (>= 0.4.0, < 2.0.0)
69
- zeitwerk (2.4.2)
70
69
 
71
70
  PLATFORMS
72
- ruby
71
+ x86_64-darwin-19
72
+ x86_64-linux
73
73
 
74
74
  DEPENDENCIES
75
- bundler (~> 1.17)
75
+ bundler
76
76
  nxt_http_client!
77
77
  nxt_vcr_harness
78
78
  pry
79
- rake (~> 13.0)
79
+ rake
80
80
  redis
81
- rspec (~> 3.0)
81
+ rspec
82
82
  rspec_junit_formatter
83
+ timecop
83
84
  vcr
84
85
  webmock
85
86
 
86
87
  BUNDLED WITH
87
- 1.17.3
88
+ 2.3.6
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # NxtHttpClient
2
2
 
3
- Build http clients with ease. NxtHttpClient is a simple DSL on top of the awesome typhoeus gem.
4
- The idea is that you can configure your http clients on the class level and then adjust or further configure
5
- request options on the instance level. All http interactions are handled by typhoeus. If you need to
6
- access the original `Typhoeus::Request` in your instance, you can do that.
3
+ Build http clients with ease. NxtHttpClient is a simple DSL on top of the [typhoeus](https://github.com/typhoeus/typhoeus)
4
+ gem. NxtHttpClient mostly provides you a simple configuration functionality to setup http connections on the class level.
5
+ Furthermore it's mostly a callback framework that allows you to seamlessly handle your responses. Since it's is just a simple
6
+ layer on top of [typhoeus](https://github.com/typhoeus/typhoeus) it also allows to access and configure the original
7
+ `Typhoeus::Request` before making a request.
8
+
7
9
 
8
10
  ## Installation
9
11
 
@@ -23,16 +25,33 @@ Or install it yourself as:
23
25
 
24
26
  ## Usage
25
27
 
28
+ A typical client could look something like this:
29
+
26
30
  ```ruby
27
- class MyClient < NxtHttpClient
28
-
29
- # In your subclasses you probably want to deep_merge options in order to not overwrite options inherited
30
- # from the parent class. Of course this will not influence the parent class and you can also reset them
31
- # to a new hash here.
32
-
33
- # Also be aware that the result of x_request_id_proc will be hashed into the cache key and thus might cause
34
- # your request not to be cached if not used properly
31
+ class UserFetcher < Client
32
+ def initialize(id)
33
+ @url = ".../users/#{id}"
34
+ end
35
+
36
+ def call
37
+ get(url) do |response_handler|
38
+ response_handler.on(:success) do |response|
39
+ JSON(response.body)
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :url
47
+ end
48
+ ```
35
49
 
50
+ In order to setup a shared configuration you would therefore setup a client base class. The configuration and any
51
+ response handler or callbacks you setup in your base class are then inherited to your concrete client implementations.
52
+
53
+ ```ruby
54
+ class Client < NxtHttpClient
36
55
  configure do |config|
37
56
  config.base_url = 'www.example.com'
38
57
  config.request_options.deep_merge!(
@@ -40,69 +59,28 @@ class MyClient < NxtHttpClient
40
59
  method: :get,
41
60
  followlocation: true
42
61
  )
43
- config.x_request_id_proc = -> { ('a'..'z').to_a.shuffle.take(10).join }
62
+ config.x_request_id_proc = -> { ('a'..'z').to_a.shuffle.take(10).join }
44
63
  end
45
-
46
- register_response_handler do |handler|
64
+
65
+ log do |info|
66
+ Rails.logger.info(info)
67
+ end
68
+
69
+ response_handler do |handler|
47
70
  handler.on(:error) do |response|
48
- Raven.extra_context(error_details: error.to_h) # call error.to_h to inspect request and response
71
+ Raven.extra_context(error_details: error.to_h)
49
72
  raise StandardError, "I can't handle this: #{response.code}"
50
73
  end
51
74
  end
52
-
53
- # Will be called before fire so you can reconfigure your handler before fire
54
- before_fire do |client, request, handler|
55
- handler.on!(200) do |response|
56
- # ...
57
- end
58
- end
59
-
60
- # Will be called after fire. You probably want to return the result here in order for your code
61
- # to be able to access the result from the response handler from before.
62
- # In case one of the response handler callbacks raises an error
63
- # after fire will has access to it and you may want to reraise the error in that case.
64
-
65
- after_fire do |client, request, response, result, error|
66
- if error
67
- raise error
68
- else
69
- result
70
- end
71
- end
72
-
73
- def fetch_details
74
- fire('details', method: :get) do |handler|
75
- handler.on(:success) do |response|
76
- response.body
77
- end
78
-
79
- handler.on(404) do |response|
80
- raise StandardError, '404'
81
- end
82
-
83
- # You can also fuzzy match response codes using the wildcard *
84
- handler.on('5**') do |response|
85
- raise StandardError, 'This is bad'
86
- end
87
- end
88
- end
89
-
90
- # there are also convenience methods for all http verbs (get post patch put delete head)
91
- def update
92
- post(params: { my: 'payload' }) do |handler|
93
- # ...
94
- end
95
- end
96
75
  end
97
76
  ```
98
77
 
99
78
  ### HTTP Methods
100
79
 
101
- Instead of fire you can simply use the http verbs as methods
80
+ In order to build a request and execute it NxtHttpClient implements all http standard methods.
102
81
 
103
82
  ```ruby
104
- class MyClient < NxtHttpClient
105
-
83
+ class Client < NxtHttpClient
106
84
  def initialize(url)
107
85
  @url = url
108
86
  end
@@ -131,21 +109,22 @@ class MyClient < NxtHttpClient
131
109
  end
132
110
  ```
133
111
 
134
-
135
112
  ### configure
136
113
 
137
- Register default request options on the class level. Available options are `request_options` that are passed directly to
138
- the underlying Typhoeus Request. Then there is `base_url` and `x_request_id_proc`.
114
+ Register your default request options on the class level. Available options are `request_options` that are passed
115
+ directly to the underlying Typhoeus Request. Then there is `base_url` and `x_request_id_proc`.
139
116
 
140
- ### register_response_handler
117
+ ### response_handler
141
118
 
142
- Register a default response handler for your client class.
143
- You can reconfigure or overwrite it this completely later on the instance level.
119
+ Register a default response handler for your client class. You can reconfigure or overwrite this in subclasses and
120
+ on the instance level.
144
121
 
145
122
  ### fire
146
123
 
147
- Use `fire('uri', **request_options)` to actually fire your requests and define what to do with the response by using
148
- the NxtHttpClient DSL. Registered callbacks have a hierarchy by which they are executed. Specific callbacks will come first
124
+ All http methods internally are delegate to `fire('uri', **request_options)`. Since `fire` is a public method you can
125
+ also use it to fire your requests and use the response handler to register callbacks for specific responses.
126
+
127
+ Registered callbacks have a hierarchy by which they are executed. Specific callbacks will come first
149
128
  and more common callbacks will come later in case none of the specific callbacks matched. It this is not what you want you
150
129
  can simply put the logic you need into one common callback that is called in any case. You can also use strings with wildcards
151
130
  to match a group of response by status code. `handler.on('4**') { ... }` basically would match all client errors.
@@ -159,40 +138,111 @@ fire('uri', **request_options) do |handler|
159
138
  handler.on(:success) do |response|
160
139
  response.body
161
140
  end
162
-
141
+
163
142
  handler.on(:timed_out) do |response|
164
143
  raise StandardError, 'Timeout'
165
144
  end
166
-
145
+
167
146
  handler.on(:error) do |response|
168
147
  raise StandardError, 'This is bad'
169
148
  end
170
-
149
+
171
150
  handler.on(:others) do |response|
172
151
  raise StandardError, 'Other problem'
173
152
  end
174
-
153
+
175
154
  handler.on(:headers) do |response|
176
155
  # This is already executed when the headers are received
177
156
  end
178
-
157
+
179
158
  handler.on(:body) do |chunk|
180
- # Use this to stream the body in chunks
159
+ # Use this to stream the body in chunks
181
160
  end
182
161
  end
183
- ```
162
+ ```
184
163
 
185
164
  ### Callbacks around fire
186
165
 
187
- You can also hook into the before_fire and after_fire callbacks to do something before and after the actual request is executed.
188
- These callbacks are inherited down the class hierarchy but are not being chained. Meaning when you overwrite those in your subclass,
189
- the callbacks defined by your parent class will not be called anymore.
166
+ Next to implementing callbacks for handling responses there are also callbacks around making requests. Note tht you can
167
+ have as many callbacks as you want. In case you need to reset them because you do not want to inherit them from your
168
+ parent class (might be a smell when you need to...) you can reset callbacks via `clear_fire_callbacks` on the class level.
169
+
170
+ ```ruby
171
+
172
+ clear_fire_callbacks # Call this to clear callbacks setup in the parent class
173
+
174
+ before_fire do |client, request, response_handler|
175
+ # here you have access to the client, request and response_handler
176
+ end
177
+
178
+ around_fire do |client, request, response_handler, fire|
179
+ # here you have access to the client, request and response_handler
180
+ fire.call # You have to call fire here and return the result to the next callback in the chain
181
+ end
182
+
183
+ after_fire do |client, request, response, result, error|
184
+ result # The result of the last callback in the chain is the result of fire!
185
+ end
186
+ ```
187
+
190
188
 
191
189
  ### NxtHttpClient::Error
192
190
 
193
191
  NxtHttpClient also provides an error base class that you might want to use as the base for your client errors.
194
192
  It comes with a nice set of useful methods. You can ask the error for the request and response options since it
195
- requires the response for initialization.
193
+ requires the response for initialization. Furthermore it has a handy `to_h` method that provides you all info about
194
+ the request and response.
195
+
196
+ #### Timeouts
197
+ NxtHttpClient::Error exposes the `timed_out?` method from `Typhoeus::Response`, so you can check if an error is raised due to a timeout. This is useful when setting a custom timeout value in your configuration.
198
+
199
+ ### Logging
200
+
201
+ NxtHttpClient also comes with a log method on the class level that you can pass a proc if you want to log your request.
202
+ Your proc needs to accept an argument in order to get access to information about the request and response made.
203
+
204
+ ```ruby
205
+ log do |info|
206
+ Rails.logger.info(info)
207
+ end
208
+
209
+ # info is a hash that is implemented as follows:
210
+
211
+ {
212
+ client: client,
213
+ started_at: started_at,
214
+ request: request,
215
+ finished_at: now,
216
+ elapsed_time_in_milliseconds: finished_at - started_at,
217
+ response: request.response,
218
+ http_status: request.response&.code
219
+ }
220
+ ```
221
+
222
+ ### Caching
223
+
224
+ Typhoeus ships with caching built in. Checkout the [typhoeus](https://github.com/typhoeus/typhoeus) docu to figure out
225
+ how to set it up. NxtHttpClient builds some functionality on top of this and offer to cache requests within the current
226
+ thread or globally. You can simply make use of it by providing one of the caching options `:thread` or`:global` as config
227
+ request option or the actual request options when building the request.
228
+
229
+ ```ruby
230
+ class Client < NxtHttpClient::Client
231
+ configure do |config|
232
+ config.request_options = { cache: :thread }
233
+ end
234
+
235
+ response_handler do |handler|
236
+ handler.on(200) do |response|
237
+ # ...
238
+ end
239
+ end
240
+
241
+ def call
242
+ get('.../url.com', cache: :thread) # configure caching per request level
243
+ end
244
+ end
245
+ ```
196
246
 
197
247
  ## Development
198
248
 
@@ -202,7 +252,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
202
252
 
203
253
  ## Contributing
204
254
 
205
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/nxt_http_client.
255
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nxt-insurance/nxt_http_client.
206
256
 
207
257
  ## License
208
258
 
@@ -11,42 +11,30 @@ module NxtHttpClient
11
11
  Typhoeus::Request.new(url, **opts.symbolize_keys)
12
12
  end
13
13
 
14
- delegate :before_fire_callback, :after_fire_callback, to: :class
15
-
16
14
  def fire(url = '', **opts, &block)
17
- response_handler = opts.fetch(:response_handler) { dup_handler_from_class || NxtHttpClient::ResponseHandler.new }
18
- response_handler.configure(&block) if block_given?
15
+ response_handler = build_response_handler(opts[:response_handler], &block)
19
16
  request = build_request(url, **opts.except(:response_handler))
20
- before_fire_callback && instance_exec(self, request, response_handler, &before_fire_callback)
21
-
22
- if response_handler.callbacks.resolve('headers')
23
- request.on_headers do |response|
24
- response_handler.eval_callback(self, 'headers', response)
25
- end
26
- end
27
17
 
28
- if response_handler.callbacks.resolve('body')
29
- request.on_body do |response|
30
- response_handler.eval_callback(self, 'body', response)
31
- end
32
- end
33
-
34
- result = nil
35
18
  current_error = nil
19
+ result = nil
20
+
21
+ setup_on_headers_callback(request, response_handler)
22
+ setup_on_body_callback(request, response_handler)
36
23
 
37
24
  request.on_complete do |response|
38
25
  result = callback_or_response(response, response_handler)
26
+ end
27
+
28
+ run_before_fire_callbacks(request, response_handler)
29
+
30
+ run_around_fire_callbacks(request, response_handler) do
31
+ request.run
39
32
  rescue StandardError => error
40
33
  current_error = error
41
- ensure
42
- if after_fire_callback
43
- result = instance_exec(self, request, response, result, current_error, &after_fire_callback)
44
- else
45
- result || (raise current_error)
46
- end
47
34
  end
48
35
 
49
- request.run
36
+ result = run_after_fire_callbacks(request, request.response, result, current_error)
37
+ result || (raise current_error if current_error)
50
38
 
51
39
  result
52
40
  end
@@ -60,7 +48,7 @@ module NxtHttpClient
60
48
  private
61
49
 
62
50
  def build_url(opts, url)
63
- base_url = opts.delete(:base_url) || default_config.base_url
51
+ base_url = opts.delete(:base_url) || config.base_url
64
52
  url = [base_url, url].reject(&:blank?).join('/')
65
53
 
66
54
  url_without_duplicated_hashes(url)
@@ -68,11 +56,11 @@ module NxtHttpClient
68
56
  end
69
57
 
70
58
  def build_headers(opts)
71
- opts = default_config.request_options.with_indifferent_access.deep_merge(opts.with_indifferent_access)
59
+ opts = config.request_options.with_indifferent_access.deep_merge(opts.with_indifferent_access)
72
60
  opts[:headers] ||= {}
73
61
 
74
- if default_config.x_request_id_proc
75
- opts[:headers]['X-Request-ID'] ||= default_config.x_request_id_proc.call
62
+ if config.x_request_id_proc
63
+ opts[:headers][XRequestId] ||= config.x_request_id_proc.call
76
64
  end
77
65
 
78
66
  build_cache_header(opts)
@@ -83,8 +71,8 @@ module NxtHttpClient
83
71
  self.class.response_handler.dup
84
72
  end
85
73
 
86
- def default_config
87
- self.class.default_config
74
+ def config
75
+ self.class.config
88
76
  end
89
77
 
90
78
  def build_cache_header(opts)
@@ -92,13 +80,13 @@ module NxtHttpClient
92
80
  strategy = opts.delete(:cache)
93
81
 
94
82
  case strategy.to_s
95
- when 'thread'
96
- cache_key = Thread.current[:nxt_http_client_cache_key] ||= "#{SecureRandom.base58}::#{DateTime.current}"
97
- opts[:headers].reverse_merge!(cache_key: cache_key)
98
- when 'global'
99
- opts[:headers].delete(:cache_key)
100
- else
101
- raise ArgumentError, "Cache strategy unknown: #{strategy}. Options are #{CACHE_STRATEGIES}"
83
+ when 'thread'
84
+ cache_key = Thread.current[:nxt_http_client_cache_key] ||= "#{SecureRandom.base58}::#{DateTime.current}"
85
+ opts[:headers].reverse_merge!(cache_key: cache_key)
86
+ when 'global'
87
+ opts[:headers].delete(:cache_key)
88
+ else
89
+ raise ArgumentError, "Cache strategy unknown: #{strategy}. Options are #{CACHE_STRATEGIES}"
102
90
  end
103
91
  end
104
92
  end
@@ -116,5 +104,56 @@ module NxtHttpClient
116
104
  callback = response_handler.callback_for_response(response)
117
105
  callback && instance_exec(response, &callback) || response
118
106
  end
107
+
108
+ def build_response_handler(handler, &block)
109
+ response_handler = handler || dup_handler_from_class || NxtHttpClient::ResponseHandler.new
110
+ response_handler.configure(&block) if block_given?
111
+ response_handler
112
+ end
113
+
114
+ def run_before_fire_callbacks(request, response_handler)
115
+ callbacks.run_before(target: self, request: request, response_handler: response_handler)
116
+ end
117
+
118
+ def run_around_fire_callbacks(request, response_handler, &fire)
119
+ callbacks.run_around(
120
+ target: self,
121
+ request: request,
122
+ response_handler: response_handler,
123
+ fire: fire
124
+ )
125
+ end
126
+
127
+ def run_after_fire_callbacks(request, response, result, current_error)
128
+ return result unless callbacks.any_after_callbacks?
129
+
130
+ callbacks.run_after(
131
+ target: self,
132
+ request: request,
133
+ response: response,
134
+ result: result,
135
+ error: current_error
136
+ )
137
+ end
138
+
139
+ def setup_on_headers_callback(request, response_handler)
140
+ return unless response_handler.callbacks.resolve('headers')
141
+
142
+ request.on_headers do |response|
143
+ response_handler.eval_callback(self, 'headers', response)
144
+ end
145
+ end
146
+
147
+ def setup_on_body_callback(request, response_handler)
148
+ return unless response_handler.callbacks.resolve('body')
149
+
150
+ request.on_body do |response|
151
+ response_handler.eval_callback(self, 'body', response)
152
+ end
153
+ end
154
+
155
+ def callbacks
156
+ @callbacks ||= self.class.callbacks
157
+ end
119
158
  end
120
159
  end
@@ -1,57 +1,79 @@
1
1
  module NxtHttpClient
2
2
  module ClientDsl
3
3
  def configure(opts = {}, &block)
4
- opts.each { |k, v| default_config.send(k, v) }
5
- default_config.tap { |d| block.call(d) }
6
- default_config
4
+ opts.each { |k, v| config.send(k, v) }
5
+ config.tap { |d| block.call(d) }
6
+ config
7
7
  end
8
8
 
9
- def before_fire(&block)
10
- @before_fire_callback = block
9
+ def log(&block)
10
+ @logger ||= block || dup_option_from_ancestor(:@logger)
11
+
12
+ return unless @logger.present?
13
+ logger = @logger
14
+
15
+ around_fire do |client, request, response_handler, fire|
16
+ Logger.new(logger).call(client, request, response_handler, fire)
17
+ end
11
18
  end
12
19
 
13
- def before_fire_callback
14
- @before_fire_callback ||= dup_instance_variable_from_ancestor_chain(:@before_fire_callback)
20
+ def clear_fire_callbacks(*kinds)
21
+ callbacks.clear(*kinds)
22
+ end
23
+
24
+ def before_fire(&block)
25
+ callbacks.register(:before, block)
15
26
  end
16
27
 
17
28
  def after_fire(&block)
18
- @after_fire_callback = block
29
+ callbacks.register(:after, block)
30
+ end
31
+
32
+ def around_fire(&block)
33
+ callbacks.register(:around, block)
19
34
  end
20
35
 
21
- def after_fire_callback
22
- @after_fire_callback ||= dup_instance_variable_from_ancestor_chain(:@after_fire_callback)
36
+ def config
37
+ @config ||= dup_option_from_ancestor(:@config) { Config.new }
23
38
  end
24
39
 
25
- def default_config
26
- @default_config ||= dup_instance_variable_from_ancestor_chain(:@default_config) { DefaultConfig.new }
40
+ def callbacks
41
+ @callbacks ||= dup_option_from_ancestor(:@callbacks) { FireCallbacks.new }
27
42
  end
28
43
 
29
- def register_response_handler(handler = nil, &block)
30
- @response_handler = handler
31
- @response_handler ||= dup_instance_variable_from_ancestor_chain(:@response_handler) { NxtHttpClient::ResponseHandler.new }
44
+ def response_handler(handler = Undefined.new, &block)
45
+ if undefined?(handler)
46
+ @response_handler ||= dup_option_from_ancestor(:@response_handler) { NxtHttpClient::ResponseHandler.new }
47
+ else
48
+ @response_handler = handler
49
+ end
50
+
32
51
  @response_handler.configure(&block) if block_given?
33
52
  @response_handler
34
53
  end
35
54
 
36
- def response_handler
37
- @response_handler ||= dup_instance_variable_from_ancestor_chain(:@response_handler) { NxtHttpClient::ResponseHandler.new }
38
- end
55
+ alias_method :response_handler, :response_handler
56
+
57
+ private
39
58
 
40
59
  def client_ancestors
41
60
  ancestors.select { |ancestor| ancestor <= NxtHttpClient::Client }
42
61
  end
43
62
 
44
- def instance_variable_from_ancestor_chain(instance_variable_name)
45
- client = client_ancestors.find { |c| c.instance_variable_get(instance_variable_name) }
46
-
47
- client.instance_variable_get(instance_variable_name)
63
+ def option_from_ancestors(name)
64
+ client = client_ancestors.find { |c| c.instance_variable_get(name) }
65
+ client && client.instance_variable_get(name)
48
66
  end
49
67
 
50
- def dup_instance_variable_from_ancestor_chain(instance_variable_name)
51
- result = instance_variable_from_ancestor_chain(instance_variable_name).dup
68
+ def dup_option_from_ancestor(name)
69
+ result = option_from_ancestors(name).dup
52
70
  return result unless block_given?
53
71
 
54
72
  result || yield
55
73
  end
74
+
75
+ def undefined?(value)
76
+ value.is_a?(Undefined)
77
+ end
56
78
  end
57
79
  end
@@ -1,7 +1,7 @@
1
1
  module NxtHttpClient
2
2
  CONFIGURABLE_OPTIONS = %i[request_options base_url x_request_id_proc].freeze
3
3
 
4
- DefaultConfig = Struct.new('DefaultConfig', *CONFIGURABLE_OPTIONS) do
4
+ Config = Struct.new('Config', *CONFIGURABLE_OPTIONS) do
5
5
  def initialize(request_options: ActiveSupport::HashWithIndifferentAccess.new, base_url: '', x_request_id_proc: nil)
6
6
  self.request_options = request_options
7
7
  self.base_url = base_url
@@ -9,11 +9,12 @@ module NxtHttpClient
9
9
  end
10
10
 
11
11
  attr_reader :response, :id, :message
12
+ delegate :timed_out?, :status_message, to: :response
12
13
 
13
14
  alias_method :to_s, :message
14
15
 
15
16
  def default_message
16
- "NxtHttpClient::Error::#{response_code}"
17
+ "#{self.class.name}::#{response_code}"
17
18
  end
18
19
 
19
20
  def to_h
@@ -23,12 +24,14 @@ module NxtHttpClient
23
24
  response_code: response_code,
24
25
  request_options: request_options,
25
26
  response_headers: response_headers,
26
- body: body
27
+ request_headers: request_headers,
28
+ body: body,
29
+ x_request_id: x_request_id
27
30
  }
28
31
  end
29
32
 
30
33
  def body
31
- if response_content_type&.starts_with?('application/json')
34
+ if response_content_type&.starts_with?(ApplicationJson)
32
35
  JSON.parse(response.body)
33
36
  else
34
37
  response.body
@@ -49,6 +52,10 @@ module NxtHttpClient
49
52
  request.url
50
53
  end
51
54
 
55
+ def x_request_id
56
+ request_headers[XRequestId]
57
+ end
58
+
52
59
  def request_options
53
60
  @request_options ||= (request.original_options || {}).with_indifferent_access
54
61
  end
@@ -70,5 +77,3 @@ module NxtHttpClient
70
77
  end
71
78
  end
72
79
  end
73
-
74
-
@@ -0,0 +1,66 @@
1
+ module NxtHttpClient
2
+ class FireCallbacks
3
+ def initialize
4
+ @registry = build_registry
5
+ end
6
+
7
+ def clear(*kinds)
8
+ Array(kinds).each { |kind| registry.register!(kind, []) }
9
+ end
10
+
11
+ def register(kind, callback)
12
+ registry.resolve!(kind) << callback
13
+ end
14
+
15
+ def run(target, kind, *args)
16
+ registry.resolve!(kind).each do |callback|
17
+ run_callback(target, callback, *args)
18
+ end
19
+ end
20
+
21
+ def run_before(target:, request:, response_handler:)
22
+ registry.resolve!(:before).each do |callback|
23
+ run_callback(target, callback, *[target, request, response_handler])
24
+ end
25
+ end
26
+
27
+ def run_after(target:, request:, response:, result:, error:)
28
+ registry.resolve!(:after).inject(result) do |_, callback|
29
+ run_callback(target, callback, *[target, request, response, result, error])
30
+ end
31
+ end
32
+
33
+ def any_after_callbacks?
34
+ registry.resolve(:after).any?
35
+ end
36
+
37
+ def run_around(target:, request:, response_handler:, fire:)
38
+ around_callbacks = registry.resolve!(:around)
39
+ return fire.call unless around_callbacks.any?
40
+
41
+ args = *[target, request, response_handler]
42
+ callback_chain = around_callbacks.reverse.inject(fire) do |previous, callback|
43
+ -> { target.instance_exec(*args, previous, &callback) }
44
+ end
45
+
46
+ callback_chain.call
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :registry
52
+
53
+ def run_callback(target, callback, *args)
54
+ args = args.take(callback.arity)
55
+ target.instance_exec(*args, &callback)
56
+ end
57
+
58
+ def build_registry
59
+ NxtRegistry::Registry.new(:callbacks) do
60
+ register(:before, [])
61
+ register(:around, [])
62
+ register(:after, [])
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,4 @@
1
+ module NxtHttpClient
2
+ XRequestId = 'X-Request-ID'.freeze
3
+ ApplicationJson = 'application/json'
4
+ end
@@ -0,0 +1,47 @@
1
+ module NxtHttpClient
2
+ class Logger
3
+ def initialize(logger)
4
+ @logger = logger
5
+ end
6
+
7
+ def call(client, request, _response_handler, fire)
8
+ started_at = now
9
+ error = nil
10
+ result = nil
11
+
12
+ options = {
13
+ client: client,
14
+ started_at: started_at,
15
+ request: request
16
+ }
17
+
18
+ begin
19
+ result = fire.call
20
+ rescue => e
21
+ error = e
22
+ options.merge!(error: e)
23
+ ensure
24
+ finished_at = now
25
+ options.merge!(
26
+ finished_at: now,
27
+ elapsed_time_in_milliseconds: finished_at - started_at,
28
+ response: request.response,
29
+ http_status: request.response&.code
30
+ )
31
+ end
32
+
33
+ logger.call(options)
34
+ raise error if error
35
+
36
+ result
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :logger
42
+
43
+ def now
44
+ Time.current.to_i * 1000
45
+ end
46
+ end
47
+ end
@@ -10,8 +10,6 @@ module NxtHttpClient
10
10
  attr_accessor :result
11
11
 
12
12
  def eval_callback(target, key, response)
13
- return unless callbacks.resolve!(key)
14
-
15
13
  target.instance_exec(response, &callbacks.resolve(key))
16
14
  end
17
15
 
@@ -20,6 +18,8 @@ module NxtHttpClient
20
18
  end
21
19
 
22
20
  def register_callback(code, overwrite: false, &block)
21
+ code = regex_or_code(code)
22
+
23
23
  if overwrite
24
24
  callbacks.register!(code, block)
25
25
  else
@@ -36,14 +36,10 @@ module NxtHttpClient
36
36
 
37
37
  def callback_for_response(response)
38
38
  key_from_response = response.code.to_s
39
- return callbacks.resolve('any') if callbacks.resolve('any').present?
40
-
41
- first_matching_key = callbacks.keys.sort.reverse.find do |key|
42
- regex_key = key.gsub('*', '[0-9]{1}')
43
- key_from_response =~ /\A#{regex_key}\z/
44
- end
39
+ matching_any_callback = callbacks.resolve('any')
40
+ return matching_any_callback if matching_any_callback.present?
45
41
 
46
- first_matching_key && callbacks.resolve(first_matching_key) ||
42
+ callbacks.resolve(key_from_response) ||
47
43
  response.success? && callbacks.resolve('success') ||
48
44
  response.timed_out? && callbacks.resolve('timed_out') ||
49
45
  !response.success? && callbacks.resolve('error') ||
@@ -60,6 +56,14 @@ module NxtHttpClient
60
56
 
61
57
  private
62
58
 
59
+ def regex_or_code(key)
60
+ return key if key.is_a?(Regexp)
61
+ return key if key.to_s.exclude?('*')
62
+
63
+ regex_key = key.to_s.gsub('*', '[0-9]{1}')
64
+ /\A#{regex_key}\z/
65
+ end
66
+
63
67
  def raise_callback_already_registered(code)
64
68
  msg = "Callback already registered for status: #{code}."
65
69
  msg << ' Use bang method to overwrite the callback.'
@@ -0,0 +1,4 @@
1
+ module NxtHttpClient
2
+ class Undefined
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module NxtHttpClient
2
- VERSION = "0.3.4"
2
+ VERSION = "1.0.3"
3
3
  end
@@ -3,8 +3,12 @@ require 'typhoeus'
3
3
  require 'nxt_registry'
4
4
 
5
5
  require 'nxt_http_client/version'
6
+ require 'nxt_http_client/headers'
6
7
  require 'nxt_http_client/response_handler'
7
- require 'nxt_http_client/default_config'
8
+ require 'nxt_http_client/config'
9
+ require 'nxt_http_client/undefined'
10
+ require 'nxt_http_client/logger'
11
+ require 'nxt_http_client/fire_callbacks'
8
12
  require 'nxt_http_client/client_dsl'
9
13
  require 'nxt_http_client/client'
10
14
  require 'nxt_http_client/error'
@@ -1,4 +1,3 @@
1
-
2
1
  lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require "nxt_http_client/version"
@@ -36,16 +35,17 @@ Gem::Specification.new do |spec|
36
35
  spec.require_paths = ['lib']
37
36
 
38
37
  spec.add_dependency 'typhoeus'
39
- spec.add_dependency 'activesupport', '~> 6.0'
38
+ spec.add_dependency 'activesupport', '< 8.0'
40
39
  spec.add_dependency 'nxt_registry'
41
40
 
42
- spec.add_development_dependency 'bundler', '~> 1.17'
43
- spec.add_development_dependency 'rake', '~> 13.0'
44
- spec.add_development_dependency 'rspec', '~> 3.0'
41
+ spec.add_development_dependency 'bundler'
42
+ spec.add_development_dependency 'rake'
43
+ spec.add_development_dependency 'rspec'
45
44
  spec.add_development_dependency 'pry'
46
45
  spec.add_development_dependency 'vcr'
47
46
  spec.add_development_dependency 'webmock'
48
47
  spec.add_development_dependency 'nxt_vcr_harness'
49
48
  spec.add_development_dependency 'redis'
50
49
  spec.add_development_dependency 'rspec_junit_formatter'
50
+ spec.add_development_dependency 'timecop'
51
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nxt_http_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Robecke
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2021-01-05 00:00:00.000000000 Z
14
+ date: 2022-02-08 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: typhoeus
@@ -31,16 +31,16 @@ dependencies:
31
31
  name: activesupport
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  requirements:
34
- - - "~>"
34
+ - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '6.0'
36
+ version: '8.0'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - "~>"
41
+ - - "<"
42
42
  - !ruby/object:Gem::Version
43
- version: '6.0'
43
+ version: '8.0'
44
44
  - !ruby/object:Gem::Dependency
45
45
  name: nxt_registry
46
46
  requirement: !ruby/object:Gem::Requirement
@@ -59,44 +59,44 @@ dependencies:
59
59
  name: bundler
60
60
  requirement: !ruby/object:Gem::Requirement
61
61
  requirements:
62
- - - "~>"
62
+ - - ">="
63
63
  - !ruby/object:Gem::Version
64
- version: '1.17'
64
+ version: '0'
65
65
  type: :development
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
68
68
  requirements:
69
- - - "~>"
69
+ - - ">="
70
70
  - !ruby/object:Gem::Version
71
- version: '1.17'
71
+ version: '0'
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: rake
74
74
  requirement: !ruby/object:Gem::Requirement
75
75
  requirements:
76
- - - "~>"
76
+ - - ">="
77
77
  - !ruby/object:Gem::Version
78
- version: '13.0'
78
+ version: '0'
79
79
  type: :development
80
80
  prerelease: false
81
81
  version_requirements: !ruby/object:Gem::Requirement
82
82
  requirements:
83
- - - "~>"
83
+ - - ">="
84
84
  - !ruby/object:Gem::Version
85
- version: '13.0'
85
+ version: '0'
86
86
  - !ruby/object:Gem::Dependency
87
87
  name: rspec
88
88
  requirement: !ruby/object:Gem::Requirement
89
89
  requirements:
90
- - - "~>"
90
+ - - ">="
91
91
  - !ruby/object:Gem::Version
92
- version: '3.0'
92
+ version: '0'
93
93
  type: :development
94
94
  prerelease: false
95
95
  version_requirements: !ruby/object:Gem::Requirement
96
96
  requirements:
97
- - - "~>"
97
+ - - ">="
98
98
  - !ruby/object:Gem::Version
99
- version: '3.0'
99
+ version: '0'
100
100
  - !ruby/object:Gem::Dependency
101
101
  name: pry
102
102
  requirement: !ruby/object:Gem::Requirement
@@ -181,6 +181,20 @@ dependencies:
181
181
  - - ">="
182
182
  - !ruby/object:Gem::Version
183
183
  version: '0'
184
+ - !ruby/object:Gem::Dependency
185
+ name: timecop
186
+ requirement: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ type: :development
192
+ prerelease: false
193
+ version_requirements: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
184
198
  description: NxtHttpClinet allows you to easily create and configure http clients.
185
199
  email:
186
200
  - a.robecke@getsafe.de
@@ -191,6 +205,7 @@ files:
191
205
  - ".circleci/config.yml"
192
206
  - ".gitignore"
193
207
  - ".rspec"
208
+ - ".ruby-version"
194
209
  - ".travis.yml"
195
210
  - CHANGELOG.md
196
211
  - Gemfile
@@ -203,9 +218,13 @@ files:
203
218
  - lib/nxt_http_client.rb
204
219
  - lib/nxt_http_client/client.rb
205
220
  - lib/nxt_http_client/client_dsl.rb
206
- - lib/nxt_http_client/default_config.rb
221
+ - lib/nxt_http_client/config.rb
207
222
  - lib/nxt_http_client/error.rb
223
+ - lib/nxt_http_client/fire_callbacks.rb
224
+ - lib/nxt_http_client/headers.rb
225
+ - lib/nxt_http_client/logger.rb
208
226
  - lib/nxt_http_client/response_handler.rb
227
+ - lib/nxt_http_client/undefined.rb
209
228
  - lib/nxt_http_client/version.rb
210
229
  - nxt_http_client.gemspec
211
230
  homepage: https://github.com/nxt-insurance
@@ -230,7 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
249
  - !ruby/object:Gem::Version
231
250
  version: '0'
232
251
  requirements: []
233
- rubygems_version: 3.0.3
252
+ rubygems_version: 3.1.6
234
253
  signing_key:
235
254
  specification_version: 4
236
255
  summary: NxtHttpClinet is a simple DSL on top the typhoeus http gem