fluffle 0.0.2 → 0.0.3

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: c36afa6e383f47841a3217e98ca0e40a6080024c
4
- data.tar.gz: acae18e71e14ed4414e74bf8d9f166a015849ffc
3
+ metadata.gz: 77c72df240211039c526d501f7f34af3bb36330c
4
+ data.tar.gz: f71668dca91239e6b211be0239d43f5a72dd51d4
5
5
  SHA512:
6
- metadata.gz: c90963f8af3c68307b79571253dbbce7be639b7d76f655dc4811cdbcf7dc602bd0e0f60e46d536c817e2eb3b5c90a5efe326453fcaaf0ebd766de0c3a9f53a0b
7
- data.tar.gz: bc776904911b7098d7d9e67be211f34d251d11c29f70197346157187e18fd4471d2fbc8d4cc4efff675240bf60a6a03241c336a8bd706f17e407b2c3f7fdc0e1
6
+ metadata.gz: e195c331ce7a3817cef21c76f05f2253b46929b1355b4fad38c617c2b15ab53c7b05ff74c2a9c4296bf0ca5d375a0598b6c9bfcd5f19392595c00edb8dc25997
7
+ data.tar.gz: d1c08745886f2031b8582890b1e28f09783e66b9f93d45e176b3707d8cb6012eabd46d70967bd2c58149b0d9007cc65c38f36949cfda72d15296660a42211e97
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  .DS_Store
2
+ Gemfile.lock
2
3
  pkg
data/fluffle.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
12
12
  s.homepage = 'https://github.com/Everlane/fluffle'
13
13
  s.license = 'MIT'
14
14
 
15
- s.required_ruby_version = '>= 1.9.3'
15
+ s.required_ruby_version = '>= 2.0'
16
16
 
17
17
  s.add_dependency 'bunny', '~> 2.5.0'
18
18
  s.add_dependency 'concurrent-ruby', '~> 1.0.2'
@@ -9,7 +9,13 @@ module Fluffle
9
9
  class Client
10
10
  include Connectable
11
11
 
12
+ attr_accessor :default_timeout
13
+ attr_accessor :logger
14
+
12
15
  def initialize(url:)
16
+ @default_timeout = 5
17
+ @logger = Fluffle.logger
18
+
13
19
  self.connect url
14
20
 
15
21
  @uuid = UUIDTools::UUID.timestamp_create.to_s
@@ -27,20 +33,33 @@ module Fluffle
27
33
 
28
34
  def subscribe
29
35
  @reply_queue.subscribe do |delivery_info, properties, payload|
30
- self.handle_resposne delivery_info: delivery_info,
31
- properties: properties,
32
- payload: payload
36
+ self.handle_reply delivery_info: delivery_info,
37
+ properties: properties,
38
+ payload: payload
33
39
  end
34
40
  end
35
41
 
36
- def handle_resposne(delivery_info:, properties:, payload:)
37
- payload = Oj.load payload
42
+ # Fetch and set the `IVar` with a response from the server. This method is
43
+ # called from the reply queue's background thread; the main thread will
44
+ # normally be waiting for the `IVar` to be set.
45
+ def handle_reply(delivery_info:, properties:, payload:)
46
+ payload = Oj.load payload
47
+ id = payload['id']
38
48
 
39
- ivar = @pending_responses.delete payload['id']
40
- ivar.set payload
49
+ ivar = @pending_responses.delete id
50
+
51
+ if ivar
52
+ ivar.set payload
53
+ else
54
+ self.logger.error "Missing pending response IVar: id=#{id || 'null'}"
55
+ end
41
56
  end
42
57
 
43
- def call(method, params = [], queue: 'default')
58
+ def call(method, params = [], queue: 'default', **opts)
59
+ # Using `.fetch` here so that we can pass `nil` as the timeout and have
60
+ # it be respected
61
+ timeout = opts.fetch :timeout, self.default_timeout
62
+
44
63
  id = random_bytes_as_hex 8
45
64
 
46
65
  payload = {
@@ -50,24 +69,66 @@ module Fluffle
50
69
  'params' => params
51
70
  }
52
71
 
53
- @exchange.publish Oj.dump(payload), routing_key: Fluffle.request_queue_name(queue),
54
- correlation_id: id,
55
- reply_to: @reply_queue.name
56
-
57
- ivar = Concurrent::IVar.new
58
- @pending_responses[id] = ivar
59
-
60
- response = ivar.value
72
+ response = publish_and_wait payload, queue: queue,
73
+ timeout: timeout
61
74
 
