jimson 0.10.0 → 0.14.0
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 +5 -5
- data/{CHANGELOG.rdoc → CHANGELOG.md} +42 -14
- data/README.md +31 -16
- data/Rakefile +8 -8
- data/lib/jimson/client.rb +36 -21
- data/lib/jimson/client/error.rb +2 -2
- data/lib/jimson/router.rb +2 -2
- data/lib/jimson/router/map.rb +9 -7
- data/lib/jimson/server.rb +1 -1
- data/lib/jimson/version.rb +3 -0
- data/spec/client_spec.rb +97 -20
- data/spec/router_spec.rb +31 -9
- data/spec/server_spec.rb +2 -2
- metadata +110 -40
- data/VERSION +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 796c16d539f0164203cf72d44e36f82ceb24c0fc110f1a6a31319d5fbb0dd393
|
4
|
+
data.tar.gz: 443273c640503427548b431d8da0e619d3bc5a65c57bb6be5cb75ffec0cde77f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96d7c724b0f3e0e764c09bbe57a4b4fc33caa2784835d0caeafdd8efe6532006be80754a970e2bab9558a3b17511602bf830a38863b53733b62e0c26834b17bc
|
7
|
+
data.tar.gz: 0ff8ecb09c52137decaaf34132974fcc3286e37a28a7f1a256ec5a4368df72fe55c0f51b9ea87602065d46716b5326f6f97d3e4e148937f70353b59d3342f1c6
|
@@ -1,35 +1,63 @@
|
|
1
|
-
|
1
|
+
# 0.13.0 / 2021-05-10
|
2
|
+
|
3
|
+
* Major enhancements
|
4
|
+
|
5
|
+
* Added option to use strings as id instead of an int
|
6
|
+
|
7
|
+
# 0.12.0 / 2021-05-05
|
8
|
+
|
9
|
+
* Major enhancements
|
10
|
+
|
11
|
+
* Add ability to specify custom content type
|
12
|
+
* Add support for named parameters
|
13
|
+
* Add Router :ns_sep option for custom namespace
|
14
|
+
* made it possible to overwrite advanced RestClient values, like ssl_ca_file
|
15
|
+
|
16
|
+
* Minor enhancements
|
17
|
+
|
18
|
+
* Unified Bignum and Fixnum to Integer
|
19
|
+
* Add data returned to invalid response exception
|
20
|
+
* Fix json variable name on invalid json response
|
21
|
+
|
22
|
+
# 0.11.0 / 2016-03-13
|
23
|
+
|
24
|
+
* Minor enhancements
|
25
|
+
|
26
|
+
* Update dependency versions for Ruby 2.*
|
27
|
+
* Remove Gemfile.lock
|
28
|
+
|
29
|
+
# 0.10.0 / 2013-06-28
|
2
30
|
|
3
31
|
* Minor enhancements
|
4
32
|
|
5
33
|
* Update dependency versions
|
6
34
|
|
7
|
-
|
35
|
+
# 0.9.1 / 2012-09-18
|
8
36
|
|
9
37
|
* Bug fixes
|
10
38
|
|
11
39
|
* Allow opts to be passed to Server.with_routes
|
12
40
|
|
13
|
-
|
41
|
+
# 0.9.0 / 2012-08-22
|
14
42
|
|
15
43
|
* Minor enhancements
|
16
44
|
|
17
45
|
* Add show_errors option to server, which will cause application errors to include the error name and the first line of the backtrace
|
18
46
|
|
19
|
-
|
47
|
+
# 0.8.0 / 2012-08-17
|
20
48
|
|
21
49
|
* Major enhancements
|
22
50
|
|
23
51
|
* Add namespaced method calls to client (e.g. 'client[:foo].sum(1,2,3) # calls foo.sum')
|
24
52
|
* Add Server.with_routes to quickly created a routed server
|
25
53
|
|
26
|
-
|
54
|
+
# 0.7.1 / 2012-08-16
|
27
55
|
|
28
56
|
* Bug fixes
|
29
57
|
|
30
58
|
* Fix handling of array params in client, which were erroneously being flattened
|
31
59
|
|
32
|
-
|
60
|
+
# 0.7.0 / 2012-04-13
|
33
61
|
|
34
62
|
* Major enhancements
|
35
63
|
|
@@ -39,13 +67,13 @@
|
|
39
67
|
|
40
68
|
* Fix deprecation warning about RDoc task in Rakefile
|
41
69
|
|
42
|
-
|
70
|
+
# 0.6.0 / 2012-03-14
|
43
71
|
|
44
72
|
* Minor enhancements
|
45
73
|
|
46
74
|
* Add ability to pass options to Rack and RestClient
|
47
75
|
|
48
|
-
|
76
|
+
# 0.5.0 / 2012-03-06
|
49
77
|
|
50
78
|
* Major enhancements
|
51
79
|
|
@@ -55,37 +83,37 @@
|
|
55
83
|
|
56
84
|
* Allow BigNum in 'id' field of request and response
|
57
85
|
|
58
|
-
|
86
|
+
# 0.3.1 / 2011-08-11
|
59
87
|
|
60
88
|
* Minor enhancements
|
61
89
|
|
62
90
|
* Refactor the way the server is intantiated/started to work better with config.ru
|
63
91
|
|
64
|
-
|
92
|
+
# 0.3.0 / 2011-08-11
|
65
93
|
|
66
94
|
* Major enhancements
|
67
95
|
|
68
96
|
* Replace eventmachine-httpserver with rack for more cross-platform goodness
|
69
97
|
|
70
|
-
|
98
|
+
# 0.2.3 / 2011-08-01
|
71
99
|
|
72
100
|
* Bug fixes
|
73
101
|
|
74
102
|
* Fix argument error in client error handling
|
75
103
|
|
76
|
-
|
104
|
+
# 0.2.2 / 2011-07-28
|
77
105
|
|
78
106
|
* Bug fixes
|
79
107
|
|
80
108
|
* Fix invalid local variable error in client error handling
|
81
109
|
|
82
|
-
|
110
|
+
# 0.2.1 / 2011-07-27
|
83
111
|
|
84
112
|
* Bug fixes
|
85
113
|
|
86
114
|
* Fix error in client handling some errors caused by errant 'new' keyword
|
87
115
|
|
88
|
-
|
116
|
+
# 0.2.0 / 2011-07-20
|
89
117
|
|
90
118
|
* Major enhancements
|
91
119
|
|
data/README.md
CHANGED
@@ -1,29 +1,44 @@
|
|
1
1
|
# Jimson
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
### [JSON-RPC 2.0](https://www.jsonrpc.org/specification) Client and Server for Ruby
|
4
|
+
|
5
|
+
[](https://github.com/chriskite/jimson/actions/workflows/ruby.yml)
|
4
6
|
|
5
7
|
## Client: Quick Start
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'jimson'
|
11
|
+
client = Jimson::Client.new("http://www.example.com:8999") # the URL for the JSON-RPC 2.0 server to connect to
|
12
|
+
result = client.sum(1,2) # call the 'sum' method on the RPC server and save the result '3'
|
13
|
+
```
|
9
14
|
|
10
15
|
## Server: Quick Start
|
11
|
-
require 'jimson'
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
```ruby
|
18
|
+
require 'jimson'
|
19
|
+
|
20
|
+
class MyHandler
|
21
|
+
extend Jimson::Handler
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
def sum(a,b)
|
24
|
+
a + b
|
25
|
+
end
|
26
|
+
end
|
20
27
|
|
21
|
-
|
22
|
-
|
28
|
+
server = Jimson::Server.new(MyHandler.new)
|
29
|
+
server.start # serve with webrick on http://0.0.0.0:8999/
|
30
|
+
```
|
23
31
|
|
24
32
|
## JSON Engine
|
25
|
-
|
33
|
+
|
34
|
+
Jimson uses [multi\_json](https://github.com/intridea/multi_json), so you can load the JSON library of your choice in your application and Jimson will use it automatically.
|
26
35
|
|
27
36
|
For example, require the 'json' gem in your application:
|
28
|
-
require 'json'
|
29
37
|
|
38
|
+
```ruby
|
39
|
+
require 'json'
|
40
|
+
```
|
41
|
+
|
42
|
+
## Previous maintainer
|
43
|
+
|
44
|
+
This gem was maintained by [Chris Kite](https://github.com/chriskite/) till April 2021.
|
data/Rakefile
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/lib/')
|
2
|
+
require 'jimson/version'
|
1
3
|
require 'rubygems'
|
2
4
|
require 'rake'
|
3
5
|
require 'rspec/core/rake_task'
|
4
6
|
|
7
|
+
gem 'rubygems-tasks', '~> 0.2'
|
8
|
+
require 'rubygems/tasks'
|
9
|
+
|
10
|
+
Gem::Tasks.new
|
11
|
+
|
5
12
|
desc "Run all specs"
|
6
13
|
RSpec::Core::RakeTask.new(:rspec) do |spec|
|
7
14
|
spec.pattern = 'spec/**/*_spec.rb'
|
8
15
|
end
|
9
16
|
|
10
|
-
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
11
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
12
|
-
spec.rcov = true
|
13
|
-
end
|
14
|
-
|
15
17
|
task :default => :rspec
|
16
18
|
|
17
19
|
require 'rdoc/task'
|
18
20
|
|
19
21
|
Rake::RDocTask.new do |rdoc|
|
20
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
21
|
-
|
22
22
|
rdoc.rdoc_dir = 'rdoc'
|
23
|
-
rdoc.title = "jimson #{
|
23
|
+
rdoc.title = "jimson #{Jimson::VERSION}"
|
24
24
|
rdoc.rdoc_files.include('README*')
|
25
25
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
26
26
|
end
|
data/lib/jimson/client.rb
CHANGED
@@ -12,13 +12,17 @@ module Jimson
|
|
12
12
|
rand(10**12)
|
13
13
|
end
|
14
14
|
|
15
|
-
def initialize(url, opts = {}, namespace = nil)
|
15
|
+
def initialize(url, opts = {}, namespace = nil, client_opts = {})
|
16
|
+
URI.parse(url) # for the sake of validating the url
|
16
17
|
@url = url
|
17
|
-
URI.parse(@url) # for the sake of validating the url
|
18
|
-
@batch = []
|
19
18
|
@opts = opts
|
19
|
+
@opts[:id_type] ||= :int # Possible id types: :int, :string
|
20
20
|
@namespace = namespace
|
21
|
-
@
|
21
|
+
@client_opts = client_opts
|
22
|
+
|
23
|
+
@batch = []
|
24
|
+
@headers = opts.slice( * opts.keys - [:id_type] )
|
25
|
+
@headers[:content_type] ||= 'application/json'
|
22
26
|
end
|
23
27
|
|
24
28
|
def process_call(sym, args)
|
@@ -43,11 +47,11 @@ module Jimson
|
|
43
47
|
'jsonrpc' => JSON_RPC_VERSION,
|
44
48
|
'method' => namespaced_method,
|
45
49
|
'params' => args,
|
46
|
-
'id' => self.class.make_id
|
50
|
+
'id' => format_post_id(self.class.make_id)
|
47
51
|
})
|
48
|
-
resp = RestClient.
|
52
|
+
resp = RestClient::Request.execute(@client_opts.merge(:method => :post, :url => @url, :payload => post_data, :headers => @headers))
|
49
53
|
if resp.nil? || resp.body.nil? || resp.body.empty?
|
50
|
-
raise Client::Error::InvalidResponse.new
|
54
|
+
raise Client::Error::InvalidResponse.new(resp)
|
51
55
|
end
|
52
56
|
|
53
57
|
return resp.body
|
@@ -55,9 +59,9 @@ module Jimson
|
|
55
59
|
|
56
60
|
def send_batch_request(batch)
|
57
61
|
post_data = MultiJson.encode(batch)
|
58
|
-
resp = RestClient.
|
62
|
+
resp = RestClient::Request.execute(@client_opts.merge(:method => :post, :url => @url, :payload => post_data, :headers => @headers))
|
59
63
|
if resp.nil? || resp.body.nil? || resp.body.empty?
|
60
|
-
raise Client::Error::InvalidResponse.new
|
64
|
+
raise Client::Error::InvalidResponse.new(resp)
|
61
65
|
end
|
62
66
|
|
63
67
|
return resp.body
|
@@ -72,7 +76,7 @@ module Jimson
|
|
72
76
|
end
|
73
77
|
|
74
78
|
def process_single_response(data)
|
75
|
-
raise Client::Error::InvalidResponse.new if !valid_response?(data)
|
79
|
+
raise Client::Error::InvalidResponse.new(data) if !valid_response?(data)
|
76
80
|
|
77
81
|
if !!data['error']
|
78
82
|
code = data['error']['code']
|
@@ -93,17 +97,17 @@ module Jimson
|
|
93
97
|
return false if data.has_key?('error') && data.has_key?('result')
|
94
98
|
|
95
99
|
if data.has_key?('error')
|
96
|
-
if !data['error'].is_a?(Hash) || !data['error'].has_key?('code') || !data['error'].has_key?('message')
|
100
|
+
if !data['error'].is_a?(Hash) || !data['error'].has_key?('code') || !data['error'].has_key?('message')
|
97
101
|
return false
|
98
102
|
end
|
99
103
|
|
100
|
-
if !data['error']['code'].is_a?(
|
104
|
+
if !data['error']['code'].is_a?(Integer) || !data['error']['message'].is_a?(String)
|
101
105
|
return false
|
102
106
|
end
|
103
107
|
end
|
104
108
|
|
105
109
|
return true
|
106
|
-
|
110
|
+
|
107
111
|
rescue
|
108
112
|
return false
|
109
113
|
end
|
@@ -116,30 +120,39 @@ module Jimson
|
|
116
120
|
end
|
117
121
|
|
118
122
|
def send_batch
|
119
|
-
batch = @batch.map(&:first) # get the requests
|
123
|
+
batch = @batch.map(&:first) # get the requests
|
120
124
|
response = send_batch_request(batch)
|
121
125
|
|
122
126
|
begin
|
123
127
|
responses = MultiJson.decode(response)
|
124
128
|
rescue
|
125
|
-
raise Client::Error::InvalidJSON.new(
|
129
|
+
raise Client::Error::InvalidJSON.new(response)
|
126
130
|
end
|
127
131
|
|
128
132
|
process_batch_response(responses)
|
129
133
|
@batch = []
|
130
134
|
end
|
131
135
|
|
136
|
+
def format_post_id(id)
|
137
|
+
if @opts[:id_type] == :string
|
138
|
+
id.to_s
|
139
|
+
else
|
140
|
+
id
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
132
144
|
end
|
133
145
|
|
134
146
|
class BatchClient < BlankSlate
|
135
|
-
|
147
|
+
|
136
148
|
def initialize(helper)
|
137
149
|
@helper = helper
|
138
150
|
end
|
139
151
|
|
140
152
|
def method_missing(sym, *args, &block)
|
153
|
+
args = args.first if args.size == 1 && args.first.is_a?(Hash)
|
141
154
|
request = Jimson::Request.new(sym.to_s, args)
|
142
|
-
@helper.push_batch_request(request)
|
155
|
+
@helper.push_batch_request(request)
|
143
156
|
end
|
144
157
|
|
145
158
|
end
|
@@ -148,7 +161,7 @@ module Jimson
|
|
148
161
|
reveal :instance_variable_get
|
149
162
|
reveal :inspect
|
150
163
|
reveal :to_s
|
151
|
-
|
164
|
+
|
152
165
|
def self.batch(client)
|
153
166
|
helper = client.instance_variable_get(:@helper)
|
154
167
|
batch_client = BatchClient.new(helper)
|
@@ -156,12 +169,13 @@ module Jimson
|
|
156
169
|
helper.send_batch
|
157
170
|
end
|
158
171
|
|
159
|
-
def initialize(url, opts = {}, namespace = nil)
|
160
|
-
@url, @opts, @namespace = url, opts, namespace
|
161
|
-
@helper = ClientHelper.new(url, opts, namespace)
|
172
|
+
def initialize(url, opts = {}, namespace = nil, client_opts = {})
|
173
|
+
@url, @opts, @namespace, @client_opts = url, opts, namespace, client_opts
|
174
|
+
@helper = ClientHelper.new(url, opts, namespace, client_opts)
|
162
175
|
end
|
163
176
|
|
164
177
|
def method_missing(sym, *args, &block)
|
178
|
+
args = args.first if args.size == 1 && args.first.is_a?(Hash)
|
165
179
|
@helper.process_call(sym, args)
|
166
180
|
end
|
167
181
|
|
@@ -171,6 +185,7 @@ module Jimson
|
|
171
185
|
new_ns = @namespace.nil? ? "#{method}." : "#@namespace#{method}."
|
172
186
|
return Client.new(@url, @opts, new_ns)
|
173
187
|
end
|
188
|
+
args = args.first if args.size == 1 && args.first.is_a?(Hash)
|
174
189
|
@helper.process_call(method, args)
|
175
190
|
end
|
176
191
|
|
data/lib/jimson/client/error.rb
CHANGED
@@ -2,8 +2,8 @@ module Jimson
|
|
2
2
|
class Client
|
3
3
|
module Error
|
4
4
|
class InvalidResponse < StandardError
|
5
|
-
def initialize()
|
6
|
-
super(
|
5
|
+
def initialize(response = nil)
|
6
|
+
super("Invalid or empty response from server:\n#{response.inspect}")
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
data/lib/jimson/router.rb
CHANGED
data/lib/jimson/router/map.rb
CHANGED
@@ -7,8 +7,10 @@ module Jimson
|
|
7
7
|
#
|
8
8
|
class Map
|
9
9
|
|
10
|
-
def initialize
|
10
|
+
def initialize(opts = {})
|
11
11
|
@routes = {}
|
12
|
+
@opts = opts
|
13
|
+
@ns_sep = opts[:ns_sep] || '.'
|
12
14
|
end
|
13
15
|
|
14
16
|
#
|
@@ -28,7 +30,7 @@ module Jimson
|
|
28
30
|
@routes[ns.to_s] = handler
|
29
31
|
else
|
30
32
|
# passed a block for nested namespacing
|
31
|
-
map = Jimson::Router::Map.new
|
33
|
+
map = Jimson::Router::Map.new(@opts)
|
32
34
|
@routes[ns.to_s] = map
|
33
35
|
map.instance_eval &block
|
34
36
|
end
|
@@ -38,11 +40,11 @@ module Jimson
|
|
38
40
|
# Return the handler for a (possibly namespaced) method name
|
39
41
|
#
|
40
42
|
def handler_for_method(method)
|
41
|
-
parts = method.split(
|
42
|
-
ns = (method.index(
|
43
|
+
parts = method.split(@ns_sep)
|
44
|
+
ns = (method.index(@ns_sep) == nil ? '' : parts.first)
|
43
45
|
handler = @routes[ns]
|
44
46
|
if handler.is_a?(Jimson::Router::Map)
|
45
|
-
return handler.handler_for_method(parts[1..-1].join(
|
47
|
+
return handler.handler_for_method(parts[1..-1].join(@ns_sep))
|
46
48
|
end
|
47
49
|
handler
|
48
50
|
end
|
@@ -51,7 +53,7 @@ module Jimson
|
|
51
53
|
# Strip off the namespace part of a method and return the bare method name
|
52
54
|
#
|
53
55
|
def strip_method_namespace(method)
|
54
|
-
method.split(
|
56
|
+
method.split(@ns_sep).last
|
55
57
|
end
|
56
58
|
|
57
59
|
#
|
@@ -59,7 +61,7 @@ module Jimson
|
|
59
61
|
#
|
60
62
|
def jimson_methods
|
61
63
|
arr = @routes.keys.map do |ns|
|
62
|
-
prefix = (ns == '' ? '' : "#{ns}
|
64
|
+
prefix = (ns == '' ? '' : "#{ns}#{@ns_sep}")
|
63
65
|
handler = @routes[ns]
|
64
66
|
if handler.is_a?(Jimson::Router::Map)
|
65
67
|
handler.jimson_methods
|
data/lib/jimson/server.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -5,8 +5,8 @@ module Jimson
|
|
5
5
|
BOILERPLATE = {'jsonrpc' => '2.0', 'id' => 1}
|
6
6
|
|
7
7
|
before(:each) do
|
8
|
-
@resp_mock =
|
9
|
-
ClientHelper.stub
|
8
|
+
@resp_mock = double('http_response')
|
9
|
+
ClientHelper.stub(:make_id).and_return(1)
|
10
10
|
end
|
11
11
|
|
12
12
|
after(:each) do
|
@@ -36,7 +36,7 @@ module Jimson
|
|
36
36
|
'id' => 1
|
37
37
|
})
|
38
38
|
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
39
|
-
RestClient.should_receive(:
|
39
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
40
40
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
41
41
|
@client[:foo].sum(1, 2, 3).should == 42
|
42
42
|
end
|
@@ -50,11 +50,40 @@ module Jimson
|
|
50
50
|
'id' => 1
|
51
51
|
})
|
52
52
|
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
53
|
-
RestClient.should_receive(:
|
53
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
54
54
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
55
55
|
@client[:foo][:bar].sum(1, 2, 3).should == 42
|
56
56
|
end
|
57
57
|
end
|
58
|
+
|
59
|
+
context "when sending named paramaters" do
|
60
|
+
it "sends the the params as a hash" do
|
61
|
+
expected = MultiJson.encode({
|
62
|
+
'jsonrpc' => '2.0',
|
63
|
+
'method' => 'foo.sum',
|
64
|
+
'params' => { :first_number => 5, :second_number => 6 },
|
65
|
+
'id' => 1
|
66
|
+
})
|
67
|
+
response = MultiJson.encode(BOILERPLATE.merge({'result' => 11}))
|
68
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
69
|
+
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
70
|
+
@client[:foo].sum({:first_number => 5, :second_number => 6}).should == 11
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "sends the id as string" do
|
75
|
+
expected = MultiJson.encode({
|
76
|
+
'jsonrpc' => '2.0',
|
77
|
+
'method' => 'foo.sum',
|
78
|
+
'params' => [1,2,3],
|
79
|
+
'id' => "1"
|
80
|
+
})
|
81
|
+
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
82
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
83
|
+
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
84
|
+
client = Client.new(SPEC_URL, {id_type: :string})
|
85
|
+
client[:foo].sum(1, 2, 3).should == 42
|
86
|
+
end
|
58
87
|
end
|
59
88
|
|
60
89
|
context "when sending positional arguments" do
|
@@ -66,7 +95,7 @@ module Jimson
|
|
66
95
|
'id' => 1
|
67
96
|
})
|
68
97
|
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
69
|
-
RestClient.should_receive(:
|
98
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
70
99
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
71
100
|
@client['foo', 1, 2, 3].should == 42
|
72
101
|
end
|
@@ -80,7 +109,7 @@ module Jimson
|
|
80
109
|
'id' => 1
|
81
110
|
})
|
82
111
|
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
83
|
-
RestClient.should_receive(:
|
112
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
84
113
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
85
114
|
@client['foo', [1, 2], 3].should == 42
|
86
115
|
end
|
@@ -100,7 +129,7 @@ module Jimson
|
|
100
129
|
end
|
101
130
|
it "sends a valid JSON-RPC request and returns the result" do
|
102
131
|
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
103
|
-
RestClient.should_receive(:
|
132
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
104
133
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
105
134
|
client = Client.new(SPEC_URL)
|
106
135
|
client.foo(1,2,3).should == 42
|
@@ -108,11 +137,27 @@ module Jimson
|
|
108
137
|
|
109
138
|
it "sends a valid JSON-RPC request with custom options" do
|
110
139
|
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
111
|
-
RestClient.should_receive(:
|
140
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {:content_type => 'application/json', :timeout => 10000}).and_return(@resp_mock)
|
112
141
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
113
142
|
client = Client.new(SPEC_URL, :timeout => 10000)
|
114
143
|
client.foo(1,2,3).should == 42
|
115
144
|
end
|
145
|
+
|
146
|
+
it "sends a valid JSON-RPC request with custom content_type" do
|
147
|
+
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
148
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {:content_type => 'application/json-rpc', :timeout => 10000}).and_return(@resp_mock)
|
149
|
+
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
150
|
+
client = Client.new(SPEC_URL, :timeout => 10000, :content_type => 'application/json-rpc')
|
151
|
+
client.foo(1,2,3).should == 42
|
152
|
+
end
|
153
|
+
|
154
|
+
it "adds RestClient::Request parameters if given" do
|
155
|
+
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
156
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {:content_type => 'application/json-rpc', :timeout => 10000}, ssl_ca_file: 'myca.pem').and_return(@resp_mock)
|
157
|
+
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
158
|
+
client = Client.new(SPEC_URL, {:timeout => 10000, :content_type => 'application/json-rpc'}, '', {ssl_ca_file: 'myca.pem'})
|
159
|
+
client.foo(1,2,3).should == 42
|
160
|
+
end
|
116
161
|
end
|
117
162
|
|
118
163
|
context "when one of the parameters is an array" do
|
@@ -124,12 +169,44 @@ module Jimson
|
|
124
169
|
'id' => 1
|
125
170
|
})
|
126
171
|
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
127
|
-
RestClient.should_receive(:
|
172
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
128
173
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
129
174
|
client = Client.new(SPEC_URL)
|
130
175
|
client.foo([1,2],3).should == 42
|
131
176
|
end
|
132
177
|
end
|
178
|
+
|
179
|
+
context "when one of the parameters is a hash" do
|
180
|
+
it "sends a correct JSON-RPC request (array is preserved with hash) and returns the results" do
|
181
|
+
expected = MultiJson.encode({
|
182
|
+
'jsonrpc' => '2.0',
|
183
|
+
'method' => 'foo',
|
184
|
+
'params' => [1,{'bar' => 'baz'},3],
|
185
|
+
'id' => 1
|
186
|
+
})
|
187
|
+
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
188
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
189
|
+
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
190
|
+
client = Client.new(SPEC_URL)
|
191
|
+
client.foo(1, {'bar' => 'baz'}, 3).should == 42
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "when using named parameters" do
|
196
|
+
it "sends a correct JSON-RPC request (named parameters are preserved) and returns the result" do
|
197
|
+
expected = MultiJson.encode({
|
198
|
+
'jsonrpc' => '2.0',
|
199
|
+
'method' => 'foo',
|
200
|
+
'params' => {'bar' => 'baz'},
|
201
|
+
'id' => 1
|
202
|
+
})
|
203
|
+
response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
|
204
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
|
205
|
+
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
206
|
+
client = Client.new(SPEC_URL)
|
207
|
+
client.foo({'bar' => 'baz'}).should == 42
|
208
|
+
end
|
209
|
+
end
|
133
210
|
end
|
134
211
|
|
135
212
|
describe "sending a batch request" do
|
@@ -137,8 +214,8 @@ module Jimson
|
|
137
214
|
batch = MultiJson.encode([
|
138
215
|
{"jsonrpc" => "2.0", "method" => "sum", "params" => [1,2,4], "id" => "1"},
|
139
216
|
{"jsonrpc" => "2.0", "method" => "subtract", "params" => [42,23], "id" => "2"},
|
140
|
-
{"jsonrpc" => "2.0", "method" => "foo_get", "params" =>
|
141
|
-
{"jsonrpc" => "2.0", "method" => "get_data", "id" => "9"}
|
217
|
+
{"jsonrpc" => "2.0", "method" => "foo_get", "params" => {"name" => "myself"}, "id" => "5"},
|
218
|
+
{"jsonrpc" => "2.0", "method" => "get_data", "id" => "9"}
|
142
219
|
])
|
143
220
|
|
144
221
|
response = MultiJson.encode([
|
@@ -148,10 +225,10 @@ module Jimson
|
|
148
225
|
{"jsonrpc" => "2.0", "result" => ["hello", 5], "id" => "9"}
|
149
226
|
])
|
150
227
|
|
151
|
-
ClientHelper.stub
|
152
|
-
RestClient.should_receive(:
|
228
|
+
ClientHelper.stub(:make_id).and_return('1', '2', '5', '9')
|
229
|
+
RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: batch, headers: {:content_type => 'application/json'}, ssl_ca_file: 'myca.pem').and_return(@resp_mock)
|
153
230
|
@resp_mock.should_receive(:body).at_least(:once).and_return(response)
|
154
|
-
client = Client.new(SPEC_URL)
|
231
|
+
client = Client.new(SPEC_URL, {}, '', {ssl_ca_file: 'myca.pem'})
|
155
232
|
|
156
233
|
sum = subtract = foo = data = nil
|
157
234
|
Jimson::Client.batch(client) do |batch|
|
@@ -161,14 +238,14 @@ module Jimson
|
|
161
238
|
data = batch.get_data
|
162
239
|
end
|
163
240
|
|
164
|
-
sum.succeeded?.should
|
165
|
-
sum.is_error?.should
|
241
|
+
sum.succeeded?.should be true
|
242
|
+
sum.is_error?.should be false
|
166
243
|
sum.result.should == 7
|
167
244
|
|
168
245
|
subtract.result.should == 19
|
169
246
|
|
170
|
-
foo.is_error?.should
|
171
|
-
foo.succeeded?.should
|
247
|
+
foo.is_error?.should be true
|
248
|
+
foo.succeeded?.should be false
|
172
249
|
foo.error['code'].should == -32601
|
173
250
|
|
174
251
|
data.result.should == ['hello', 5]
|
@@ -179,9 +256,9 @@ module Jimson
|
|
179
256
|
context "when an error occurs in the Jimson::Client code" do
|
180
257
|
it "tags the raised exception with Jimson::Client::Error" do
|
181
258
|
client_helper = ClientHelper.new(SPEC_URL)
|
182
|
-
ClientHelper.stub
|
259
|
+
ClientHelper.stub(:new).and_return(client_helper)
|
183
260
|
client = Client.new(SPEC_URL)
|
184
|
-
client_helper.stub
|
261
|
+
client_helper.stub(:send_single_request).and_raise "intentional error"
|
185
262
|
lambda { client.foo }.should raise_error(Jimson::Client::Error)
|
186
263
|
end
|
187
264
|
end
|
data/spec/router_spec.rb
CHANGED
@@ -3,7 +3,8 @@ require 'spec_helper'
|
|
3
3
|
module Jimson
|
4
4
|
describe Router do
|
5
5
|
|
6
|
-
let(:router) { Router.new }
|
6
|
+
let(:router) { Router.new(opts) }
|
7
|
+
let(:opts) { {} }
|
7
8
|
|
8
9
|
class RouterFooHandler
|
9
10
|
extend Jimson::Handler
|
@@ -44,7 +45,7 @@ module Jimson
|
|
44
45
|
end
|
45
46
|
|
46
47
|
context 'when given nested namespaces' do
|
47
|
-
|
48
|
+
before {
|
48
49
|
router.draw do
|
49
50
|
root RouterFooHandler
|
50
51
|
namespace 'ns1' do
|
@@ -52,22 +53,43 @@ module Jimson
|
|
52
53
|
namespace 'ns2', RouterBarHandler
|
53
54
|
end
|
54
55
|
end
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
}
|
57
|
+
context 'default ns_sep' do
|
58
|
+
it 'takes a block with a DSL to set the root and namespaces' do
|
59
|
+
router.handler_for_method('hi').should be_a(RouterFooHandler)
|
60
|
+
router.handler_for_method('ns1.hi').should be_a(RouterBazHandler)
|
61
|
+
router.handler_for_method('ns1.ns2.hi').should be_a(RouterBarHandler)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
context 'custom ns_sep' do
|
65
|
+
let(:opts) { {ns_sep: '::'} }
|
66
|
+
it 'takes a block with a DSL to set the root and namespaces' do
|
67
|
+
router.handler_for_method('hi').should be_a(RouterFooHandler)
|
68
|
+
router.handler_for_method('ns1::hi').should be_a(RouterBazHandler)
|
69
|
+
router.handler_for_method('ns1::ns2::hi').should be_a(RouterBarHandler)
|
70
|
+
end
|
59
71
|
end
|
72
|
+
|
60
73
|
end
|
61
74
|
end
|
62
75
|
|
63
76
|
describe '#jimson_methods' do
|
64
|
-
|
77
|
+
before {
|
65
78
|
router.draw do
|
66
79
|
root RouterFooHandler
|
67
80
|
namespace 'foo', RouterBarHandler
|
68
81
|
end
|
69
|
-
|
70
|
-
|
82
|
+
}
|
83
|
+
context 'default ns_sep' do
|
84
|
+
it 'returns an array of namespaced method names from all registered handlers' do
|
85
|
+
router.jimson_methods.sort.should == ['hi', 'foo.bye'].sort
|
86
|
+
end
|
87
|
+
end
|
88
|
+
context 'custom ns_sep' do
|
89
|
+
let(:opts) { {ns_sep: '::'} }
|
90
|
+
it 'returns an array of namespaced method names from all registered handlers' do
|
91
|
+
router.jimson_methods.sort.should == ['hi', 'foo::bye'].sort
|
92
|
+
end
|
71
93
|
end
|
72
94
|
end
|
73
95
|
|
data/spec/server_spec.rb
CHANGED
@@ -118,7 +118,7 @@ module Jimson
|
|
118
118
|
}
|
119
119
|
end
|
120
120
|
|
121
|
-
it "handles
|
121
|
+
it "handles integer" do
|
122
122
|
req = {
|
123
123
|
'jsonrpc' => '2.0',
|
124
124
|
'method' => 'subtract',
|
@@ -458,7 +458,7 @@ module Jimson
|
|
458
458
|
namespace 'foo', OtherHandler.new
|
459
459
|
end
|
460
460
|
|
461
|
-
app.show_errors.should
|
461
|
+
app.show_errors.should be true
|
462
462
|
end
|
463
463
|
end
|
464
464
|
end
|
metadata
CHANGED
@@ -1,124 +1,194 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jimson
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Kite
|
8
|
-
|
8
|
+
- Gilbert
|
9
|
+
- Bodo Tasche
|
10
|
+
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
|
-
date:
|
13
|
+
date: 2021-08-16 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: blankslate
|
15
17
|
requirement: !ruby/object:Gem::Requirement
|
16
18
|
requirements:
|
17
|
-
- -
|
19
|
+
- - ">="
|
18
20
|
- !ruby/object:Gem::Version
|
19
|
-
version: 3.1.
|
21
|
+
version: 3.1.3
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
23
25
|
requirements:
|
24
|
-
- -
|
26
|
+
- - ">="
|
25
27
|
- !ruby/object:Gem::Version
|
26
|
-
version: 3.1.
|
28
|
+
version: 3.1.3
|
27
29
|
- !ruby/object:Gem::Dependency
|
28
30
|
name: rest-client
|
29
31
|
requirement: !ruby/object:Gem::Requirement
|
30
32
|
requirements:
|
31
|
-
- -
|
33
|
+
- - ">="
|
32
34
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
35
|
+
version: 1.7.3
|
34
36
|
type: :runtime
|
35
37
|
prerelease: false
|
36
38
|
version_requirements: !ruby/object:Gem::Requirement
|
37
39
|
requirements:
|
38
|
-
- -
|
40
|
+
- - ">="
|
39
41
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.
|
42
|
+
version: 1.7.3
|
41
43
|
- !ruby/object:Gem::Dependency
|
42
44
|
name: multi_json
|
43
45
|
requirement: !ruby/object:Gem::Requirement
|
44
46
|
requirements:
|
45
|
-
- -
|
47
|
+
- - ">="
|
46
48
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
49
|
+
version: 1.11.2
|
48
50
|
type: :runtime
|
49
51
|
prerelease: false
|
50
52
|
version_requirements: !ruby/object:Gem::Requirement
|
51
53
|
requirements:
|
52
|
-
- -
|
54
|
+
- - ">="
|
53
55
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
56
|
+
version: 1.11.2
|
55
57
|
- !ruby/object:Gem::Dependency
|
56
58
|
name: rack
|
57
59
|
requirement: !ruby/object:Gem::Requirement
|
58
60
|
requirements:
|
59
|
-
- -
|
61
|
+
- - ">="
|
60
62
|
- !ruby/object:Gem::Version
|
61
63
|
version: 1.4.5
|
62
64
|
type: :runtime
|
63
65
|
prerelease: false
|
64
66
|
version_requirements: !ruby/object:Gem::Requirement
|
65
67
|
requirements:
|
66
|
-
- -
|
68
|
+
- - ">="
|
67
69
|
- !ruby/object:Gem::Version
|
68
70
|
version: 1.4.5
|
69
|
-
|
70
|
-
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rspec
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '2.14'
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 2.14.1
|
81
|
+
type: :development
|
82
|
+
prerelease: false
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '2.14'
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 2.14.1
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: rack-test
|
93
|
+
requirement: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
type: :development
|
99
|
+
prerelease: false
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: rake
|
107
|
+
requirement: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
- !ruby/object:Gem::Dependency
|
120
|
+
name: rdoc
|
121
|
+
requirement: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '4.2'
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 4.2.2
|
129
|
+
type: :development
|
130
|
+
prerelease: false
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '4.2'
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 4.2.2
|
139
|
+
description:
|
140
|
+
email:
|
71
141
|
executables: []
|
72
142
|
extensions: []
|
73
143
|
extra_rdoc_files:
|
74
144
|
- README.md
|
75
145
|
files:
|
76
|
-
-
|
146
|
+
- CHANGELOG.md
|
77
147
|
- LICENSE.txt
|
78
|
-
- CHANGELOG.rdoc
|
79
148
|
- README.md
|
80
149
|
- Rakefile
|
81
150
|
- lib/jimson.rb
|
151
|
+
- lib/jimson/client.rb
|
152
|
+
- lib/jimson/client/error.rb
|
153
|
+
- lib/jimson/handler.rb
|
154
|
+
- lib/jimson/request.rb
|
82
155
|
- lib/jimson/response.rb
|
83
156
|
- lib/jimson/router.rb
|
84
|
-
- lib/jimson/server/error.rb
|
85
|
-
- lib/jimson/server.rb
|
86
|
-
- lib/jimson/handler.rb
|
87
157
|
- lib/jimson/router/map.rb
|
88
|
-
- lib/jimson/
|
89
|
-
- lib/jimson/
|
90
|
-
- lib/jimson/
|
158
|
+
- lib/jimson/server.rb
|
159
|
+
- lib/jimson/server/error.rb
|
160
|
+
- lib/jimson/version.rb
|
161
|
+
- spec/client_spec.rb
|
91
162
|
- spec/handler_spec.rb
|
163
|
+
- spec/router_spec.rb
|
92
164
|
- spec/server_spec.rb
|
93
165
|
- spec/spec_helper.rb
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
licenses: []
|
166
|
+
homepage: https://github.com/bitboxer/jimson.git
|
167
|
+
licenses:
|
168
|
+
- MIT
|
98
169
|
metadata: {}
|
99
|
-
post_install_message:
|
170
|
+
post_install_message:
|
100
171
|
rdoc_options: []
|
101
172
|
require_paths:
|
102
173
|
- lib
|
103
174
|
required_ruby_version: !ruby/object:Gem::Requirement
|
104
175
|
requirements:
|
105
|
-
- -
|
176
|
+
- - ">="
|
106
177
|
- !ruby/object:Gem::Version
|
107
178
|
version: '0'
|
108
179
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
180
|
requirements:
|
110
|
-
- -
|
181
|
+
- - ">="
|
111
182
|
- !ruby/object:Gem::Version
|
112
183
|
version: '0'
|
113
184
|
requirements: []
|
114
|
-
|
115
|
-
|
116
|
-
signing_key:
|
185
|
+
rubygems_version: 3.1.2
|
186
|
+
signing_key:
|
117
187
|
specification_version: 4
|
118
188
|
summary: JSON-RPC 2.0 client and server
|
119
189
|
test_files:
|
120
|
-
- spec/handler_spec.rb
|
121
|
-
- spec/server_spec.rb
|
122
|
-
- spec/spec_helper.rb
|
123
190
|
- spec/router_spec.rb
|
124
191
|
- spec/client_spec.rb
|
192
|
+
- spec/handler_spec.rb
|
193
|
+
- spec/spec_helper.rb
|
194
|
+
- spec/server_spec.rb
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.10.0
|