nxt_http_client 0.3.0 → 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: 22aa2f16ec7170133779ae75097fdd0c164a3a3a793a98f8d565d6ddfd2ca96c
4
- data.tar.gz: c21b929937fe483cd7d56754d50ba37ae9d83b10bb120e5b772b425f3a04e559
3
+ metadata.gz: 851b1b29746c3a64735f9cf29c98378fbe49ada821dbcbc8c69ae28312596d0b
4
+ data.tar.gz: 3cfcbc1b1a0db863ede586976f83afdbb96b1ca06596bd0d7c9610e04171ca3b
5
5
  SHA512:
6
- metadata.gz: be208ed0c5e3b617d48dcca068c8c588e9748ab34f23a538d9039f6b7ed1649dd96f54ec0dbb716de3c823b5f6ff452b06eadfd79a85514f97106d4fef0d7fd4
7
- data.tar.gz: 0743b9d3371ff51381c8a0f5b45547bc86302f0282e8064fe7a93f268e195361ca055ed7a085df38b82653909542488cd6de02a2d5b95abcf547068215097278
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,24 @@
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
+
10
+ # v0.3.4 2021-01-05
11
+
12
+ ### Updated
13
+
14
+ - Loosen ActiveSupport version requirement to allow 6.1
15
+
16
+ # v0.3.3 2020-09-30
17
+
18
+ ### Updated NxtRegistry
19
+
20
+ [Compare v0.3.2...v0.3.3](https://github.com/nxt-insurance/nxt_http_client/compare/v0.3.2...v0.3.3)
21
+
1
22
  # v0.2.10 2020-03-10
2
23
 
3
24
  ### Refactored
@@ -5,4 +26,4 @@
5
26
  - [internal] Added CHANGELOG.MD
6
27
  - Refactored a bit
7
28
 
8
- [Compare v0.2.9...v0.2.10](https://github.com/nxt-insurance/nxt_http_client/compare/v0.2.9...v0.2.10)
29
+ [Compare v0.2.9...v0.3.0](https://github.com/nxt-insurance/nxt_http_client/compare/v0.2.9...v0.2.10)
@@ -1,79 +1,79 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_http_client (0.3.0)
5
- activesupport (~> 6.0.0)
4
+ nxt_http_client (1.0.0)
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.6)
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.7)
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.12.2)
27
+ ffi (1.14.2)
28
28
  hashdiff (1.0.1)
29
- i18n (1.8.5)
29
+ i18n (1.8.7)
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.6)
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.1)
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.1)
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.0)
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,91 +25,106 @@ 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|
65
+ log do |info|
66
+ Rails.logger.info(info)
67
+ end
68
+
69
+ response_handler do |handler|
45
70
  handler.on(:error) do |response|
46
- 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)
47
72
  raise StandardError, "I can't handle this: #{response.code}"
48
73
  end
49
74
  end
50
-
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
75
+ end
76
+ ```
77
+
78
+ ### HTTP Methods
79
+
80
+ In order to build a request and execute it NxtHttpClient implements all http standard methods.
81
+
82
+ ```ruby
83
+ class Client < NxtHttpClient
84
+ def initialize(url)
85
+ @url = url
56
86
  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
87
+
88
+ attr_reader :url
89
+
90
+ def fetch
91
+ get(url) do
92
+ handler.on(:success) { |response| response.body }
68
93
  end
69
94
  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
95
+
96
+ def create(params)
97
+ post(url, params: params) do
98
+ handler.on(:success) { |response| response.body }
85
99
  end
86
100
  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
101
+
102
+ def update(params)
103
+ put(url, params: params) do
104
+ handler.on(:success) { |response| response.body }
92
105
  end
93
106
  end
107
+
108
+ # ... there are others as you know ...
94
109
  end
95
110
  ```
96
111
 
97
112
  ### configure
98
113
 
99
- Register default request options on the class level. Available options are `request_options` that are passed directly to
100
- 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`.
101
116
 
102
- ### register_response_handler
117
+ ### response_handler
103
118
 
104
- Register a default response handler for your client class.
105
- 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.
106
121
 
107
122
  ### fire
108
123
 
109
- Use `fire('uri', **request_options)` to actually fire your requests and define what to do with the response by using
110
- 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
111
128
  and more common callbacks will come later in case none of the specific callbacks matched. It this is not what you want you
112
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
113
130
  to match a group of response by status code. `handler.on('4**') { ... }` basically would match all client errors.
@@ -146,15 +163,83 @@ end
146
163
 
147
164
  ### Callbacks around fire
148
165
 
149
- You can also hook into the before_fire and after_fire callbacks to do something before and after the actual request is executed.
150
- These callbacks are inherited down the class hierarchy but are not being chained. Meaning when you overwrite those in your subclass,
151
- 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
+
152
188
 
153
189
  ### NxtHttpClient::Error
154
190
 
155
191
  NxtHttpClient also provides an error base class that you might want to use as the base for your client errors.
156
192
  It comes with a nice set of useful methods. You can ask the error for the request and response options since it
157
- 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
+ ```
158
243
 
159
244
  ## Development
160
245
 
@@ -164,7 +249,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
164
249
 
165
250
  ## Contributing
166
251
 
167
- 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.
168
253
 
169
254
  ## License
170
255
 
@@ -3,13 +3,16 @@ 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
15
 
12
16
  module NxtHttpClient
13
17
  class Error < StandardError; end
14
- # Your code goes here...
15
18
  end
@@ -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
@@ -1,14 +1,19 @@
1
1
  module NxtHttpClient
2
2
  class Error < StandardError
3
- def initialize(response)
3
+ def initialize(response, message = nil)
4
4
  @response = response.blank? ? Typhoeus::Response.new : response
5
5
  @id = SecureRandom.uuid
6
+ @message = message || default_message
7
+
8
+ super(@message)
6
9
  end
7
10
 
8
- attr_reader :response, :id
11
+ attr_reader :response, :id, :message
12
+
13
+ alias_method :to_s, :message
9
14
 
10
- def to_s
11
- "NxtHttpClient::Error::#{response_code}"
15
+ def default_message
16
+ "#{self.class.name}::#{response_code}"
12
17
  end
13
18
 
14
19
  def to_h
@@ -18,12 +23,14 @@ module NxtHttpClient
18
23
  response_code: response_code,
19
24
  request_options: request_options,
20
25
  response_headers: response_headers,
21
- body: body
26
+ request_headers: request_headers,
27
+ body: body,
28
+ x_request_id: x_request_id
22
29
  }
23
30
  end
24
31
 
25
32
  def body
26
- if response_content_type == 'application/json'
33
+ if response_content_type&.starts_with?(ApplicationJson)
27
34
  JSON.parse(response.body)
28
35
  else
29
36
  response.body
@@ -44,6 +51,10 @@ module NxtHttpClient
44
51
  request.url
45
52
  end
46
53
 
54
+ def x_request_id
55
+ request_headers[XRequestId]
56
+ end
57
+
47
58
  def request_options
48
59
  @request_options ||= (request.original_options || {}).with_indifferent_access
49
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.0'
2
+ VERSION = "1.0.0"
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.0
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: 2020-07-30 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
@@ -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