hal-client 3.15.0 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a02928e0fcb08ea201d51a1b76e3db118ea2efde
4
- data.tar.gz: 51cf12b4ac3d35e742322d75ae86ca4a16f0f114
3
+ metadata.gz: 2e97fd75dd9bb812c2cdf28bcd0a9a316a94bb71
4
+ data.tar.gz: d260178d87921fe2031ae62a33952cdbbc7019a7
5
5
  SHA512:
6
- metadata.gz: 72e1e77e6fdc336f0d66755aa598a66213f021cfca90554b98cba699b63a83dd9eb82fb267a906c5e00c55f35953491e8aee3be8bc934c11e79f1e5617d7c63d
7
- data.tar.gz: 92fb2baaacfeca8b492e9c842a8cf5f9b9e77f29c76983918809895249a289058583b7cdece571d37fb5a054b0b09721315770b5d73f5c7c7a2ea01afc7f9580
6
+ metadata.gz: 6cd9c0e7192d66633ec12c0409a4cde8a8b6fb413965671f7590dd9bc77778399da2be2cc229567066a422d317c94eab89e7822869744ade921be700ee1c14f3
7
+ data.tar.gz: 77e05a49bc31a09950cce202d93936e9eea97c1776229b3d7c43f89e647bafa5618d97a3f099693622f295eecdac0c9cfe55cd3fc751564a1e3f1db60d5ad85c
@@ -20,4 +20,6 @@ class HalClient
20
20
 
21
21
  # Server responded with a 5xx status code
22
22
  HttpServerError = Class.new(HttpError)
23
+
24
+ TimeoutError = Class.new(StandardError)
23
25
  end
@@ -1,3 +1,3 @@
1
1
  class HalClient
2
- VERSION = "3.15.0"
2
+ VERSION = "3.16.0"
3
3
  end
data/lib/hal_client.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "hal_client/version"
2
2
  require 'http'
3
3
  require 'multi_json'
4
+ require 'benchmark'
4
5
 
5
6
  # Adapter used to access resources.
6
7
  class HalClient
@@ -31,11 +32,18 @@ class HalClient
31
32
  # the authorization header
32
33
  # :headers - a hash of other headers to send on each request.
33
34
  # :base_client - An HTTP::Client object to use.
35
+ # :logger - a Logger object to which benchmark and activity info
36
+ # will be written. Benchmark data will be written at info level
37
+ # and activity at debug level.
38
+ # :timeout - number of seconds that after which any request will be
39
+ # terminated and an exception raised. Default: Float::INFINITY
34
40
  def initialize(options={})
35
41
  @default_message_request_headers = HTTP::Headers.new
36
42
  @default_entity_request_headers = HTTP::Headers.new
37
43
  @auth_helper = as_callable(options.fetch(:authorization, NullAuthHelper))
38
44
  @base_client ||= options[:base_client]
45
+ @logger = options.fetch(:logger, NullLogger.new)
46
+ @timeout = options.fetch(:timeout, Float::INFINITY)
39
47
 
40
48
  default_message_request_headers.set('Accept', options[:accept]) if
41
49
  options[:accept]
@@ -65,6 +73,7 @@ class HalClient
65
73
  default_message_request_headers.set('Accept', accept_values.join(", "))
66
74
  # We can work with HAL so provide a back stop accept.
67
75
  end
76
+ protected :initialize
68
77
 
69
78
  # Returns a `Representation` of the resource identified by `url`.
70
79
  #
@@ -72,7 +81,9 @@ class HalClient
72
81
  # headers - custom header fields to use for this request
73
82
  def get(url, headers={})
74
83
  headers = auth_headers(url).merge(headers)
75
- interpret_response client_for_get(override_headers: headers).get(url)
84
+ client = client_for_get(override_headers: headers)
85
+ resp = bmtb("GET <#{url}>") { client.get(url) }
86
+ interpret_response resp
76
87
 
77
88
  rescue HttpError => e
78
89
  fail e.class.new("GET <#{url}> failed with code #{e.response.status}", e.response)
@@ -82,6 +93,8 @@ class HalClient
82
93
  protected
83
94
 
84
95
  def def_unsafe_request(method)
96
+ verb = method.to_s.upcase
97
+
85
98
  define_method(method) do |url, data, headers={}|
86
99
  headers = auth_headers(url).merge(headers)
87
100
 
@@ -94,11 +107,12 @@ class HalClient
94
107
  end
95
108
 
96
109
  begin
97
- interpret_response client_for_post(override_headers: headers)
98
- .request(method, url, body: req_body)
110
+ client = client_for_post(override_headers: headers)
111
+ resp = bmtb("#{verb} <#{url}>") { client.request(method, url, body: req_body) }
112
+ interpret_response resp
99
113
 
100
114
  rescue HttpError => e
101
- fail e.class.new("#{method.to_s.upcase} <#{url}> failed with code #{e.response.status}", e.response)
115
+ fail e.class.new("#{verb} <#{url}> failed with code #{e.response.status}", e.response)
102
116
  end
103
117
  end
104
118
  end
@@ -133,22 +147,17 @@ class HalClient
133
147
  headers = auth_headers(url).merge(headers)
134
148
 
135
149
  begin
136
- interpret_response client_for_post(override_headers: headers)
137
- .request(:delete, url)
150
+ client = client_for_post(override_headers: headers)
151
+ resp = bmtb("DELETE <#{url}>") { client.request(:delete, url) }
152
+ interpret_response resp
138
153
  rescue HttpError => e
139
154
  fail e.class.new("DELETE <#{url}> failed with code #{e.response.status}", e.response)
140
155
  end
141
156
  end
142
157
 
