hal-client 3.15.0 → 3.16.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
  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