62
75
  if response['result']
63
76
  response['result']
64
77
  else
65
- raise # TODO: Raise known error subclass to be caught by client code
78
+ error = response['error'] || {}
79
+
80
+ raise Errors::CustomError.new code: error['code'] || 0,
81
+ message: error['message'] || "Missing both `result' and `error' on Response object",
82
+ data: error['data']
66
83
  end
67
84
  end
68
85
 
69
86
  protected
70
87
 
88
+ # Publish a payload to the server and wait (block) for the response
89
+ #
90
+ # Returns a Hash from the parsed JSON response from the server
91
+ # Raises TimeoutError if server failed to respond in time
92
+ def publish_and_wait(payload, queue:, timeout:)
93
+ id = payload['id']
94
+
95
+ ivar = self.publish payload, queue: queue
96
+
97
+ response = ivar.value timeout
98
+
99
+ if ivar.incomplete?
100
+ method = payload['method']
101
+ arity = payload['params']&.length || 0
102
+ raise Errors::TimeoutError.new("Timed out waiting for response to `#{method}/#{arity}'")
103
+ end
104
+
105
+ return response
106
+ ensure
107
+ # Don't leak the `IVar` if it timed out
108
+ @pending_responses.delete id
109
+ end
110
+
111
+ # Create an `IVar` future for the response, store that in
112
+ # `@pending_responses`, and finally publish the payload to the server
113
+ #
114
+ # Returns the `IVar`
115
+ def publish(payload, queue:)
116
+ id = payload['id']
117
+
118
+ opts = {
119
+ routing_key: Fluffle.request_queue_name(queue),
120
+ correlation_id: id,
121
+ reply_to: @reply_queue.name
122
+ }
123
+
124
+ ivar = Concurrent::IVar.new
125
+ @pending_responses[id] = ivar
126
+
127
+ @exchange.publish Oj.dump(payload), opts
128
+
129
+ ivar
130
+ end
131
+
71
132
  def random_bytes_as_hex(bytes)
72
133
  # Adapted from `SecureRandom.hex`
73
134
  @prng.bytes(bytes).unpack('H*')[0]
@@ -10,11 +10,14 @@ module Fluffle
10
10
  end
11
11
  end
12
12
 
13
+ class TimeoutError < StandardError
14
+ end
15
+
13
16
  # Raise this within your own code to get an error that will be faithfully
14
17
  # translated into the code, message, and data member fields of the
15
18
  # spec's `Error` response object
16
19
  class CustomError < BaseError
17
- attr_accessor :data
20
+ attr_accessor :code, :data
18
21
 
19
22
  def initialize(code: 0, message:, data: nil)
20
23
  @code = code
@@ -1,3 +1,3 @@
1
1
  module Fluffle
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
data/lib/fluffle.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'bunny'
2
+ require 'logger'
2
3
 
3
4
  require 'fluffle/version'
4
5
  require 'fluffle/client'
@@ -17,4 +18,8 @@ module Fluffle
17
18
  def self.response_queue_name(name)
18
19
  "fluffle.responses.#{name}"
19
20
  end
21
+
22
+ def self.logger
23
+ @logger ||= Logger.new $stdout
24
+ end
20
25
  end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fluffle::Client do
