jsonrpc2 0.0.8 → 0.0.9

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.
@@ -10,6 +10,14 @@ end
10
10
  class RemoteAuthError < RemoteError
11
11
  end
12
12
 
13
+ # JSON RPC invalid JSON data
14
+ class RemoteDataError < RemoteError
15
+ end
16
+
17
+ # JSON RPC wrong content type
18
+ class WrongContentTypeError < RemoteError
19
+ end
20
+
13
21
  # Simple JSONRPC client
14
22
  class Client
15
23
  # Create client object
@@ -17,11 +25,11 @@ class Client
17
25
  # @param [String] uri Create client object
18
26
  # @param [Hash] options Global options
19
27
  def initialize(uri, options = {})
20
- @uri = uri
21
- @client = HTTPClient.new
28
+ @uri = uri
29
+ @client = HTTPClient.new
22
30
  @options = options
23
- @id = 0
24
- end
31
+ @id = 0
32
+ end
25
33
 
26
34
  # Call method with named arguments
27
35
  # @param [String] method Remote method name
@@ -30,8 +38,8 @@ class Client
30
38
  # @return Method call result
31
39
  # @raise [RemoteError] Error thrown by API
32
40
  # @raise Transport/Network/HTTP errors
33
- def call(method, args = {}, options = {}, &block)
34
- headers = { 'Content-Type' => 'application/json-rpc' }
41
+ def call(method, args = {}, options = {}, &block)
42
+ headers = { 'Content-Type' => 'application/json-rpc' }
35
43
 
36
44
  # Merge one level of hashes - ie. merge :headers
37
45
  options = @options.merge(options) { |key,v1,v2| v2 = v1.merge(v2) if v1.class == v2.class && v1.is_a?(Hash); v2 }
@@ -43,30 +51,57 @@ class Client
43
51
  if options[:user] && options[:pass]
44
52
  @client.set_auth(@uri, options[:user], options[:pass])
45
53
  end
46
- result = @client.post(@uri,
47
- { 'method' => method, 'params' => args, 'jsonrpc' => '2.0', 'id' => (@id+=1) }.to_json,
48
- headers)
49
- if result.contenttype =~ /^application\/json/
50
- body = result.body
51
- body = body.content if body.respond_to?(:content) #
52
- data = JSON.parse body
53
- if data.has_key?('result')
54
- return data['result']
55
- else
54
+ result = @client.post(@uri,
55
+ { 'method' => method, 'params' => args, 'jsonrpc' => '2.0', 'id' => (@id+=1) }.to_json,
56
+ headers)
57
+
58
+ body = result.body
59
+ body = body.content if body.respond_to?(:content) # Only on old versions of HTTPAccess2 ?
60
+
61
+ if result.status_code == 200
62
+ begin
63
+ data = JSON.parse body
64
+ rescue Exception
65
+ body = result.body.to_s
66
+ body = "#{body[0..256]}...[#{body.size-256} bytes trimmed]" if body.size > 256
67
+
68
+ if result.contenttype =~ /^application\/json/
69
+ raise RemoteDataError, "Content-Type is '#{result.contenttype}', but body of '#{body}' failed to parse."
70
+ else
71
+ raise WrongContentTypeError, "Content-Type is '#{result.contenttype}', but should be application/json-rpc or application/json (body='#{body}')"
72
+ end
73
+ end
74
+
75
+ unless data.is_a?(Hash) && data["jsonrpc"] == "2.0"
76
+ raise RemoteDataError, "No jsonrpc parameter in response. This must be \"2.0\""
77
+ end
78
+
79
+ unless result.contenttype =~ /^application\/json/
80
+ STDERR.puts "WARNING: Content-Type is '#{result.contenttype}', but should be application/json-rpc or application/json."
81
+ end
82
+
83
+ if data.has_key?('result')
84
+ return data['result']
85
+ elsif data.has_key?('error')
56
86
  if data['error']['code'] == -32000 && data['error']['message'] =~ /^AuthFail/
