nxt_http_client 0.3.1 → 1.0.1

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: 78ac5880ecaada2ab1423658c89c7e46a7edff5b3936c386255776d2a3a9fb93
4
- data.tar.gz: 3bbd588bdaf3b12481685495fcbae33d497b8ced2a646e204bbc15d9739605d8
3
+ metadata.gz: b46524948cd4090f671207e4bd80e79494304185c04872e86ab69de5fca62024
4
+ data.tar.gz: 1f8e3173afaf3251ec5fde4c3b2cf2482aa3018139e79ea1faf51eb5e78e3091
5
5
  SHA512:
6
- metadata.gz: 5fc826649a72b9b0f2e3076aab3e791045b01c021d1122344449f2b6865878270872e8cfd7a20edc58333608b0c4bbcce347efef4e7c4b4c126d6d173caa75ae
7
- data.tar.gz: 0e06702933121d3cda176b386c1ed47df0a53fe276b00d4e2e2a2e7efc48ad7804bee9ee54593317f6b2c9eac1415a9351f227bc8625bfe224fbdb4e9f6e4cd5
6
+ metadata.gz: 530c4ce286a20f2eb1495e869f16bb0d3e4495bb44e5fc1f0e48067add813ff7196aca74b47bd7b3e2a199bbf3a441b344a7ec1d83823d6cebabdc9da0969194
7
+ data.tar.gz: 4c96dead69a3b9c7cab56929e1f40e6d9332324a97e569e3747b0d4405386168f7f3b4b72f8a235d529126281bc2b394903c5a8f3d2a1d082679ab33a2c6a117
data/.circleci/config.yml CHANGED
@@ -9,7 +9,7 @@ jobs:
9
9
  - image: circleci/ruby:2.7.0-node
10
10
  - image: circleci/redis:5.0.4
11
11
  environment:
12
- BUNDLER_VERSION: 2.1.4
12
+ BUNDLER_VERSION: 2.2.4
13
13
 
14
14
  working_directory: ~/repo