143
- class << self
144
- def delete(url, headers={})
145
- new.delete(url, headers)
146
- end
147
- end
148
-
149
158
  protected
150
159
 
151
- attr_reader :headers, :auth_helper
160
+ attr_reader :headers, :auth_helper, :logger, :timeout
152
161
 
153
162
  NullAuthHelper = ->(_url) { nil }
154
163
 
@@ -248,6 +257,33 @@ class HalClient
248
257
  [:content_type, /^content-type$/i].any?{|pat| pat === field_name}
249
258
  end
250
259
 
260
+ def bmtb(msg, &blk)
261
+ benchmark(msg) { timebox(msg, &blk) }
262
+ end
263
+
264
+ def timebox(msg, &blk)
265
+ if timeout < Float::INFINITY
266
+ Timeout.timeout(timeout, &blk)
267
+ else
268
+ yield
269
+ end
270
+
271
+ rescue Timeout::Error
272
+ timeout_ms = timeout * 1000
273
+ raise TimeoutError, "Killed %s for taking more than %.1fms." % [msg, timeout_ms]
274
+ end
275
+ def benchmark(msg, &blk)
276
+ result = nil
277
+ elapsed = Benchmark.realtime do
278
+ result = yield
279
+ end
280
+
281
+ logger.info '%s (%.1fms)' % [ msg, elapsed*1000 ]
282
+
283
+ result
284
+ end
285
+
286
+
251
287
  module EntryPointCovenienceMethods
252
288
  # Returns a `Representation` of the resource identified by `url`.
253
289
  #
@@ -266,6 +302,24 @@ class HalClient
266
302
  default_client.post(url, data, options)
267
303
  end
268
304
 
305
+ # Patch a `Representation` or `String` to the resource identified at `url`.
306
+ #
307
+ # url - The URL of the resource of interest.
308
+ # data - a `String` or an object that responds to `#to_hal`
309
+ # options - set of options to pass to `RestClient#get`
310
+ def patch(url, data, options={})
311
+ default_client.patch(url, data, options)
312
+ end
313
+
314
+ # Delete the resource identified at `url`.
315
+ #
316
+ # url - The URL of the resource of interest.
317
+ # options - set of options to pass to `RestClient#get`
318
+ def delete(url, options={})
319
+ default_client.delete(url, options)
320
+ end
321
+
322
+
269
323
  protected
270
324
 
271
325
  def default_client
@@ -274,4 +328,9 @@ class HalClient
274
328
  end
275
329
  extend EntryPointCovenienceMethods
276
330
 
331
+
332
+ class NullLogger
333
+ def info(*_); end
334
+ def debug(*_); end
335
+ end
277
336
  end
@@ -77,6 +77,43 @@ describe HalClient do
77
77
  expect(request.with(headers: { "DummyHeader" => "Test" })).to have_been_made
78
78
  end
79
79
  end
80
+
81
+ context "logging" do
82
+ let(:logger) { MemoryLogger.new }
83
+ subject(:client) { HalClient.new logger: logger }
84
+ it "logs the request" do
85
+ expect(logger.info_entries).to have(1).item
86
+ expect(logger.info_entries.first).to include "http://example.com/foo"
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ context "server takes too long" do
93
+ let!(:request) { stub_request(:any, "http://example.com/foo")
94
+ .with { sleep 1 } }
95
+
96
+ subject(:client) { HalClient.new timeout: 0.1 }
97
+
98
+ it "#get raises TimeoutError" do
99
+ expect{client.get "http://example.com/foo"}.to raise_exception HalClient::TimeoutError
100
+ end
101
+
102
+ it "#get includes url and verb in the error message" do
103
+ err = client.get("http://example.com/foo") rescue $!
104
+ expect(err.to_s).to include "GET <http://example.com/foo>"
105
+ end
106
+
107
+ it "#post raises HttpClientError" do
108
+ expect{client.post "http://example.com/foo", "foo"}.to raise_exception HalClient::TimeoutError
109
+ end
110
+
111
+ it "#post attaches response to the raise error" do
112
+ err = client.post("http://example.com/foo", "") rescue $!
113
+ expect(err.to_s).to include "POST <http://example.com/foo>"
114
+ end
115
+
116
+
80
117
  end
81
118
 
82
119
  context "server responds with client error" do
@@ -233,6 +270,16 @@ describe HalClient do
233
270
  expect(post_request).to have_been_made
234
271
  end
235
272
  end
273
+
274
+ context "logging" do
275
+ before do return_val end
276
+ let(:logger) { MemoryLogger.new }
277
+ subject(:client) { HalClient.new logger: logger }
278
+ it "logs the request" do
279
+ expect(logger.info_entries).to have(1).item
280
+ expect(logger.info_entries.first).to include "http://example.com/foo"
281
+ end
282
+ end
236
283
  end
237
284
 
238
285
  describe ".post(<url>)" do
@@ -298,5 +345,24 @@ describe HalClient do
298
345
  to_return body: "{}" }
299
346
 
300
347
  let!(:request) { stub_request(:get, "http://example.com/foo").
301
- to_return body: "{}" }
348
+ to_return body: "{}" }
349
+
350
+ class MemoryLogger
351
+ def info_entries
352
+ @info_entries ||= []
353
+ end
354
+ def debug_entries
355
+ @debug_entries ||= []
356
+ end
357
+
358
+ def info(msg=nil)
359
+ msg = yield if block_given?
360
+ info_entries << msg
361
+ end
362
+
363
+ def debug(msg=nil)
364
+ msg = yield if block_given?
365
+ debug_entries << msg
366
+ end
367
+ end
302
368
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hal-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.15.0
4
+ version: 3.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-01 00:00:00.000000000 Z
11
+ date: 2016-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http