57
87
  raise RemoteAuthError, data['error']['message']
58
88
  else
59
89
  raise RemoteError, data['error']['message']
60
90
  end
61
- end
91
+ else
92
+ body = result.body.to_s
93
+ body = "#{body[0..256]}...[#{body.size-256} bytes trimmed]" if body.size > 256
94
+
95
+ raise RemoteDataError, "A JSON-RPC 2.0 response must either have a 'result' or an 'error' value - in '#{body}'."
96
+ end
62
97
  elsif result.status_code == 401
63
98
  if result.headers['WWW-Authenticate'].to_s =~ /realm="([^"]*?)"/
64
99
  suffix = " for #{$1}"
65
100
  end
66
101
  raise RemoteAuthError, "Authentication failed#{suffix}"
67
- else
68
- raise result.body
69
- end
70
- end
102
+ else
103
+ raise RemoteAuthError, "Call failed - HTTP status #{result.status_code}"
104
+ end
105
+ end
71
106
  end
72
107
  end
@@ -15,8 +15,8 @@ module JSONRPC2
15
15
  # * Array [Type] - An array of type Type
16
16
  # * Value (or Any or void) - Any value of any type
17
17
  # * CustomType - A defined custom object type
18
- module Types
19
- module_function
18
+ module Types
19
+ module_function
20
20
  DateTimeRegex = %r"([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
21
21
  DateRegex = %r'\A\d{4}-\d{2}-\d{2}\z'
22
22
  TimeRegex = %r'\A\d{2}:\d{2}(?:\.\d{1,4})?\z'
@@ -29,53 +29,51 @@ module JSONRPC2
29
29
  # separated if more than one
30
30
  # @param object Value to check check type
31
31
  # @return [Boolean] true if ok
32
- def valid?(interface, type_string, object)
33
- res = type_string.split(/,/).any? do |type|
34
- case type
35
- when 'String'
36
- object.is_a?(String)
37
- when 'Number'
38
- object.kind_of?(Numeric)
39
- when 'true'
40
- object == true
41
- when 'false'
42
- object == false
43
- when 'Boolean'
44
- object == true || object == false
45
- when 'null'
46
- object.nil?
47
- when 'Integer'
48
- object.kind_of?(Numeric) && (object.to_i.to_f == object.to_f)
49
- when 'Object'
50
- object.is_a?(Hash)
51
- when 'Array'
52
- object.is_a?(Array)
53
- when 'Date'
54
- object.is_a?(String) && DateRegex.match(object) || object.is_a?(Date)
55
- when 'Time'
56
- object.is_a?(String) && TimeRegex.match(object) || object.is_a?(Time)
57
- when 'DateTime'
58
- object.is_a?(String) && DateTimeRegex.match(object) || object.is_a?(Time)
59
- when /\AArray\[(.*)\]\z/
60
- object.is_a?(Array) && object.all? { |value| valid?(interface, $1, value) }
61
- when 'Value', 'Any', 'void'
62
- true
63
- else # Custom type
64
- subset = (type[-1] == ?*)
65
- type = type[0...-1] if subset
32
+ def valid?(interface, type_string, object)
33
+ res = type_string.split(/,/).any? do |type|
34
+ case type
35
+ when 'String'
36
+ object.is_a?(String)
37
+ when 'Number'
38
+ object.kind_of?(Numeric)
39
+ when 'true'
40
+ object == true
41
+ when 'false'
42
+ object == false
43
+ when 'Boolean'
44
+ object == true || object == false
45
+ when 'null'
46
+ object.nil?
47
+ when 'Integer'
48
+ object.kind_of?(Numeric) && (object.to_i.to_f == object.to_f)
49
+ when 'Object'
50
+ object.is_a?(Hash)
51
+ when 'Array'
52
+ object.is_a?(Array)
53
+ when 'Date'
54
+ object.is_a?(String) && DateRegex.match(object) || object.is_a?(Date)
55
+ when 'Time'
56
+ object.is_a?(String) && TimeRegex.match(object) || object.is_a?(Time)
57
+ when 'DateTime'
58
+ object.is_a?(String) && DateTimeRegex.match(object) || object.is_a?(Time)
59
+ when /\AArray\[(.*)\]\z/
60
+ object.is_a?(Array) && object.all? { |value| valid?(interface, $1, value) }
61
+ when 'Value', 'Any', 'void'
62
+ true
63
+ else # Custom type
64
+ subset = (type[-1] == ?*)
65
+ type = type[0...-1] if subset
66
66
 
