fluffle 0.0.1
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 +7 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +21 -0
- data/README.md +33 -0
- data/Rakefile +4 -0
- data/examples/client.rb +14 -0
- data/examples/server.rb +12 -0
- data/fluffle.gemspec +28 -0
- data/fluffle.jpg +0 -0
- data/lib/fluffle/client.rb +76 -0
- data/lib/fluffle/connectable.rb +20 -0
- data/lib/fluffle/errors.rb +50 -0
- data/lib/fluffle/handlers/base.rb +9 -0
- data/lib/fluffle/handlers/delegator.rb +13 -0
- data/lib/fluffle/handlers/dispatcher.rb +27 -0
- data/lib/fluffle/server.rb +128 -0
- data/lib/fluffle/version.rb +3 -0
- data/lib/fluffle.rb +20 -0
- data/spec/server_spec.rb +116 -0
- data/spec/spec_helper.rb +3 -0
- metadata +164 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e96a7339ec279990aaa392943d1311b880fb8b50
|
4
|
+
data.tar.gz: ce5531d38a298fc3dc30bbb6bf5e9f60be34e35a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ccae5e39f1dcfa3f32fddaf180ee0124e3d76313beb0983d0fa1f668fa6e3ebfd118620b8bc18d783804bce022dc119ae2e08c1afb4b7dc996c05c9970373b3
|
7
|
+
data.tar.gz: 69eeb23cf91f14575f93a1c2f82a53fdd1e719fb39b132a3baf5e0baa74132987257fa1456c53cbb5623a0b0a715eb074f027e784ed68fc3b05e9cef7d88d63d
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
fluffle (0.0.1)
|
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
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Everlane Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# fluffle
|
2
|
+
|
3
|
+
An implementation of [JSON-RPC][] over RabbitMQ through the [Bunny][] library. Provides both a client and server.
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
> A group of baby bunnies is called a [fluffle][].
|
8
|
+
|
9
|
+
[Bunny]: https://github.com/ruby-amqp/bunny
|
10
|
+
[fluffle]: http://imgur.com/6eABy1v
|
11
|
+
[JSON-RPC]: http://www.jsonrpc.org/specification
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
- Client: Thread-safe blocking client (via [concurrent-ruby][])
|
16
|
+
- Server: One-thread-per-queue implementation (multi-threaded coming soon)
|
17
|
+
- Server: Easy-to-use built-in handlers and straightforward API for building custom handlers
|
18
|
+
|
19
|
+
[concurrent-ruby]: https://github.com/ruby-concurrency/concurrent-ruby
|
20
|
+
|
21
|
+
## Examples
|
22
|
+
|
23
|
+
See the [`examples`](examples/) directory.
|
24
|
+
|
25
|
+
The server provides a few options for handling RPC requests:
|
26
|
+
|
27
|
+
- Dispatcher pattern: `dispatcher.handle('upcase') { |str| str.upcase }`
|
28
|
+
- Delegator pattern: delegate will receive the `#upcase` message with a single argument (the string)
|
29
|
+
- Custom: any handler needs to implement the API described in `Fluffle::Handlers::Base`
|
30
|
+
|
31
|
+
## License
|
32
|
+
|
33
|
+
Released under the MIT license, see [LICENSE](LICENSE) for details.
|
data/Rakefile
ADDED
data/examples/client.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
lib = File.join File.dirname(__FILE__), '..', 'lib'
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'fluffle'
|
5
|
+
|
6
|
+
server = Fluffle::Client.new url: 'amqp://localhost'
|
7
|
+
|
8
|
+
timings = 10.times.map do
|
9
|
+
t0 = Time.now
|
10
|
+
server.call('foo').inspect
|
11
|
+
Time.now - t0
|
12
|
+
end
|
13
|
+
|
14
|
+
puts timings
|
data/examples/server.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
lib = File.join File.dirname(__FILE__), '..', 'lib'
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'fluffle'
|
5
|
+
|
6
|
+
server = Fluffle::Server.new url: 'amqp://localhost'
|
7
|
+
|
8
|
+
server.drain 'default' do |dispatcher|
|
9
|
+
dispatcher.handle('foo') { 'bar' }
|
10
|
+
end
|
11
|
+
|
12
|
+
server.start
|
data/fluffle.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
lib = File.join File.dirname(__FILE__), 'lib'
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'fluffle/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'fluffle'
|
8
|
+
s.version = Fluffle::VERSION
|
9
|
+
s.authors = ['Dirk Gadsden']
|
10
|
+
s.email = ['dirk@esherido.com']
|
11
|
+
s.summary = 'Client and server implementations for JSON-RPC over RabbitMQ'
|
12
|
+
s.homepage = 'https://github.com/Everlane/fluffle'
|
13
|
+
s.license = 'MIT'
|
14
|
+
|
15
|
+
s.required_ruby_version = '>= 1.9.3'
|
16
|
+
|
17
|
+
s.add_dependency 'bunny', '~> 2.5.0'
|
18
|
+
s.add_dependency 'concurrent-ruby', '~> 1.0.2'
|
19
|
+
s.add_dependency 'oj', '~> 2.17.1'
|
20
|
+
s.add_dependency 'uuidtools', '~> 2.1.5'
|
21
|
+
|
22
|
+
s.add_development_dependency 'pry', '~> 0.10.1'
|
23
|
+
s.add_development_dependency 'rake', '~> 11.2.2'
|
24
|
+
s.add_development_dependency 'rspec', '~> 3.4.0'
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
28
|
+
end
|
data/fluffle.jpg
ADDED
Binary file
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
require 'oj'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'uuidtools'
|
5
|
+
|
6
|
+
require 'fluffle/connectable'
|
7
|
+
|
8
|
+
module Fluffle
|
9
|
+
class Client
|
10
|
+
include Connectable
|
11
|
+
|
12
|
+
def initialize(url:)
|
13
|
+
self.connect url
|
14
|
+
|
15
|
+
@uuid = UUIDTools::UUID.timestamp_create.to_s
|
16
|
+
@channel = @connection.create_channel
|
17
|
+
@exchange = @channel.default_exchange
|
18
|
+
@reply_queue = @channel.queue Fluffle.response_queue_name(@uuid), exclusive: true
|
19
|
+
|
20
|
+
# Used for generating unique message IDs
|
21
|
+
@prng = Random.new
|
22
|
+
|
23
|
+
@pending_responses = Concurrent::Map.new
|
24
|
+
|
25
|
+
self.subscribe
|
26
|
+
end
|
27
|
+
|
28
|
+
def subscribe
|
29
|
+
@reply_queue.subscribe do |delivery_info, properties, payload|
|
30
|
+
self.handle_resposne delivery_info: delivery_info,
|
31
|
+
properties: properties,
|
32
|
+
payload: payload
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle_resposne(delivery_info:, properties:, payload:)
|
37
|
+
payload = Oj.load payload
|
38
|
+
|
39
|
+
ivar = @pending_responses.delete payload['id']
|
40
|
+
ivar.set payload
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(method, params = [], queue: 'default')
|
44
|
+
id = random_bytes_as_hex 8
|
45
|
+
|
46
|
+
payload = {
|
47
|
+
'jsonrpc' => '2.0',
|
48
|
+
'id' => id,
|
49
|
+
'method' => method,
|
50
|
+
'params' => params
|
51
|
+
}
|
52
|
+
|
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
|
61
|
+
|
62
|
+
if response['result']
|
63
|
+
response['result']
|
64
|
+
else
|
65
|
+
raise # TODO: Raise known error subclass to be caught by client code
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def random_bytes_as_hex(bytes)
|
72
|
+
# Adapted from `SecureRandom.hex`
|
73
|
+
@prng.bytes(bytes).unpack('H*')[0]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Fluffle
|
2
|
+
module Connectable
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
attr_reader :connection
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def connect(*args)
|
10
|
+
self.stop if self.connected?
|
11
|
+
|
12
|
+
@connection = Bunny.new *args
|
13
|
+
@connection.start
|
14
|
+
end
|
15
|
+
|
16
|
+
def connected?
|
17
|
+
@connection&.connected?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Fluffle
|
2
|
+
module Errors
|
3
|
+
class BaseError < StandardError
|
4
|
+
def to_response
|
5
|
+
{
|
6
|
+
'code' => self.code,
|
7
|
+
'message' => self.message,
|
8
|
+
'data' => self.data
|
9
|
+
}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Raise this within your own code to get an error that will be faithfully
|
14
|
+
# translated into the code, message, and data member fields of the
|
15
|
+
# spec's `Error` response object
|
16
|
+
class CustomError < BaseError
|
17
|
+
attr_accessor :data
|
18
|
+
|
19
|
+
def initialize(code: 0, message:, data: nil)
|
20
|
+
@code = code
|
21
|
+
@data = data
|
22
|
+
|
23
|
+
super message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Superclass of all errors that may be raised within the server
|
28
|
+
class ServerError < BaseError
|
29
|
+
# Longer-form description that may be present in the `data` field of
|
30
|
+
# the `Error` response object
|
31
|
+
attr_reader :description
|
32
|
+
|
33
|
+
def data
|
34
|
+
{ 'description' => @description }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class InvalidRequestError < ServerError
|
39
|
+
def initialize(description)
|
40
|
+
@description = description
|
41
|
+
|
42
|
+
super 'Invalid Request'
|
43
|
+
end
|
44
|
+
|
45
|
+
def code
|
46
|
+
-32600
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Fluffle
|
2
|
+
module Handlers
|
3
|
+
class Dispatcher < Base
|
4
|
+
def initialize
|
5
|
+
@routes = []
|
6
|
+
|
7
|
+
yield self if block_given?
|
8
|
+
end
|
9
|
+
|
10
|
+
# pattern - Right now just a String that 1-to-1 matches the `method`
|
11
|
+
# block - Block to call with the `params`
|
12
|
+
def handle(pattern, &block)
|
13
|
+
@routes << [pattern, block]
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(method:, params:, **_)
|
17
|
+
@routes.each do |(pattern, block)|
|
18
|
+
next if pattern != method
|
19
|
+
|
20
|
+
return block.call(*params)
|
21
|
+
end
|
22
|
+
|
23
|
+
raise NoMethodError, "Undefined method '#{method}'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Fluffle
|
2
|
+
class Server
|
3
|
+
class << self
|
4
|
+
attr_accessor :default_server
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :connection, :handlers
|
8
|
+
|
9
|
+
def initialize(url: nil)
|
10
|
+
self.connect(url) if url
|
11
|
+
|
12
|
+
@handlers = {}
|
13
|
+
@queues = {}
|
14
|
+
|
15
|
+
self.class.default_server ||= self
|
16
|
+
end
|
17
|
+
|
18
|
+
def connect(*args)
|
19
|
+
self.stop if self.connected?
|
20
|
+
|
21
|
+
@connection = Bunny.new *args
|
22
|
+
@connection.start
|
23
|
+
end
|
24
|
+
|
25
|
+
def connected?
|
26
|
+
@connection&.connected?
|
27
|
+
end
|
28
|
+
|
29
|
+
def drain(queue: 'default', handler: nil, &block)
|
30
|
+
if handler && block
|
31
|
+
raise ArgumentError, 'Cannot provide both handler: and block'
|
32
|
+
end
|
33
|
+
|
34
|
+
handler = Fluffle::Handlers::Dispatcher.new(&block) if block
|
35
|
+
|
36
|
+
@handlers[queue_name.to_s] = handler
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
@channel = @connection.create_channel
|
41
|
+
@exchange = @channel.default_exchange
|
42
|
+
|
43
|
+
@handlers.each do |name, handler|
|
44
|
+
qualified_name = Fluffle.response_queue_name name
|
45
|
+
queue = @channel.queue qualified_name
|
46
|
+
|
47
|
+
queue.subscribe do |delivery_info, properties, payload|
|
48
|
+
self.handle_request queue_name: name,
|
49
|
+
handler: handler,
|
50
|
+
delivery_info: delivery_info,
|
51
|
+
properties: properties,
|
52
|
+
payload: payload
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@channel.work_pool.join
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_request(queue_name:, handler:, delivery_info:, properties:, payload:)
|
60
|
+
id = nil
|
61
|
+
reply_to = properties[:reply_to]
|
62
|
+
|
63
|
+
begin
|
64
|
+
id, method, params = self.decode payload
|
65
|
+
|
66
|
+
validate_request method: method
|
67
|
+
|
68
|
+
result = handler.call id: id,
|
69
|
+
method: method,
|
70
|
+
params: params,
|
71
|
+
meta: {
|
72
|
+
reply_to: reply_to
|
73
|
+
}
|
74
|
+
rescue => err
|
75
|
+
error = self.build_error_response err
|
76
|
+
end
|
77
|
+
|
78
|
+
response = { 'jsonrpc' => '2.0', 'id' => id }
|
79
|
+
|
80
|
+
if error
|
81
|
+
response['error'] = error
|
82
|
+
else
|
83
|
+
response['result'] = result
|
84
|
+
end
|
85
|
+
|
86
|
+
@exchange.publish Oj.dump(response), routing_key: reply_to,
|
87
|
+
correlation_id: id
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def decode(payload)
|
93
|
+
payload = Oj.load payload
|
94
|
+
|
95
|
+
id = payload['id']
|
96
|
+
method = payload['method']
|
97
|
+
params = payload['params']
|
98
|
+
|
99
|
+
[id, method, params]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Raises if elements of the request do not comply with the spec
|
103
|
+
def validate_request(request)
|
104
|
+
raise Errors::InvalidRequestError.new("Missing `method' Request object member") unless request[:method]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Convert a Ruby error into a hash complying with the JSON-RPC spec
|
108
|
+
# for `Error` response objects
|
109
|
+
def build_error_response(err)
|
110
|
+
if err.is_a? Errors::BaseError
|
111
|
+
err.to_response
|
112
|
+
|
113
|
+
elsif err.is_a? NoMethodError
|
114
|
+
{ 'code' => -32601, 'message' => 'Method not found' }
|
115
|
+
|
116
|
+
else
|
117
|
+
response = {
|
118
|
+
'code' => 0,
|
119
|
+
'message' => err.message
|
120
|
+
}
|
121
|
+
|
122
|
+
response['data'] = err.data if err.respond_to? :data
|
123
|
+
|
124
|
+
response
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/fluffle.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
require 'fluffle/version'
|
4
|
+
require 'fluffle/client'
|
5
|
+
require 'fluffle/errors'
|
6
|
+
require 'fluffle/handlers/base'
|
7
|
+
require 'fluffle/handlers/delegator'
|
8
|
+
require 'fluffle/handlers/dispatcher'
|
9
|
+
require 'fluffle/server'
|
10
|
+
|
11
|
+
module Fluffle
|
12
|
+
# Expand a short name into a fully-qualified one
|
13
|
+
def self.request_queue_name(name)
|
14
|
+
"fluffle.requests.#{name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.response_queue_name(name)
|
18
|
+
"fluffle.responses.#{name}"
|
19
|
+
end
|
20
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Fluffle::Server do
|
4
|
+
describe '#handle_request' do
|
5
|
+
before do
|
6
|
+
@exchange_spy = exchange_spy = spy 'Exchange'
|
7
|
+
|
8
|
+
subject.instance_eval do
|
9
|
+
@exchange = exchange_spy
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def id
|
14
|
+
@id = '123' unless instance_variable_defined? :@id
|
15
|
+
@id
|
16
|
+
end
|
17
|
+
|
18
|
+
def method
|
19
|
+
@method = 'do_something' unless instance_variable_defined? :@method
|
20
|
+
@method
|
21
|
+
end
|
22
|
+
|
23
|
+
def params
|
24
|
+
@params = [] unless instance_variable_defined? :@params
|
25
|
+
@params
|
26
|
+
end
|
27
|
+
|
28
|
+
def reply_to
|
29
|
+
@reply_to = 'fakeclient' unless instance_variable_defined? :@reply_to
|
30
|
+
@reply_to
|
31
|
+
end
|
32
|
+
|
33
|
+
def make_request(payload: {}, handler:)
|
34
|
+
payload['jsonrpc'] = '2.0' unless payload.key? 'jsonrpc'
|
35
|
+
payload['id'] = id unless payload.key? 'id'
|
36
|
+
payload['method'] = method unless payload.key? 'method'
|
37
|
+
payload['params'] = params unless payload.key? 'params'
|
38
|
+
|
39
|
+
subject.handle_request queue_name: 'fakequeue',
|
40
|
+
handler: handler,
|
41
|
+
delivery_info: double('DeliveryInfo'),
|
42
|
+
properties: { reply_to: reply_to },
|
43
|
+
payload: Oj.dump(payload)
|
44
|
+
end
|
45
|
+
|
46
|
+
def expect_response(payload:, routing_key: nil, correlation_id: nil)
|
47
|
+
routing_key ||= reply_to
|
48
|
+
correlation_id ||= id
|
49
|
+
|
50
|
+
payload['jsonrpc'] ||= '2.0'
|
51
|
+
payload['id'] ||= id
|
52
|
+
|
53
|
+
expect(@exchange_spy).to have_received(:publish) do |payload_json, opts|
|
54
|
+
expect(Oj.load(payload_json)).to eq payload
|
55
|
+
|
56
|
+
expect(opts).to eq routing_key: routing_key,
|
57
|
+
correlation_id: correlation_id
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'responds with the result for a normal request' do
|
62
|
+
@params = ['bar']
|
63
|
+
|
64
|
+
result = 'baz'
|
65
|
+
|
66
|
+
handler = double 'Handler'
|
67
|
+
expect(handler).to receive(:call) do |args|
|
68
|
+
expect(args).to eq({
|
69
|
+
id: id,
|
70
|
+
method: method,
|
71
|
+
params: params,
|
72
|
+
meta: { reply_to: reply_to }
|
73
|
+
})
|
74
|
+
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
make_request handler: handler
|
79
|
+
|
80
|
+
expect_response payload: { 'result' => result }
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'responds with the appropriate code and message when method not found' do
|
84
|
+
@method = 'notfound'
|
85
|
+
|
86
|
+
handler = double 'Handler'
|
87
|
+
expect(handler).to receive(:call).and_raise(NoMethodError.new("undefined method `#{method}'"))
|
88
|
+
|
89
|
+
make_request handler: handler
|
90
|
+
|
91
|
+
expect_response payload: {
|
92
|
+
'error' => {
|
93
|
+
'code' => -32601,
|
94
|
+
'message' => 'Method not found'
|
95
|
+
}
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
it "responds with the appropriate code and message when `method' isn't supplied" do
|
100
|
+
@method = nil
|
101
|
+
|
102
|
+
handler = double 'Handler'
|
103
|
+
expect(handler).not_to receive(:call)
|
104
|
+
|
105
|
+
make_request handler: handler
|
106
|
+
|
107
|
+
expect_response payload: {
|
108
|
+
'error' => {
|
109
|
+
'code' => -32600,
|
110
|
+
'message' => 'Invalid Request',
|
111
|
+
'data' => { 'description' => "Missing `method' Request object member" }
|
112
|
+
}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluffle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dirk Gadsden
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bunny
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.5.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.5.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: concurrent-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: oj
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.17.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.17.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: uuidtools
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.1.5
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.1.5
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.10.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.10.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 11.2.2
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 11.2.2
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.4.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.4.0
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- dirk@esherido.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- Gemfile
|
121
|
+
- Gemfile.lock
|
122
|
+
- LICENSE
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- examples/client.rb
|
126
|
+
- examples/server.rb
|
127
|
+
- fluffle.gemspec
|
128
|
+
- fluffle.jpg
|
129
|
+
- lib/fluffle.rb
|
130
|
+
- lib/fluffle/client.rb
|
131
|
+
- lib/fluffle/connectable.rb
|
132
|
+
- lib/fluffle/errors.rb
|
133
|
+
- lib/fluffle/handlers/base.rb
|
134
|
+
- lib/fluffle/handlers/delegator.rb
|
135
|
+
- lib/fluffle/handlers/dispatcher.rb
|
136
|
+
- lib/fluffle/server.rb
|
137
|
+
- lib/fluffle/version.rb
|
138
|
+
- spec/server_spec.rb
|
139
|
+
- spec/spec_helper.rb
|
140
|
+
homepage: https://github.com/Everlane/fluffle
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.9.3
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 2.5.1
|
161
|
+
signing_key:
|
162
|
+
specification_version: 4
|
163
|
+
summary: Client and server implementations for JSON-RPC over RabbitMQ
|
164
|
+
test_files: []
|