fluffle 0.0.2 → 0.0.3

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: 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