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 +4 -4
- data/.gitignore +1 -0
- data/fluffle.gemspec +1 -1
- data/lib/fluffle/client.rb +78 -17
- data/lib/fluffle/errors.rb +4 -1
- data/lib/fluffle/version.rb +1 -1
- data/lib/fluffle.rb +5 -0
- data/spec/client_spec.rb +82 -0
- metadata +4 -4
- data/Gemfile.lock +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77c72df240211039c526d501f7f34af3bb36330c
|
4
|
+
data.tar.gz: f71668dca91239e6b211be0239d43f5a72dd51d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e195c331ce7a3817cef21c76f05f2253b46929b1355b4fad38c617c2b15ab53c7b05ff74c2a9c4296bf0ca5d375a0598b6c9bfcd5f19392595c00edb8dc25997
|
7
|
+
data.tar.gz: d1c08745886f2031b8582890b1e28f09783e66b9f93d45e176b3707d8cb6012eabd46d70967bd2c58149b0d9007cc65c38f36949cfda72d15296660a42211e97
|
data/.gitignore
CHANGED
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 = '>=
|
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'
|
data/lib/fluffle/client.rb
CHANGED
@@ -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.
|
31
|
-
|
32
|
-
|
36
|
+
self.handle_reply delivery_info: delivery_info,
|
37
|
+
properties: properties,
|
38
|
+
payload: payload
|
33
39
|
end
|
34
40
|
end
|
35
41
|
|
36
|
-
|
37
|
-
|
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
|
40
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
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]
|
data/lib/fluffle/errors.rb
CHANGED
@@ -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
|
data/lib/fluffle/version.rb
CHANGED
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
|
data/spec/client_spec.rb
ADDED
@@ -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.
|
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-
|
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:
|
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
|