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 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
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ pkg
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
+ ![](fluffle.jpg)
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
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ # No-op to make Travis CI happy
4
+ task 'default'
@@ -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
@@ -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,9 @@
1
+ module Fluffle
2
+ module Handlers
3
+ class Base
4
+ def call(method:, params:, id:, meta:)
5
+ raise RuntimeError, '#call is not implemented on abstract Base handler class'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Fluffle
2
+ module Handlers
3
+ class Delegator < Base
4
+ def initialize(delegated_object)
5
+ @delegated_object = delegated_object
6
+ end
7
+
8
+ def call(method:, params:, **_)
9
+ @delegated_object.send method.to_sym, *params
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,3 @@
1
+ module Fluffle
2
+ VERSION = '0.0.1'
3
+ 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
@@ -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
@@ -0,0 +1,3 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'fluffle'
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: []