jsonrpc2 0.0.9 → 0.2.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 +7 -0
- data/.github/workflows/test.yml +26 -0
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +38 -0
- data/Dockerfile +8 -0
- data/README.markdown +36 -46
- data/bin/run-example +7 -0
- data/bin/setup +3 -0
- data/bin/test +7 -0
- data/example/config.ru +29 -24
- data/{bin → exe}/jsonrpc2 +0 -0
- data/jsonrpc2.gemspec +15 -3
- data/lib/assets/js/jquery-1.10.2.min.js +6 -0
- data/lib/jsonrpc2/html.rb +4 -3
- data/lib/jsonrpc2/interface.rb +333 -239
- data/lib/jsonrpc2/textile.rb +5 -5
- data/lib/jsonrpc2/types.rb +14 -10
- data/lib/jsonrpc2/version.rb +1 -1
- data/spec/lib/interface_spec.rb +162 -0
- data/spec/spec_helper.rb +94 -0
- metadata +136 -52
data/lib/jsonrpc2/interface.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'digest'
|
1
5
|
require 'jsonrpc2'
|
2
6
|
require 'jsonrpc2/accept'
|
3
7
|
require 'jsonrpc2/textile'
|
@@ -8,6 +12,16 @@ require 'json'
|
|
8
12
|
require 'base64'
|
9
13
|
|
10
14
|
module JSONRPC2
|
15
|
+
module_function
|
16
|
+
|
17
|
+
def environment
|
18
|
+
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
19
|
+
end
|
20
|
+
|
21
|
+
def development?
|
22
|
+
environment.eql?('development')
|
23
|
+
end
|
24
|
+
|
11
25
|
# Authentication failed error - only used if transport level authentication isn't.
|
12
26
|
# e.g. BasicAuth returns a 401 HTTP response, rather than throw this.
|
13
27
|
class AuthFail < RuntimeError; end
|
@@ -15,301 +29,381 @@ module JSONRPC2
|
|
15
29
|
# API error - thrown when an error is detected, e.g. params of wrong type.
|
16
30
|
class APIFail < RuntimeError; end
|
17
31
|
|
18
|
-
#
|
19
|
-
class
|
20
|
-
|
32
|
+
# KnownError - thrown when a predictable error occurs.
|
33
|
+
class KnownError < RuntimeError
|
34
|
+
attr_accessor :code, :data
|
35
|
+
def self.exception(args)
|
36
|
+
code, message, data = *args
|
37
|
+
exception = new(message)
|
38
|
+
exception.code = code
|
39
|
+
exception.data = data
|
40
|
+
exception
|
41
|
+
end
|
42
|
+
end
|
21
43
|
|
22
|
-
#
|
44
|
+
# Base class for JSONRPC2 interface
|
45
|
+
class Interface
|
46
|
+
class << self
|
47
|
+
# @!group Authentication
|
48
|
+
# Get/set authenticator object for API interface - see {JSONRPC2::Auth} and {JSONRPC2::BasicAuth}
|
49
|
+
#
|
50
|
+
# @param [#check] args An object that responds to check(environment, json_call_data)
|
51
|
+
# @return [#check, nil] Currently set object or nil
|
52
|
+
def auth_with *args
|
53
|
+
if args.empty?
|
54
|
+
return @auth_with
|
55
|
+
else
|
56
|
+
@auth_with = args[0]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# @!endgroup
|
60
|
+
|
61
|
+
# @!group Rack-related
|
62
|
+
# Rack compatible call handler
|
63
|
+
#
|
64
|
+
# @param [Hash] environment Rack environment hash
|
65
|
+
# @return [Array<Fixnum, Hash<String,String>, Array<String>>] Rack-compatible response
|
66
|
+
def call(environment)
|
67
|
+
environment['json.request-id'] = Digest::MD5.hexdigest("#{$host ||= Socket.gethostname}-#{$$}-#{Time.now.to_f}")[0,8]
|
68
|
+
request = Rack::Request.new(environment)
|
69
|
+
catch :rack_response do
|
70
|
+
best = JSONRPC2::HTTPUtils.which(environment['HTTP_ACCEPT'], %w[text/html application/json-rpc application/json])
|
71
|
+
|
72
|
+
if request.path_info =~ %r'/_assets' or request.path_info == '/favicon.ico'
|
73
|
+
best = 'text/html' # hack for assets
|
74
|
+
end
|
75
|
+
|
76
|
+
case best
|
77
|
+
when 'text/html', 'text/css', 'image/png' # Assume browser
|
78
|
+
monitor_time(environment, request.POST['__json__']) { JSONRPC2::HTML.call(self, request) }
|
79
|
+
when 'application/json-rpc', 'application/json', nil # Assume correct by default
|
80
|
+
environment['rack.input'].rewind
|
81
|
+
raw = environment['rack.input'].read
|
82
|
+
data = JSON.parse(raw) if raw.to_s.size >= 2
|
83
|
+
monitor_time(environment, raw) { self.new(environment).rack_dispatch(data) }
|
84
|
+
else
|
85
|
+
[406, {'Content-Type' => 'text/html'},
|
86
|
+
["<!DOCTYPE html><html><head><title>Media type mismatch</title></head><body>I am unable to acquiesce to your request</body></html>"]]
|
87
|
+
end
|
88
|
+
end
|
23
89
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
90
|
+
rescue Exception => e
|
91
|
+
if environment['rack.logger'].respond_to?(:error)
|
92
|
+
environment['rack.logger'].error "#{e.class}: #{e.message} - #{e.backtrace * "\n "}"
|
93
|
+
end
|
94
|
+
raise e.class, e.message, e.backtrace
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def monitor_time(env, data, &block)
|
100
|
+
if env['rack.logger'].respond_to?(:info)
|
101
|
+
if env["HTTP_AUTHORIZATION"].to_s =~ /Basic /i
|
102
|
+
auth = Base64.decode64(env["HTTP_AUTHORIZATION"].to_s.sub(/Basic /i, '')) rescue nil
|
103
|
+
auth ||= env["HTTP_AUTHORIZATION"]
|
104
|
+
else
|
105
|
+
auth = env["HTTP_AUTHORIZATION"]
|
106
|
+
end
|
107
|
+
env['rack.logger'].info("[JSON-RPC2] #{env['json.request-id']} #{env['REQUEST_URI']} - Auth: #{auth}, Data: #{data.is_a?(String) ? data : data.inspect}")
|
108
|
+
end
|
109
|
+
t = Time.now.to_f
|
110
|
+
return yield
|
111
|
+
ensure
|
112
|
+
if env['rack.logger'].respond_to?(:info)
|
113
|
+
env['rack.logger'].info("[JSON-RPC2] #{env['json.request-id']} Completed in #{'%.3f' % ((Time.now.to_f - t) * 1000)}ms#{ $! ? " - exception = #{$!.class}:#{$!.message}" : "" }")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
# @!endgroup
|
34
117
|
end
|
35
118
|
|
36
|
-
#
|
119
|
+
# Create new interface object
|
120
|
+
#
|
121
|
+
# @param [Hash] env Rack environment
|
122
|
+
def initialize(env)
|
123
|
+
@_jsonrpc_env = env
|
124
|
+
@_jsonrpc_request = Rack::Request.new(env)
|
125
|
+
end
|
37
126
|
|
38
|
-
#
|
127
|
+
# Internal
|
128
|
+
def rack_dispatch(rpcData)
|
129
|
+
catch(:rack_response) do
|
130
|
+
json = dispatch(rpcData)
|
131
|
+
[200, {'Content-Type' => 'application/json-rpc'}, [json]]
|
132
|
+
end
|
133
|
+
end
|
39
134
|
|
40
|
-
#
|
135
|
+
# Dispatch call to api method(s)
|
41
136
|
#
|
42
|
-
# @param [Hash]
|
43
|
-
# @return [
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
best = 'text/html' # hack for assets
|
51
|
-
end
|
52
|
-
|
53
|
-
case best
|
54
|
-
when 'text/html', 'text/css', 'image/png' # Assume browser
|
55
|
-
JSONRPC2::HTML.call(self, request)
|
56
|
-
when 'application/json-rpc', 'application/json', nil # Assume correct by default
|
57
|
-
environment['rack.input'].rewind
|
58
|
-
data = JSON.parse(environment['rack.input'].read)
|
59
|
-
self.new(environment).rack_dispatch(data)
|
60
|
-
else
|
61
|
-
[406, {'Content-Type' => 'text/html'},
|
62
|
-
["<!DOCTYPE html><html><head><title>Media type mismatch</title></head><body>I am unable to acquiesce to your request</body></html>"]]
|
63
|
-
end
|
137
|
+
# @param [Hash,Array] rpc_data Array of calls or Hash containing one call
|
138
|
+
# @return [Hash,Array] Depends on input, but either a hash result or an array of results corresponding to calls.
|
139
|
+
def dispatch(rpc_data)
|
140
|
+
result = case rpc_data
|
141
|
+
when Array
|
142
|
+
rpc_data.map { |rpc| dispatch_single(rpc) }
|
143
|
+
else
|
144
|
+
dispatch_single(rpc_data)
|
64
145
|
end
|
146
|
+
|
147
|
+
return result.to_json
|
65
148
|
end
|
66
149
|
|
67
|
-
|
150
|
+
protected
|
68
151
|
|
69
|
-
|
152
|
+
# JSON result helper
|
153
|
+
def response_ok(id, result)
|
154
|
+
{ 'jsonrpc' => '2.0', 'result' => result, 'id' => id }
|
155
|
+
end
|
70
156
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
def initialize(env)
|
75
|
-
@_jsonrpc_env = env
|
76
|
-
@_jsonrpc_request = Rack::Request.new(env)
|
77
|
-
end
|
78
|
-
# Internal
|
79
|
-
def rack_dispatch(rpcData)
|
80
|
-
catch(:rack_response) do
|
81
|
-
json = dispatch(rpcData)
|
82
|
-
[200, {'Content-Type' => 'application/json-rpc'}, [json]]
|
157
|
+
# JSON error helper
|
158
|
+
def response_error(code, message, data)
|
159
|
+
{ 'jsonrpc' => '2.0', 'error' => { 'code' => code, 'message' => message, 'data' => data }, 'id' => (@_jsonrpc_call && @_jsonrpc_call['id'] || nil) }
|
83
160
|
end
|
84
|
-
end
|
85
161
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# @return [Hash,Array] Depends on input, but either a hash result or an array of results corresponding to calls.
|
90
|
-
def dispatch(rpc_data)
|
91
|
-
case rpc_data
|
92
|
-
when Array
|
93
|
-
rpc_data.map { |rpc| dispatch_single(rpc) }.to_json
|
94
|
-
else
|
95
|
-
dispatch_single(rpc_data).to_json
|
162
|
+
# Params helper
|
163
|
+
def params
|
164
|
+
@_jsonrpc_call['params']
|
96
165
|
end
|
97
|
-
end
|
98
166
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
{ 'jsonrpc' => '2.0', 'result' => result, 'id' => id }
|
103
|
-
end
|
104
|
-
# JSON error helper
|
105
|
-
def response_error(code, message, data)
|
106
|
-
{ 'jsonrpc' => '2.0', 'error' => { 'code' => code, 'message' => message, 'data' => data }, 'id' => (@_jsonrpc_call && @_jsonrpc_call['id'] || nil) }
|
107
|
-
end
|
108
|
-
# Params helper
|
109
|
-
def params
|
110
|
-
@_jsonrpc_call['params']
|
111
|
-
end
|
112
|
-
# Auth info
|
113
|
-
def auth
|
114
|
-
@_jsonrpc_auth
|
115
|
-
end
|
116
|
-
# Rack::Request
|
117
|
-
def request
|
118
|
-
@_jsonrpc_request
|
119
|
-
end
|
120
|
-
# Check call validity and authentication & make a single method call
|
121
|
-
#
|
122
|
-
# @param [Hash] rpc JSON-RPC-2 call
|
123
|
-
def dispatch_single(rpc)
|
124
|
-
unless rpc.has_key?('id') && rpc.has_key?('method') && rpc['jsonrpc'].eql?('2.0')
|
125
|
-
return response_error(-32600, 'Invalid request', nil)
|
167
|
+
# Auth info
|
168
|
+
def auth
|
169
|
+
@_jsonrpc_auth
|
126
170
|
end
|
127
|
-
@_jsonrpc_call = rpc
|
128
171
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
172
|
+
# Rack::Request
|
173
|
+
def request
|
174
|
+
@_jsonrpc_request
|
175
|
+
end
|
133
176
|
|
134
|
-
|
135
|
-
|
136
|
-
response_error(-32000, "AuthFail: #{e.class}: #{e.message}", {}) # XXX: Change me
|
137
|
-
rescue APIFail => e
|
138
|
-
response_error(-32000, "APIFail: #{e.class}: #{e.message}", {}) # XXX: Change me
|
139
|
-
rescue Exception => e
|
140
|
-
response_error(-32000, "#{e.class}: #{e.message}", e.backtrace) # XXX: Change me
|
177
|
+
def env
|
178
|
+
@_jsonrpc_env
|
141
179
|
end
|
142
|
-
end
|
143
|
-
# List API methods
|
144
|
-
#
|
145
|
-
# @return [Array] List of api method names
|
146
|
-
def api_methods
|
147
|
-
public_methods(false).map(&:to_s) - ['rack_dispatch', 'dispatch']
|
148
|
-
end
|
149
180
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
181
|
+
# Logger
|
182
|
+
def logger
|
183
|
+
@_jsonrpc_logger ||= (@_jsonrpc_env['rack.logger'] || Rack::NullLogger.new("null"))
|
184
|
+
end
|
185
|
+
|
186
|
+
# Check call validity and authentication & make a single method call
|
187
|
+
#
|
188
|
+
# @param [Hash] rpc JSON-RPC-2 call
|
189
|
+
def dispatch_single(rpc)
|
190
|
+
t = Time.now.to_f
|
191
|
+
|
192
|
+
result = _dispatch_single(rpc)
|
193
|
+
|
194
|
+
if result['result']
|
195
|
+
logger.info("[JSON-RPC2] #{env['json.request-id']} Call completed OK in #{'%.3f' % ((Time.now.to_f - t) * 1000)}ms")
|
196
|
+
elsif result['error']
|
197
|
+
logger.info("[JSON-RPC2] #{env['json.request-id']} Call to ##{rpc['method']} failed in #{'%.3f' % ((Time.now.to_f - t) * 1000)}ms with error #{result['error']['code']} - #{result['error']['message']}")
|
162
198
|
end
|
163
199
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
200
|
+
result
|
201
|
+
end
|
202
|
+
|
203
|
+
def _dispatch_single(rpc)
|
204
|
+
unless rpc.has_key?('id') && rpc.has_key?('method') && rpc['jsonrpc'].eql?('2.0')
|
205
|
+
return response_error(-32600, 'Invalid request', nil)
|
168
206
|
end
|
207
|
+
@_jsonrpc_call = rpc
|
169
208
|
|
170
209
|
begin
|
171
|
-
|
210
|
+
if self.class.auth_with && ! @_jsonrpc_auth
|
211
|
+
(@_jsonrpc_auth = self.class.auth_with.client_check(@_jsonrpc_env, rpc)) or raise AuthFail, "Invalid credentials"
|
212
|
+
end
|
213
|
+
|
214
|
+
call(rpc['method'], rpc['id'], rpc['params'])
|
215
|
+
rescue AuthFail => e
|
216
|
+
response_error(-32000, 'AuthFail: Invalid credentials', {})
|
217
|
+
rescue APIFail => e
|
218
|
+
response_error(-32000, 'APIFail', {})
|
219
|
+
rescue KnownError => e
|
220
|
+
response_error(e.code, 'An error occurred', {})
|
172
221
|
rescue Exception => e
|
173
|
-
|
222
|
+
log_error("Internal error calling #{rpc.inspect} - #{e.class}: #{e.message} #{e.backtrace.join("\n ")}")
|
223
|
+
invoke_server_error_hook(e)
|
224
|
+
response_error(-32000, "An error occurred. Check logs for details", {})
|
174
225
|
end
|
175
|
-
|
176
|
-
response_ok(id, result)
|
177
|
-
else
|
178
|
-
response_error(-32601, "Unknown method `#{method.inspect}'", {})
|
179
226
|
end
|
180
|
-
end
|
181
227
|
|
182
|
-
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
options[:required] = true
|
188
|
-
end
|
189
|
-
@params << options.merge({ :name => name, :type => type })
|
228
|
+
# List API methods
|
229
|
+
#
|
230
|
+
# @return [Array] List of api method names
|
231
|
+
def api_methods
|
232
|
+
public_methods(false).map(&:to_s) - ['rack_dispatch', 'dispatch']
|
190
233
|
end
|
191
|
-
private :___append_param
|
192
234
|
|
193
|
-
#
|
194
|
-
|
195
|
-
# Define a named parameter of type #type for next method
|
235
|
+
# Call method, checking param and return types
|
196
236
|
#
|
197
|
-
# @param [String]
|
198
|
-
# @param [
|
199
|
-
|
200
|
-
|
201
|
-
|
237
|
+
# @param [String] method Method name
|
238
|
+
# @param [Integer] id Method call ID - for response
|
239
|
+
# @param [Hash] params Method parameters
|
240
|
+
# @return [Hash] JSON response
|
241
|
+
def call(method, id, params)
|
242
|
+
if api_methods.include?(method)
|
243
|
+
begin
|
244
|
+
Types.valid_params?(self.class, method, params)
|
245
|
+
rescue Types::InvalidParamsError => e
|
246
|
+
return response_error(-32602, "Invalid params - #{e.message}", {})
|
247
|
+
end
|
248
|
+
|
249
|
+
if self.method(method).arity.zero?
|
250
|
+
result = send(method)
|
251
|
+
else
|
252
|
+
result = send(method, params)
|
253
|
+
end
|
254
|
+
|
255
|
+
Types.valid_result?(self.class, method, result)
|
256
|
+
|
257
|
+
response_ok(id, result)
|
258
|
+
else
|
259
|
+
response_error(-32601, "Unknown method `#{method.inspect}'", {})
|
202
260
|
end
|
203
|
-
options ||= {}
|
204
|
-
options[:desc] = desc if desc.is_a?(String)
|
205
|
-
|
206
|
-
___append_param name, type, options
|
207
261
|
end
|
208
262
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
263
|
+
class << self
|
264
|
+
# Store parameter in internal hash when building API
|
265
|
+
def ___append_param name, type, options
|
266
|
+
@params ||= []
|
267
|
+
unless options.has_key?(:required)
|
268
|
+
options[:required] = true
|
269
|
+
end
|
270
|
+
@params << options.merge({ :name => name, :type => type })
|
213
271
|
end
|
214
|
-
|
215
|
-
|
272
|
+
private :___append_param
|
273
|
+
|
274
|
+
# @!group DSL
|
275
|
+
# Define a named parameter of type #type for next method
|
276
|
+
#
|
277
|
+
# @param [String] name parameter name
|
278
|
+
# @param [String] type description of type see {Types}
|
279
|
+
def param name, type, desc = nil, options = nil
|
280
|
+
if options.nil? && desc.is_a?(Hash)
|
281
|
+
options, desc = desc, nil
|
282
|
+
end
|
283
|
+
options ||= {}
|
284
|
+
options[:desc] = desc if desc.is_a?(String)
|
216
285
|
|
217
|
-
|
218
|
-
|
286
|
+
___append_param name, type, options
|
287
|
+
end
|
219
288
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
289
|
+
# Define an optional parameter for next method
|
290
|
+
def optional name, type, desc = nil, options = nil
|
291
|
+
if options.nil? && desc.is_a?(Hash)
|
292
|
+
options, desc = desc, nil
|
293
|
+
end
|
294
|
+
options ||= {}
|
295
|
+
options[:desc] = desc if desc.is_a?(String)
|
224
296
|
|
225
|
-
|
226
|
-
|
227
|
-
@desc = str
|
228
|
-
end
|
297
|
+
___append_param(name, type, options.merge(:required => false))
|
298
|
+
end
|
229
299
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
end
|
300
|
+
# Define type of return value for next method
|
301
|
+
def result type, desc = nil
|
302
|
+
@result = { :type => type, :desc => desc }
|
303
|
+
end
|
235
304
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
305
|
+
# Set description for next method
|
306
|
+
def desc str
|
307
|
+
@desc = str
|
308
|
+
end
|
240
309
|
|
241
|
-
|
242
|
-
|
243
|
-
|
310
|
+
# Add an example for next method
|
311
|
+
def example desc, code
|
312
|
+
@examples ||= []
|
313
|
+
@examples << { :desc => desc, :code => code }
|
314
|
+
end
|
244
315
|
|
245
|
-
|
246
|
-
|
316
|
+
# Define a custom type
|
317
|
+
def type name, *fields
|
318
|
+
@types ||= {}
|
319
|
+
type = JsonObjectType.new(name, fields)
|
247
320
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
@sections << {:name => name, :summary => summary}
|
321
|
+
if block_given?
|
322
|
+
yield(type)
|
323
|
+
end
|
252
324
|
|
253
|
-
|
254
|
-
|
255
|
-
yield
|
256
|
-
@current_section = nil
|
257
|
-
end
|
258
|
-
end
|
325
|
+
@types[name] = type
|
326
|
+
end
|
259
327
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
328
|
+
# Group methods
|
329
|
+
def section name, summary=nil
|
330
|
+
@sections ||= []
|
331
|
+
@sections << {:name => name, :summary => summary}
|
264
332
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
333
|
+
@current_section = name
|
334
|
+
if block_given?
|
335
|
+
yield
|
336
|
+
@current_section = nil
|
337
|
+
end
|
338
|
+
end
|
269
339
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
340
|
+
# Exclude next method from documentation
|
341
|
+
def nodoc
|
342
|
+
@nodoc = true
|
343
|
+
end
|
274
344
|
|
275
|
-
#
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
if
|
289
|
-
|
290
|
-
|
345
|
+
# Set interface title
|
346
|
+
def title str = nil
|
347
|
+
@title = str if str
|
348
|
+
end
|
349
|
+
|
350
|
+
# Sets introduction for interface
|
351
|
+
def introduction str = nil
|
352
|
+
@introduction = str if str
|
353
|
+
end
|
354
|
+
# @!endgroup
|
355
|
+
|
356
|
+
# Catch methods added to class & store documentation
|
357
|
+
def method_added(name)
|
358
|
+
return if self == JSONRPC2::Interface
|
359
|
+
@about ||= {}
|
360
|
+
method = {}
|
361
|
+
method[:params] = @params if @params
|
362
|
+
method[:returns] = @result if @result
|
363
|
+
method[:desc] = @desc if @desc
|
364
|
+
method[:examples] = @examples if @examples
|
365
|
+
|
366
|
+
if method.empty?
|
367
|
+
if public_methods(false).include?(name)
|
368
|
+
unless @nodoc
|
369
|
+
#logger.info("#{name} has no API documentation... :(")
|
370
|
+
end
|
371
|
+
else
|
372
|
+
#logger.debug("#{name} isn't public - so no API")
|
291
373
|
end
|
292
374
|
else
|
293
|
-
|
375
|
+
method[:name] = name
|
376
|
+
method[:section] = @current_section
|
377
|
+
method[:index] = @about.size
|
378
|
+
@about[name.to_s] = method
|
294
379
|
end
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
@
|
380
|
+
|
381
|
+
@result = nil
|
382
|
+
@params = nil
|
383
|
+
@desc = nil
|
384
|
+
@examples = nil
|
385
|
+
@nodoc = false
|
300
386
|
end
|
387
|
+
private :method_added
|
388
|
+
attr_reader :about, :types
|
389
|
+
end
|
390
|
+
|
391
|
+
extend JSONRPC2::TextileEmitter
|
301
392
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
393
|
+
private
|
394
|
+
|
395
|
+
def invoke_server_error_hook(error)
|
396
|
+
on_server_error(request_id: env['json.request-id'], error: error)
|
397
|
+
rescue => error
|
398
|
+
log_error("Server error hook failed - #{error.class}: #{error.message} #{error.backtrace.join("\n ")}")
|
307
399
|
end
|
308
|
-
private :method_added
|
309
|
-
attr_reader :about, :types
|
310
400
|
|
311
|
-
|
401
|
+
# Available for reimplementation by a subclass, noop by default
|
402
|
+
def on_server_error(request_id:, error:)
|
403
|
+
end
|
312
404
|
|
313
|
-
|
314
|
-
|
405
|
+
def log_error(message)
|
406
|
+
logger.error("#{env['json.request-id']} #{message}") if logger.respond_to?(:error)
|
407
|
+
end
|
408
|
+
end
|
315
409
|
end
|