67
- custom = interface.types[type]
68
- #STDERR.puts "Type Info: #{custom.inspect}"
69
- if custom
70
- custom.valid_object?(interface, object, subset)
71
- else
72
- raise "Invalid/unknown type: #{type} for #{interface.name}"
73
- end
67
+ custom = interface.types[type]
68
+ if custom
69
+ custom.valid_object?(interface, object, subset)
70
+ else
71
+ raise "Invalid/unknown type: #{type} for #{interface.name}"
74
72
  end
75
73
  end
76
- #STDERR.puts "#{interface.name}: #{type} - #{object.inspect} - #{res ? "OK" : "FAIL"}"
77
- res
78
- end
74
+ end
75
+ res
76
+ end
79
77
 
80
78
  # Checks that param hash is valid for API call
81
79
  #
@@ -83,103 +81,103 @@ module JSONRPC2
83
81
  # @param [String] method Method name
84
82
  # @param [Hash] data params hash to check
85
83
  # @return [Boolean] true if ok
86
- def valid_params?(interface, method, data)
87
- about = interface.about[method.to_s]
88
- return true if about.nil? # No defined params
84
+ def valid_params?(interface, method, data)
85
+ about = interface.about[method.to_s]
86
+ return true if about.nil? # No defined params
89
87
 
90
- params = (about[:params] || [])
91
- param_names = params.map { |param| param[:name] }
88
+ params = (about[:params] || [])
89
+ param_names = params.map { |param| param[:name] }
92
90
 
93
- if params.empty? && data.empty?
94
- return true
95
- end
91
+ if params.empty? && (data.nil? or data.empty?)
92
+ return true
93
+ end
96
94
 
97
- extra_keys = data.keys - param_names
98
- unless extra_keys.empty?
99
- raise "Extra parameters #{extra_keys.inspect} for #{method}."
100
- end
101
-
102
- params.each do |param|
103
- if data.has_key?(param[:name])
95
+ raise "Params should not be nil" if data.nil?
96
+
97
+ extra_keys = data.keys - param_names
98
+ unless extra_keys.empty?
99
+ raise "Extra parameters #{extra_keys.inspect} for #{method}."
100
+ end
101
+
102
+ params.each do |param|
103
+ if data.has_key?(param[:name])
104
104
  value = data[param[:name].to_s]
105
- unless valid?(interface, param[:type], value)
105
+ unless valid?(interface, param[:type], value)
106
106
  raise "'#{param[:name]}' should be of type #{param[:type]}, was #{value.class.name}"
107
107
  end
108
- elsif ! param[:required]
109
- next true
110
- else
111
- raise "Missing parameter: '#{param[:name]}' of type #{param[:type]} for #{method}"
112
- end
113
- end
114
- end
108
+ elsif ! param[:required]
109
+ next true
110
+ else
111
+ raise "Missing parameter: '#{param[:name]}' of type #{param[:type]} for #{method}"
112
+ end
113
+ end
114
+ end
115
115
 
116
116
  # Checks that result is valid for API call
117
- #
117
+ #
118
118
  # @param [Interface] interface API class
119
119
  # @param [String] method Method name
120
120
  # @param [Hash] value Value to check
121
121
  # @return [Boolean] true if ok