4
+ subject do
5
+ Fluffle::Client.new url: nil
6
+ end
7
+
8
+ before do
9
+ @exchange_spy = exchange_spy = spy 'Exchange'
10
+
11
+ subject.instance_eval do
12
+ @exchange = exchange_spy
13
+ end
14
+ end
15
+
16
+ describe '#call' do
17
+ def prepare_response(payload)
18
+ ->(id) do
19
+ response_payload = { 'jsonrpc' => '2.0', 'id' => id }.merge(payload)
20
+
21
+ subject.handle_reply delivery_info: double('DeliveryInfo'),
22
+ properties: double('Properties'),
23
+ payload: Oj.dump(response_payload)
24
+ end
25
+ end
26
+
27
+ it 'returns the value on result from server' do
28
+ method = 'foo'
29
+ result = 'bar'
30
+
31
+ respond = prepare_response 'result' => result
32
+
33
+ allow(@exchange_spy).to receive(:publish) do |payload, opts|
34
+ payload = Oj.load(payload)
35
+
36
+ expect(payload).to include({
37
+ 'id' => kind_of(String),
38
+ 'method' => method
39
+ })
40
+
41
+ respond.call payload['id']
42
+ end
43
+
44
+ expect(subject.call(method)).to eq(result)
45
+ end
46
+
47
+ it 'raises on error from server' do
48
+ code = 1337
49
+ message = 'Uh-oh!'
50
+
51
+ respond = prepare_response 'error' => {
52
+ 'code' => code,
53
+ 'message' => message
54
+ }
55
+
56
+ allow(@exchange_spy).to receive(:publish) do |payload, _opts|
57
+ payload = Oj.load(payload)
58
+
59
+ respond.call payload['id']
60
+ end
61
+
62
+ expect { subject.call('will.raise') }.to raise_error do |error|
63
+ expect(error).to be_a Fluffle::Errors::CustomError
64
+ expect(error.code).to eq code
65
+ expect(error.message).to eq message
66
+ expect(error.data).to be_nil
67
+ end
68
+ end
69
+
70
+ it 'raises on timeout' do
71
+ allow(@exchange_spy).to receive(:publish)
72
+
73
+ t0 = Time.now
74
+
75
+ expect {
76
+ subject.call 'whatever', timeout: 0.01
77
+ }.to raise_error(Fluffle::Errors::TimeoutError)
78
+
79
+ expect(Time.now - t0).to be < 0.1
80
+ end
81
+ end
82
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluffle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dirk Gadsden
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-09 00:00:00.000000000 Z
11
+ date: 2016-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -118,7 +118,6 @@ files:
118
118
  - ".gitignore"
119
119
  - ".rspec"
120
120
  - Gemfile
121
- - Gemfile.lock
122
121
  - LICENSE
123
122
  - README.md
124
123
  - Rakefile
@@ -135,6 +134,7 @@ files:
135
134
  - lib/fluffle/handlers/dispatcher.rb
136
135
  - lib/fluffle/server.rb
137
136
  - lib/fluffle/version.rb
137
+ - spec/client_spec.rb
138
138
  - spec/server_spec.rb
139
139
  - spec/spec_helper.rb
140
140
  homepage: https://github.com/Everlane/fluffle
@@ -149,7 +149,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
- version: 1.9.3
152
+ version: '2.0'
153
153
  required_rubygems_version: !ruby/object:Gem::Requirement
154
154
  requirements:
155
155
  - - ">="
data/Gemfile.lock DELETED
@@ -1,52 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- fluffle (0.0.2)
5
- bunny (~> 2.5.0)
6
- concurrent-ruby (~> 1.0.2)
7
- oj (~> 2.17.1)
8
- uuidtools (~> 2.1.5)
9
-
10
- GEM
11
- remote: https://rubygems.org/
12
- specs:
13
- amq-protocol (2.0.1)
14
- bunny (2.5.0)
15
- amq-protocol (>= 2.0.1)
16
- coderay (1.1.1)
17
- concurrent-ruby (1.0.2)
18
- diff-lcs (1.2.5)
19
- method_source (0.8.2)
20
- oj (2.17.1)
21
- pry (0.10.4)
22
- coderay (~> 1.1.0)
23
- method_source (~> 0.8.1)
24
- slop (~> 3.4)
25
- rake (11.2.2)
26
- rspec (3.4.0)
27
- rspec-core (~> 3.4.0)
28
- rspec-expectations (~> 3.4.0)
29
- rspec-mocks (~> 3.4.0)
30
- rspec-core (3.4.4)
31
- rspec-support (~> 3.4.0)
32
- rspec-expectations (3.4.0)
33
- diff-lcs (>= 1.2.0, < 2.0)
34
- rspec-support (~> 3.4.0)
35
- rspec-mocks (3.4.1)
36
- diff-lcs (>= 1.2.0, < 2.0)
37
- rspec-support (~> 3.4.0)
38
- rspec-support (3.4.1)
39
- slop (3.6.0)
40
- uuidtools (2.1.5)
41
-
42
- PLATFORMS
43
- ruby
44
-
45
- DEPENDENCIES
46
- fluffle!
47
- pry (~> 0.10.1)
48
- rake (~> 11.2.2)
49
- rspec (~> 3.4.0)
50
-
51
- BUNDLED WITH
52
- 1.12.5