jsonrpc-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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