jsonrpc-client 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.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ coverage
3
+ .rvmrc
4
+ Gemfile.lock
5
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
data/LICENSE.txt ADDED
@@ -0,0 +1,17 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2
+ of this software and associated documentation files (the "Software"), to deal
3
+ in the Software without restriction, including without limitation the rights
4
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5
+ copies of the Software, and to permit persons to whom the Software is
6
+ furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in
9
+ all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
17
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # JSONRPC::Client
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'jsonrpc-client'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install jsonrpc-client
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+ require 'bundler/gem_tasks'
4
+
5
+ desc "Run all specs"
6
+ RSpec::Core::RakeTask.new(:rspec) do |spec|
7
+ spec.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
11
+ spec.pattern = 'spec/**/*_spec.rb'
12
+ spec.rcov = true
13
+ end
14
+
15
+ task :default => :rspec
16
+
17
+ begin
18
+ require 'rdoc/task'
19
+ rescue LoadError
20
+ require 'rake/rdoctask'
21
+ end
22
+
23
+ Rake::RDocTask.new do |rdoc|
24
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
25
+
26
+ rdoc.rdoc_dir = 'rdoc'
27
+ rdoc.title = "jimson #{version}"
28
+ rdoc.rdoc_files.include('README*')
29
+ rdoc.rdoc_files.include('lib/**/*.rb')
30
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jsonrpc/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "jsonrpc-client"
8
+ gem.version = JSONRPC::VERSION
9
+ gem.authors = ["Pavel Forkert"]
10
+ gem.email = ["fxposter@gmail.com"]
11
+ gem.description = %q{Simple JSON-RPC 2.0 client implementation}
12
+ gem.summary = %q{JSON-RPC 2.0 client}
13
+ gem.homepage = "https://github.com/fxposter/jsonrpc-client"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'faraday'
21
+ gem.add_dependency 'multi_json', '>= 1.1.0'
22
+ gem.add_development_dependency 'rspec'
23
+ gem.add_development_dependency 'rake'
24
+ end
@@ -0,0 +1 @@
1
+ require 'jsonrpc/client'
@@ -0,0 +1,171 @@
1
+ require 'multi_json'
2
+ require 'faraday'
3
+ require 'jsonrpc/request'
4
+ require 'jsonrpc/response'
5
+ require 'jsonrpc/error'
6
+ require 'jsonrpc/version'
7
+
8
+ module JSONRPC
9
+ class Base < BasicObject
10
+ JSON_RPC_VERSION = '2.0'
11
+
12
+ def self.make_id
13
+ rand(10**12)
14
+ end
15
+
16
+ def initialize(url, opts = {})
17
+ @url = ::URI.parse(url).to_s
18
+ @opts = opts
19
+ @opts[:content_type] ||= 'application/json'
20
+ end
21
+
22
+ def to_s
23
+ inspect
24
+ end
25
+
26
+ def inspect
27
+ "#<#{self.class.name}:0x00%08x>" % (__id__ * 2)
28
+ end
29
+
30
+ def class
31
+ (class << self; self end).superclass
32
+ end
33
+
34
+ private
35
+ def raise(*args)
36
+ ::Kernel.raise(*args)
37
+ end
38
+ end
39
+
40
+ class BatchClient < Base
41
+ attr_reader :batch
42
+
43
+ def initialize(url, opts = {})
44
+ super
45
+ @batch = []
46
+ @alive = true
47
+ yield self
48
+ send_batch
49
+ @alive = false
50
+ end
51
+
52
+ def method_missing(sym, *args, &block)
53
+ if @alive
54
+ request = ::JSONRPC::Request.new(sym.to_s, args)
55
+ push_batch_request(request)
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ private
62
+ def send_batch_request(batch)
63
+ post_data = ::MultiJson.encode(batch)
64
+ resp = ::Faraday.post(@url, post_data, @opts)
65
+ if resp.nil? || resp.body.nil? || resp.body.empty?
66
+ raise ::JSONRPC::Error::InvalidResponse.new
67
+ end
68
+
69
+ resp.body
70
+ end
71
+
72
+ def process_batch_response(responses)
73
+ responses.each do |resp|
74
+ saved_response = @batch.map { |r| r[1] }.select { |r| r.id == resp['id'] }.first
75
+ raise ::JSONRPC::Error::InvalidResponse.new if saved_response.nil?
76
+ saved_response.populate!(resp)
77
+ end
78
+ end
79
+
80
+ def push_batch_request(request)
81
+ request.id = ::JSONRPC::Base.make_id
82
+ response = ::JSONRPC::Response.new(request.id)
83
+ @batch << [request, response]
84
+ response
85
+ end
86
+
87
+ def send_batch
88
+ batch = @batch.map(&:first) # get the requests
89
+ response = send_batch_request(batch)
90
+
91
+ begin
92
+ responses = ::MultiJson.decode(response)
93
+ rescue
94
+ raise ::JSONRPC::Error::InvalidJSON.new(json)
95
+ end
96
+
97
+ process_batch_response(responses)
98
+ @batch = []
99
+ end
100
+ end
101
+
102
+ class Client < Base
103
+ def method_missing(method, *args, &block)
104
+ invoke(method, args)
105
+ end
106
+
107
+ def invoke(method, args)
108
+ resp = send_single_request(method.to_s, args)
109
+
110
+ begin
111
+ data = ::MultiJson.decode(resp)
112
+ rescue
113
+ raise ::JSONRPC::Error::InvalidJSON.new(resp)
114
+ end
115
+
116
+ process_single_response(data)
117
+ rescue => e
118
+ e.extend(::JSONRPC::Error)
119
+ raise
120
+ end
121
+
122
+ private
123
+ def send_single_request(method, args)
124
+ post_data = ::MultiJson.encode({
125
+ 'jsonrpc' => JSON_RPC_VERSION,
126
+ 'method' => method,
127
+ 'params' => args,
128
+ 'id' => ::JSONRPC::Base.make_id
129
+ })
130
+ resp = ::Faraday.post(@url, post_data, @opts)
131
+ if resp.nil? || resp.body.nil? || resp.body.empty?
132
+ raise ::JSONRPC::Error::InvalidResponse.new
133
+ end
134
+
135
+ resp.body
136
+ end
137
+
138
+ def process_single_response(data)
139
+ raise ::JSONRPC::Error::InvalidResponse.new unless valid_response?(data)
140
+
141
+ if data['error']
142
+ code = data['error']['code']
143
+ msg = data['error']['message']
144
+ raise ::JSONRPC::Error::ServerError.new(code, msg)
145
+ end
146
+
147
+ data['result']
148
+ end
149
+
150
+ def valid_response?(data)
151
+ return false if !data.is_a?(::Hash)
152
+ return false if data['jsonrpc'] != JSON_RPC_VERSION
153
+ return false if !data.has_key?('id')
154
+ return false if data.has_key?('error') && data.has_key?('result')
155
+
156
+ if data.has_key?('error')
157
+ if !data['error'].is_a?(Hash) || !data['error'].has_key?('code') || !data['error'].has_key?('message')
158
+ return false
159
+ end
160
+
161
+ if !data['error']['code'].is_a?(Fixnum) || !data['error']['message'].is_a?(String)
162
+ return false
163
+ end
164
+ end
165
+
166
+ true
167
+ rescue
168
+ false
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,21 @@
1
+ module JSONRPC
2
+ module Error
3
+ class InvalidResponse < StandardError
4
+ def initialize()
5
+ super('Invalid or empty response from server.')
6
+ end
7
+ end
8
+
9
+ class InvalidJSON < StandardError
10
+ def initialize(json)
11
+ super("Couldn't parse JSON string received from server:\n#{json}")
12
+ end
13
+ end
14
+
15
+ class ServerError < StandardError
16
+ def initialize(code, message)
17
+ super("Server error #{code}: #{message}")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module JSONRPC
2
+ class Request
3
+
4
+ attr_accessor :method, :params, :id
5
+ def initialize(method, params, id = nil)
6
+ @method = method
7
+ @params = params
8
+ @id = id
9
+ end
10
+
11
+ def to_h
12
+ h = {
13
+ 'jsonrpc' => '2.0',
14
+ 'method' => @method
15
+ }
16
+ h.merge!('params' => @params) if !!@params && !params.empty?
17
+ h.merge!('id' => id)
18
+ end
19
+
20
+ def to_json(*a)
21
+ MultiJson.encode(self.to_h)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module JSONRPC
2
+ class Response
3
+ attr_accessor :result, :error, :id
4
+
5
+ def initialize(id)
6
+ @id = id
7
+ end
8
+
9
+ def to_h
10
+ h = {'jsonrpc' => '2.0'}
11
+ h.merge!('result' => @result) if !!@result
12
+ h.merge!('error' => @error) if !!@error
13
+ h.merge!('id' => @id)
14
+ end
15
+
16
+ def is_error?
17
+ !!@error
18
+ end
19
+
20
+ def succeeded?
21
+ !!@result
22
+ end
23
+
24
+ def populate!(data)
25
+ @error = data['error'] if !!data['error']
26
+ @result = data['result'] if !!data['result']
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module JSONRPC
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ module JSONRPC
4
+ describe Client do
5
+ BOILERPLATE = {'jsonrpc' => '2.0', 'id' => 1}
6
+
7
+ before(:each) do
8
+ @resp_mock = mock('http_response')
9
+ Base.stub!(:make_id).and_return(1)
10
+ end
11
+
12
+ after(:each) do
13
+ end
14
+
15
+ describe "hidden methods" do
16
+ it "should reveal inspect" do
17
+ Client.new(SPEC_URL).inspect.should match /JSONRPC::Client/
18
+ end
19
+
20
+ it "should reveal to_s" do
21
+ Client.new(SPEC_URL).to_s.should match /JSONRPC::Client/
22
+ end
23
+ end
24
+
25
+ describe "#invoke" do
26
+ before(:each) do
27
+ expected = MultiJson.encode({
28
+ 'jsonrpc' => '2.0',
29
+ 'method' => 'foo',
30
+ 'params' => [1,2,3],
31
+ 'id' => 1
32
+ })
33
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
34
+ Faraday.should_receive(:post).with(SPEC_URL, expected, {:content_type => 'application/json'}).and_return(@resp_mock)
35
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
36
+ @client = Client.new(SPEC_URL)
37
+ end
38
+
39
+ context "when using an array of args" do
40
+ it "sends a request with the correct method and args" do
41
+ @client.invoke('foo', [1, 2, 3]).should == 42
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "sending a single request" do
47
+ context "when using positional parameters" do
48
+ before(:each) do
49
+ @expected = MultiJson.encode({
50
+ 'jsonrpc' => '2.0',
51
+ 'method' => 'foo',
52
+ 'params' => [1,2,3],
53
+ 'id' => 1
54
+ })
55
+ end
56
+ it "sends a valid JSON-RPC request and returns the result" do
57
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
58
+ Faraday.should_receive(:post).with(SPEC_URL, @expected, {:content_type => 'application/json'}).and_return(@resp_mock)
59
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
60
+ client = Client.new(SPEC_URL)
61
+ client.foo(1,2,3).should == 42
62
+ end
63
+
64
+ it "sends a valid JSON-RPC request with custom options" do
65
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
66
+ Faraday.should_receive(:post).with(SPEC_URL, @expected, {:content_type => 'application/json', :timeout => 10000}).and_return(@resp_mock)
67
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
68
+ client = Client.new(SPEC_URL, :timeout => 10000)
69
+ client.foo(1,2,3).should == 42
70
+ end
71
+
72
+ it "sends a valid JSON-RPC request with custom content_type" do
73
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
74
+ Faraday.should_receive(:post).with(SPEC_URL, @expected, {:content_type => 'application/json-rpc', :timeout => 10000}).and_return(@resp_mock)
75
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
76
+ client = Client.new(SPEC_URL, :timeout => 10000, :content_type => 'application/json-rpc')
77
+ client.foo(1,2,3).should == 42
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "sending a batch request" do
83
+ it "sends a valid JSON-RPC batch request and puts the results in the response objects" do
84
+ batch = MultiJson.encode([
85
+ {"jsonrpc" => "2.0", "method" => "sum", "params" => [1,2,4], "id" => "1"},
86
+ {"jsonrpc" => "2.0", "method" => "subtract", "params" => [42,23], "id" => "2"},
87
+ {"jsonrpc" => "2.0", "method" => "foo_get", "params" => [{"name" => "myself"}], "id" => "5"},
88
+ {"jsonrpc" => "2.0", "method" => "get_data", "id" => "9"}
89
+ ])
90
+
91
+ response = MultiJson.encode([
92
+ {"jsonrpc" => "2.0", "result" => 7, "id" => "1"},
93
+ {"jsonrpc" => "2.0", "result" => 19, "id" => "2"},
94
+ {"jsonrpc" => "2.0", "error" => {"code" => -32601, "message" => "Method not found."}, "id" => "5"},
95
+ {"jsonrpc" => "2.0", "result" => ["hello", 5], "id" => "9"}
96
+ ])
97
+
98
+ Base.stub!(:make_id).and_return('1', '2', '5', '9')
99
+ Faraday.should_receive(:post).with(SPEC_URL, batch, {:content_type => 'application/json'}).and_return(@resp_mock)
100
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
101
+ client = Client.new(SPEC_URL)
102
+
103
+ sum = subtract = foo = data = nil
104
+ client = BatchClient.new(SPEC_URL) do |batch|
105
+ sum = batch.sum(1,2,4)
106
+ subtract = batch.subtract(42,23)
107
+ foo = batch.foo_get('name' => 'myself')
108
+ data = batch.get_data
109
+ end
110
+
111
+ sum.succeeded?.should be_true
112
+ sum.is_error?.should be_false
113
+ sum.result.should == 7
114
+
115
+ subtract.result.should == 19
116
+
117
+ foo.is_error?.should be_true
118
+ foo.succeeded?.should be_false
119
+ foo.error['code'].should == -32601
120
+
121
+ data.result.should == ['hello', 5]
122
+
123
+
124
+ expect { client.sum(1, 2) }.to raise_error(NoMethodError)
125
+ end
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ require 'jsonrpc-client'
3
+ require 'multi_json'
4
+
5
+ SPEC_URL = 'http://localhost:8999'
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonrpc-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pavel Forkert
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.1.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.1.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Simple JSON-RPC 2.0 client implementation
79
+ email:
80
+ - fxposter@gmail.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - LICENSE.txt
88
+ - README.md
89
+ - Rakefile
90
+ - jsonrpc-client.gemspec
91
+ - lib/jsonrpc-client.rb
92
+ - lib/jsonrpc/client.rb
93
+ - lib/jsonrpc/error.rb
94
+ - lib/jsonrpc/request.rb
95
+ - lib/jsonrpc/response.rb
96
+ - lib/jsonrpc/version.rb
97
+ - spec/client_spec.rb
98
+ - spec/spec_helper.rb
99
+ homepage: https://github.com/fxposter/jsonrpc-client
100
+ licenses: []
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ segments:
112
+ - 0
113
+ hash: 1153313721096096844
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ segments:
121
+ - 0
122
+ hash: 1153313721096096844
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: JSON-RPC 2.0 client
129
+ test_files:
130
+ - spec/client_spec.rb
131
+ - spec/spec_helper.rb