122
- def valid_result?(interface, method, value)
123
- about = interface.about[method.to_s]
124
- return true if about.nil? # Undefined
125
- if about[:returns].nil?
126
- return value.nil?
127
- end
128
- valid?(interface, about[:returns][:type], value) or
122
+ def valid_result?(interface, method, value)
123
+ about = interface.about[method.to_s]
124
+ return true if about.nil? # Undefined
125
+ if about[:returns].nil?
126
+ return value.nil?
127
+ end
128
+ valid?(interface, about[:returns][:type], value) or
129
129
  raise "Invalid return type: should have been #{about[:returns][:type]}, was #{value.class.name}"
130
- end
131
- end
130
+ end
131
+ end
132
132
 
133
133
  # Description of JSON object
134
- class JsonObjectType
135
- attr_accessor :name, :fields
136
- def initialize(name, fields)
137
- @name, @fields = name, fields
138
- @required = true
139
- end
134
+ class JsonObjectType
135
+ attr_accessor :name, :fields
136
+ def initialize(name, fields)
137
+ @name, @fields = name, fields
138
+ @required = true
139
+ end
140
140
 
141
141
  # Check that #object Hash is valid version of this type
142
- def valid_object?(interface, object, subset = false)
143
- object.keys.all? { |key| fields.any? { |field| field[:name] == key } } &&
144
- fields.all? { |field| (object.keys.include?(field[:name]) &&
145
- Types.valid?(interface, field[:type], object[field[:name]])) || subset || (! field[:required]) }
146
- end
142
+ def valid_object?(interface, object, subset = false)
143
+ object.keys.all? { |key| fields.any? { |field| field[:name] == key } } &&
144
+ fields.all? { |field| (object.keys.include?(field[:name]) &&
145
+ Types.valid?(interface, field[:type], object[field[:name]])) || subset || (! field[:required]) }
146
+ end
147
147
 
148
148
  # Add field of #name and #type to type description
149
- def field(name, type, desc, options={})
150
- @fields << { :name => name, :type => type, :desc => desc, :required => @required }.merge(options)
151
- end
149
+ def field(name, type, desc, options={})
150
+ @fields << { :name => name, :type => type, :desc => desc, :required => @required }.merge(options)
151
+ end
152
152
  # Shortcut to define string field
153
- def string name, desc, options={}; field(name, 'String', desc, options); end
153
+ def string name, desc, options={}; field(name, 'String', desc, options); end
154
154
  # Shortcut to define number field
155
- def number name, desc, options={}; field(name, 'Number', desc, options); end
155
+ def number name, desc, options={}; field(name, 'Number', desc, options); end
156
156
  # Shortcut to define integer field
157
- def integer name, desc, options={}; field(name, 'Integer', desc, options); end
157
+ def integer name, desc, options={}; field(name, 'Integer', desc, options); end
158
158
  # Shortcut to define boolean field
159
- def boolean name, desc, options={}; field(name, 'Boolean', desc, options); end
159
+ def boolean name, desc, options={}; field(name, 'Boolean', desc, options); end
160
160
 
161
161
  # Make fields defined in block optional by default
162
- def optional(&block)
163
- old_required = @required
164
- begin
165
- @required = false
166
- yield(self)
167
- ensure
168
- @required = old_required
169
- end
170
- end
162
+ def optional(&block)
163
+ old_required = @required
164
+ begin
165
+ @required = false
166
+ yield(self)
167
+ ensure
168
+ @required = old_required
169
+ end
170
+ end
171
171
 
172
172
  # Make fields defined in block required by default
173
- def required(&block)
174
- old_required = @required
175
- begin
176
- @required = true
177
- yield(self)
178
- ensure
179
- @required = old_required
180
- end
181
- end
182
-
183
- end
184
-
173
+ def required(&block)
174
+ old_required = @required
175
+ begin
176
+ @required = true
177
+ yield(self)
178
+ ensure
179
+ @required = old_required
180
+ end
181
+ end
182
+ end
185
183
  end
@@ -1,5 +1,5 @@
1
1
  # JSONRPC2 namespace module
2
2
  module JSONRPC2
3
3
  # Version
4
- VERSION = "0.0.8"
4
+ VERSION = "0.0.9"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonrpc2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: