jsonrpc2 0.0.8 → 0.0.9

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