nxt_http_client 0.3.4 → 1.0.0

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: 851b1b29746c3a64735f9cf29c98378fbe49ada821dbcbc8c69ae28312596d0b
4
+ data.tar.gz: 3cfcbc1b1a0db863ede586976f83afdbb96b1ca06596bd0d7c9610e04171ca3b
5
5
  SHA512:
6
- metadata.gz: 2dea66682adbebf9800a68cbb3f9962d4b17623bc022163b7765fd1b4a889a78fc610dfc12719d137605e9e3b7c98b254e6f8f5ec80eca947318795b66dd48a2
7
- data.tar.gz: 8f04ddb779773c29db1a6f1af0cc73065b4da4e19e7e1a0b43eb5e1c6ec09d94acefb47ca0bdff0880377e2366354a631bc6751adad2ab73c31161108d2fa540
6
+ metadata.gz: 5c2297ee0db6aa262e4becba99f02f26e274b03a1858cc5bb3359a8f9a2fabfd0098cfdf0e5b018e13ecdd3afafa41a25251ee6131b86a41ff5d9346d80b8ea9
7
+ data.tar.gz: bb3e3ab2e9a0e884f1b4968e88a7dcb5c56b6a4e142c4454b3a6ac83c6504a5a99826ad5e412553d77ed871216e09e60e3a1067ee8e6a1ad79d040bc843e1350
@@ -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
 
@@ -1,3 +1,12 @@
1
+ # v1.0.0 2021-01-10
2
+
3
+ ### Breaking changes
4
+ - renamed register_response_handler to _response_handler
5
+ - replace before_fire and after_fire hooks with proper callbacks before, after and around fire
6
+
7
+ ### Updated
8
+ - error now includes more information about request and headers
9
+
1
10
  # v0.3.4 2021-01-05
2
11
 
3
12
  ### Updated
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_http_client (0.3.4)
4
+ nxt_http_client (1.0.0)
5
5
  activesupport (~> 6.0)
6
6
  nxt_registry
7
7
  typhoeus
@@ -9,7 +9,7 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- activesupport (6.1.0)
12
+ activesupport (6.1.1)
13
13
  concurrent-ruby (~> 1.0, >= 1.0.2)
14
14
  i18n (>= 1.6, < 2)
15
15
  minitest (>= 5.1)
@@ -24,12 +24,12 @@ GEM
24
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
29
  i18n (1.8.7)
30
30
  concurrent-ruby (~> 1.0)
31
31
  method_source (1.0.0)
32
- minitest (5.14.2)
32
+ minitest (5.14.3)
33
33
  nxt_registry (0.3.6)
34
34
  activesupport
35
35
  nxt_vcr_harness (0.1.4)
@@ -57,6 +57,7 @@ GEM
57
57
  rspec-support (3.10.1)
58
58
  rspec_junit_formatter (0.4.1)
59
59
  rspec-core (>= 2, < 4, != 2.12.0)
60
+ timecop (0.9.2)
60
61
  typhoeus (1.4.0)
61
62
  ethon (>= 0.9.0)
62
63
  tzinfo (2.0.4)
@@ -69,10 +70,10 @@ GEM
69
70
  zeitwerk (2.4.2)
70
71
 
71
72
  PLATFORMS
72
- ruby
73
+ x86_64-darwin-19
73
74
 
74
75
  DEPENDENCIES
75
- bundler (~> 1.17)
76
+ bundler (~> 2.2)
76
77
  nxt_http_client!
77
78
  nxt_vcr_harness
78
79
  pry
@@ -80,8 +81,9 @@ DEPENDENCIES
80
81
  redis
81
82
  rspec (~> 3.0)
82
83
  rspec_junit_formatter
84
+ timecop
83
85
  vcr
84
86
  webmock
85
87
 
86
88
  BUNDLED WITH
87
- 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,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.
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
32
43
 
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
44
+ private
35
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
36
55
  configure do |config|
37
56
  config.base_url = 'www.example.com'
38
57
  config.request_options.deep_merge!(
@@ -43,54 +62,14 @@ class MyClient < NxtHttpClient
43
62
  config.x_request_id_proc = -> { ('a'..'z').to_a.shuffle.take(10).join }
44
63
  end
45
64
 
46
- register_response_handler do |handler|
47
- handler.on(:error) do |response|
48
- Raven.extra_context(error_details: error.to_h) # call error.to_h to inspect request and response
49
- raise StandardError, "I can't handle this: #{response.code}"
50
- end
51
- 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
65
+ log do |info|
66
+ Rails.logger.info(info)
58
67
  end
59
68
 
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
- # ...
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}"
94
73
  end
95
74
  end
96
75
  end
@@ -98,11 +77,10 @@ end
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.
@@ -184,15 +163,83 @@ end
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
+ ### 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
+ ```
196
243
 
197
244
  ## Development
198
245
 
@@ -202,7 +249,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
202
249
 
203
250
  ## Contributing
204
251
 
205
- 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.
206
253
 
207
254
  ## License
208
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.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
@@ -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,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.0"
3
3
  end
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
39
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.4
4
+ version: 1.0.0
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: 2021-01-10 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: typhoeus
@@ -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