15
15
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ # v1.0.1 2021-02-04
2
+
3
+ ### Update NxtRegistry
4
+ - update nxt_registry to 0.3.8
5
+
6
+ # v1.0.0 2021-01-10
7
+
8
+ ### Breaking changes
9
+ - renamed register_response_handler to _response_handler
10
+ - replace before_fire and after_fire hooks with proper callbacks before, after and around fire
11
+
12
+ ### Updated
13
+ - error now includes more information about request and headers
14
+
15
+ # v0.3.4 2021-01-05
16
+
17
+ ### Updated
18
+
19
+ - Loosen ActiveSupport version requirement to allow 6.1
20
+
21
+ # v0.3.3 2020-09-30
22
+
23
+ ### Updated NxtRegistry
24
+
25
+ [Compare v0.3.2...v0.3.3](https://github.com/nxt-insurance/nxt_http_client/compare/v0.3.2...v0.3.3)
26
+
1
27
  # v0.2.10 2020-03-10
2
28
 
3
29
  ### Refactored
data/Gemfile.lock CHANGED
@@ -1,79 +1,79 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_http_client (0.3.1)
5
- activesupport (~> 6.0.0)
4
+ nxt_http_client (1.0.1)
5
+ activesupport (~> 6.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.0.3.2)
12
+ activesupport (6.1.1)
13
13
  concurrent-ruby (~> 1.0, >= 1.0.2)
14
- i18n (>= 0.7, < 2)
15
- minitest (~> 5.1)
16
- tzinfo (~> 1.1)
17
- zeitwerk (~> 2.2, >= 2.2.2)
14
+ i18n (>= 1.6, < 2)
15
+ minitest (>= 5.1)
16
+ tzinfo (~> 2.0)
17
+ zeitwerk (~> 2.3)
18
18
  addressable (2.7.0)
19
19
  public_suffix (>= 2.0.2, < 5.0)
20
- coderay (1.1.2)
21
- concurrent-ruby (1.1.7)
22
- crack (0.4.3)
23
- safe_yaml (~> 1.0.0)
24
- diff-lcs (1.3)
20
+ coderay (1.1.3)
21
+ concurrent-ruby (1.1.8)
22
+ crack (0.4.5)
23
+ rexml
24
+ diff-lcs (1.4.4)
25
25
  ethon (0.12.0)
26
26
  ffi (>= 1.3.0)
27
- ffi (1.13.1)
27
+ ffi (1.14.2)
28
28
  hashdiff (1.0.1)
29
- i18n (1.8.5)
29
+ i18n (1.8.8)
30
30
  concurrent-ruby (~> 1.0)
31
31
  method_source (1.0.0)
32
- minitest (5.14.1)
33
- nxt_registry (0.2.0)
32
+ minitest (5.14.3)
33
+ nxt_registry (0.3.8)
34
34
  activesupport
35
- nxt_vcr_harness (0.1.3)
35
+ nxt_vcr_harness (0.1.4)
36
36
  rspec (~> 3.0)
37
- vcr (~> 5.0)
37
+ vcr (~> 6.0)
38
38
  pry (0.13.1)
39
39
  coderay (~> 1.1)
40
40
  method_source (~> 1.0)
41
- public_suffix (4.0.3)
42
- rake (13.0.1)
43
- redis (4.2.1)
44
- rspec (3.9.0)
45
- rspec-core (~> 3.9.0)
46
- rspec-expectations (~> 3.9.0)
47
- rspec-mocks (~> 3.9.0)
48
- rspec-core (3.9.0)
49
- rspec-support (~> 3.9.0)
50
- rspec-expectations (3.9.0)
41
+ public_suffix (4.0.6)
42
+ rake (13.0.3)
43
+ redis (4.2.5)
44
+ rexml (3.2.4)
45
+ rspec (3.10.0)
46
+ rspec-core (~> 3.10.0)
47
+ rspec-expectations (~> 3.10.0)
48
+ rspec-mocks (~> 3.10.0)
49
+ rspec-core (3.10.1)
50
+ rspec-support (~> 3.10.0)
51
+ rspec-expectations (3.10.1)
51
52
  diff-lcs (>= 1.2.0, < 2.0)
52
- rspec-support (~> 3.9.0)
53
- rspec-mocks (3.9.0)
53
+ rspec-support (~> 3.10.0)
54
+ rspec-mocks (3.10.2)
54
55
  diff-lcs (>= 1.2.0, < 2.0)
55
- rspec-support (~> 3.9.0)
56
- rspec-support (3.9.0)
56
+ rspec-support (~> 3.10.0)
57
+ rspec-support (3.10.2)
57
58
  rspec_junit_formatter (0.4.1)
58
59
  rspec-core (>= 2, < 4, != 2.12.0)
59
- safe_yaml (1.0.5)
60
- thread_safe (0.3.6)
60
+ timecop (0.9.2)
61
61
  typhoeus (1.4.0)
62
62
  ethon (>= 0.9.0)
63
- tzinfo (1.2.7)
64
- thread_safe (~> 0.1)
65
- vcr (5.1.0)
66
- webmock (3.8.3)
63
+ tzinfo (2.0.4)
64
+ concurrent-ruby (~> 1.0)
65
+ vcr (6.0.0)
66
+ webmock (3.11.2)
67
67
  addressable (>= 2.3.6)
68
68
  crack (>= 0.3.2)
69
69
  hashdiff (>= 0.4.0, < 2.0.0)
70
- zeitwerk (2.4.0)
70
+ zeitwerk (2.4.2)
71
71
 
72
72
  PLATFORMS
73
- ruby
73
+ x86_64-darwin-19
74
74
 
75
75
  DEPENDENCIES
76
- bundler (~> 1.17)
76
+ bundler (~> 2.2)
77
77
  nxt_http_client!
78
78
  nxt_vcr_harness
79
79
  pry
@@ -81,8 +81,9 @@ DEPENDENCIES
81
81
  redis
82
82
  rspec (~> 3.0)
83
83
  rspec_junit_formatter
84
+ timecop
84
85
  vcr
85
86
  webmock
86
87
 
87
88
  BUNDLED WITH
88
- 1.17.3
89
+ 2.2.4
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,72 +25,51 @@ 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
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
28
43
 
44
+ private
45
+
46
+ attr_reader :url
47
+ end
48
+ ```
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
29
55
  configure do |config|
30
56
  config.base_url = 'www.example.com'
31
- # In your subclasses you probably want to deep_merge options in order to not overwrite options inherited
32
- # from the parent class. Of course this will not influence the parent class and you can also reset them
33
- # to a new hash here.
34
57
  config.request_options.deep_merge!(
35
58
  headers: { API_KEY: '1993' },
36
59
  method: :get,
37
60
  followlocation: true
38
61
  )
39
- # Be aware that the result of x_request_id_proc will be hashed into the cache key and thus might cause
40
- # your request not to be cached if not used properly
41
62
  config.x_request_id_proc = -> { ('a'..'z').to_a.shuffle.take(10).join }
42
63
  end
43
64
 
44
- register_response_handler do |handler|
45
- handler.on(:error) do |response|
46
- Raven.extra_context(error_details: error.to_h) # call error.to_h to inspect request and response
47
- raise StandardError, "I can't handle this: #{response.code}"
48
- end
65
+ log do |info|
66
+ Rails.logger.info(info)
49
67
  end
50
68
 
51
- before_fire do |client, request, handler|
52
- # Will be called before fire
53
- handler.on!(200) do |response|
54
- # reconfigure your handler before fire
55
- end
56
- end
57
-
58
- after_fire do |client, request, response, result, error|
59
- # Will be called after fire. You probably want to return the result here in order for your code
60
- # to be able to access the result from the response handler from before.
61
-
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
- if error
65
- raise error
66
- else
67
- result
68
- end
69
- end
70
-
71
- def fetch_details
72
- fire('details', method: :get) do |handler|
73
- handler.on(:success) do |response|
74
- response.body
75
- end
76
-
77
- handler.on(404) do |response|
78
- raise StandardError, '404'
79
- end
80
-
81
- # You can also fuzzy match response codes using the wildcard *
82
- handler.on('5**') do |response|
83
- raise StandardError, 'This is bad'
84
- end
85
- end
86
- end
87
-
88
- def update
89
- # there are also convenience methods for all http verbs (get post patch put delete head)
90
- post(params: { my: 'payload' }) do |handler|
91
- # would post to the base_url
69
+ response_handler do |handler|
70
+ handler.on(:error) do |response|
71
+ Raven.extra_context(error_details: error.to_h)
72
+ raise StandardError, "I can't handle this: #{response.code}"
92
73
  end
93
74
  end
94
75
  end
@@ -96,11 +77,10 @@ end
96
77
 
97
78
  ### HTTP Methods
98
79
 
99
- 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.
100
81
 
101
82
  ```ruby
102
- class MyClient < NxtHttpClient
103
-
83
+ class Client < NxtHttpClient
104
84
  def initialize(url)
105
85
  @url = url
106
86
  end
@@ -129,21 +109,22 @@ class MyClient < NxtHttpClient
129
109
  end
130
110
  ```
131
111
 
132
-
133
112
  ### configure
134
113
 
135
- Register default request options on the class level. Available options are `request_options` that are passed directly to
136
- 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`.
137
116
 
138
- ### register_response_handler
117
+ ### response_handler
139
118
 
140
- Register a default response handler for your client class.
141
- 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.
142
121
 
143
122
  ### fire
144
123
 
145
- Use `fire('uri', **request_options)` to actually fire your requests and define what to do with the response by using
146
- 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
147
128
  and more common callbacks will come later in case none of the specific callbacks matched. It this is not what you want you
148
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
149
130
  to match a group of response by status code. `handler.on('4**') { ... }` basically would match all client errors.
@@ -182,15 +163,83 @@ end
182
163
 
183
164
  ### Callbacks around fire
184
165
 
185
- You can also hook into the before_fire and after_fire callbacks to do something before and after the actual request is executed.
186
- These callbacks are inherited down the class hierarchy but are not being chained. Meaning when you overwrite those in your subclass,
187
- 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
+
188
188
 
189
189
  ### NxtHttpClient::Error
190
190
 
191
191
  NxtHttpClient also provides an error base class that you might want to use as the base for your client errors.
192
192
  It comes with a nice set of useful methods. You can ask the error for the request and response options since it
193
- 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
+ ### Logging
197
+
198
+ NxtHttpClient also comes with a log method on the class level that you can pass a proc if you want to log your request.
199
+ Your proc needs to accept an argument in order to get access to information about the request and response made.
200
+
201
+ ```ruby
202
+ log do |info|
203
+ Rails.logger.info(info)
204
+ end
205
+
206
+ # info is a hash that is implemented as follows:
207
+
208
+ {
209
+ client: client,
210
+ started_at: started_at,
211
+ request: request,
212
+ finished_at: now,
213
+ elapsed_time_in_milliseconds: finished_at - started_at,
214
+ response: request.response,
215
+ http_status: request.response&.code
216
+ }
217
+ ```
218
+
219
+ ### Caching
220
+
221
+ Typhoeus ships with caching built in. Checkout the [typhoeus](https://github.com/typhoeus/typhoeus) docu to figure out
222
+ how to set it up. NxtHttpClient builds some functionality on top of this and offer to cache requests within the current
223
+ thread or globally. You can simply make use of it by providing one of the caching options `:thread` or`:global` as config
224
+ request option or the actual request options when building the request.
225
+
226
+ ```ruby
227
+ class Client < NxtHttpClient::Client
228
+ configure do |config|
229
+ config.request_options = { cache: :thread }
230
+ end
231
+
232
+ response_handler do |handler|
233
+ handler.on(200) do |response|
234
+ # ...
235
+ end
236
+ end
237
+
238
+ def call
239
+ get('.../url.com', cache: :thread) # configure caching per request level
240
+ end
241
+ end
242
+ ```
194
243
 
195
244
  ## Development
196
245
 
@@ -200,7 +249,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
200
249
 
201
250
  ## Contributing
202
251
 
203
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/nxt_http_client.
252
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nxt-insurance/nxt_http_client.
204
253
 
205
254
  ## License
206
255
 
@@ -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'
@@ -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['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['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
@@ -13,7 +13,7 @@ module NxtHttpClient
13
13
  alias_method :to_s, :message
14
14
 
15
15
  def default_message
16
- "NxtHttpClient::Error::#{response_code}"
16
+ "#{self.class.name}::#{response_code}"
17
17
  end
18
18
 
19
19
  def to_h
@@ -23,12 +23,14 @@ module NxtHttpClient
23
23
  response_code: response_code,
24
24
  request_options: request_options,
25
25
  response_headers: response_headers,
26
- body: body
26
+ request_headers: request_headers,
27
+ body: body,
28
+ x_request_id: x_request_id
27
29
  }
28
30
  end
29
31
 
30
32
  def body
31
- if response_content_type&.starts_with?('application/json')
33
+ if response_content_type&.starts_with?(ApplicationJson)
32
34
  JSON.parse(response.body)
33
35
  else
34
36
  response.body
@@ -49,6 +51,10 @@ module NxtHttpClient
49
51
  request.url
50
52
  end
51
53
 
54
+ def x_request_id
55
+ request_headers[XRequestId]
56
+ end
57
+
52
58
  def request_options
53
59
  @request_options ||= (request.original_options || {}).with_indifferent_access
54
60
  end
@@ -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,18 +36,14 @@ module NxtHttpClient
36
36
 
37
37
  def callback_for_response(response)
38
38
  key_from_response = response.code.to_s
39
- return callbacks['any'] if callbacks['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[first_matching_key] ||
47
- response.success? && callbacks['success'] ||
48
- response.timed_out? && callbacks['timed_out'] ||
49
- !response.success? && callbacks['error'] ||
50
- callbacks['others']
42
+ callbacks.resolve(key_from_response) ||
43
+ response.success? && callbacks.resolve('success') ||
44
+ response.timed_out? && callbacks.resolve('timed_out') ||
45
+ !response.success? && callbacks.resolve('error') ||
46
+ callbacks.resolve('others')
51
47
  end
52
48
 
53
49
  def callbacks
@@ -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.1"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -36,10 +36,10 @@ Gem::Specification.new do |spec|
36
36
  spec.require_paths = ['lib']
37
37
 
38
38
  spec.add_dependency 'typhoeus'
39
- spec.add_dependency 'activesupport', '~> 6.0.0'
39
+ spec.add_dependency 'activesupport', '~> 6.0'
40
40
  spec.add_dependency 'nxt_registry'
41
41
 
42
- spec.add_development_dependency 'bundler', '~> 1.17'
42
+ spec.add_development_dependency 'bundler', '~> 2.2'
43
43
  spec.add_development_dependency 'rake', '~> 13.0'
44
44
  spec.add_development_dependency 'rspec', '~> 3.0'
45
45
  spec.add_development_dependency 'pry'
@@ -48,4 +48,5 @@ Gem::Specification.new do |spec|
48
48
  spec.add_development_dependency 'nxt_vcr_harness'
49
49
  spec.add_development_dependency 'redis'
50
50
  spec.add_development_dependency 'rspec_junit_formatter'
51
+ spec.add_development_dependency 'timecop'
51
52
  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.1
4
+ version: 1.0.1
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: 2020-08-10 00:00:00.000000000 Z
14
+ date: 2021-02-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: typhoeus
@@ -33,14 +33,14 @@ dependencies:
33
33
  requirements:
34
34
  - - "~>"
35
35
  - !ruby/object:Gem::Version
36
- version: 6.0.0
36
+ version: '6.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.0
43
+ version: '6.0'
44
44
  - !ruby/object:Gem::Dependency
45
45
  name: nxt_registry
46
46
  requirement: !ruby/object:Gem::Requirement
@@ -61,14 +61,14 @@ dependencies:
61
61
  requirements:
62
62
  - - "~>"
63
63
  - !ruby/object:Gem::Version
64
- version: '1.17'
64
+ version: '2.2'
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: '2.2'
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: rake
74
74
  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
@@ -203,9 +217,13 @@ files:
203
217
  - lib/nxt_http_client.rb
204
218
  - lib/nxt_http_client/client.rb
205
219
  - lib/nxt_http_client/client_dsl.rb
206
- - lib/nxt_http_client/default_config.rb
220
+ - lib/nxt_http_client/config.rb
207
221
  - lib/nxt_http_client/error.rb
222
+ - lib/nxt_http_client/fire_callbacks.rb
223
+ - lib/nxt_http_client/headers.rb
224
+ - lib/nxt_http_client/logger.rb
208
225
  - lib/nxt_http_client/response_handler.rb
226
+ - lib/nxt_http_client/undefined.rb
209
227
  - lib/nxt_http_client/version.rb
210
228
  - nxt_http_client.gemspec
211
229
  homepage: https://github.com/nxt-insurance