jsonrpc2 0.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.markdown +176 -0
- data/Rakefile +2 -0
- data/example/config.ru +34 -0
- data/jsonrpc2.gemspec +55 -0
- data/lib/jsonrpc2.rb +4 -0
- data/lib/jsonrpc2/accept.rb +67 -0
- data/lib/jsonrpc2/auth.rb +66 -0
- data/lib/jsonrpc2/client.rb +59 -0
- data/lib/jsonrpc2/html.rb +137 -0
- data/lib/jsonrpc2/interface.rb +298 -0
- data/lib/jsonrpc2/textile.rb +131 -0
- data/lib/jsonrpc2/types.rb +174 -0
- data/lib/jsonrpc2/version.rb +5 -0
- metadata +156 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
module JSONRPC2
|
3
|
+
# HTML output helpers for browseable API interface
|
4
|
+
module HTML
|
5
|
+
module_function
|
6
|
+
# Wrap body in basic bootstrap template using cdn
|
7
|
+
def html5(title, body, options={})
|
8
|
+
request = options[:request]
|
9
|
+
[
|
10
|
+
<<-HTML5
|
11
|
+
<!DOCTYPE html><html>
|
12
|
+
<head>
|
13
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
14
|
+
<title>#{title}</title>
|
15
|
+
#{options[:head]}
|
16
|
+
<link rel="stylesheet" href="//current.bootstrapcdn.com/bootstrap-v204/css/bootstrap-combined.min.css">
|
17
|
+
<script src="//current.bootstrapcdn.com/bootstrap-v204/js/bootstrap.min.js"></script>
|
18
|
+
<style>
|
19
|
+
body {
|
20
|
+
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
|
21
|
+
}
|
22
|
+
</style>
|
23
|
+
</head>
|
24
|
+
<body> <div class="navbar navbar-fixed-top">
|
25
|
+
<div class="navbar-inner">
|
26
|
+
<div class="container">
|
27
|
+
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
28
|
+
<span class="icon-bar"></span>
|
29
|
+
<span class="icon-bar"></span>
|
30
|
+
<span class="icon-bar"></span>
|
31
|
+
</a>
|
32
|
+
<a class="brand" href="#">JSON-RPC Interface</a>
|
33
|
+
<div class="nav-collapse">
|
34
|
+
<ul class="nav">
|
35
|
+
<li class="#{['', '/'].include?(request.path_info) ? 'active' : ''}"><a href="#{request.script_name}/">API Overview</a></li>
|
36
|
+
</ul>
|
37
|
+
</div><!--/.nav-collapse -->
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<div class="container">#{body}</div>
|
43
|
+
</body>
|
44
|
+
</html>
|
45
|
+
HTML5
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Process browser request for #interface
|
50
|
+
# @param [JSONRPC2::Interface] interface Interface being accessed
|
51
|
+
# @param [Rack::Request] request Request being processed
|
52
|
+
# @return [Rack::Response]
|
53
|
+
def call(interface, request)
|
54
|
+
#require 'pp'; pp interface.about
|
55
|
+
|
56
|
+
if interface.auth_with.kind_of?(JSONRPC2::HttpAuth)
|
57
|
+
response = catch(:rack_response) do
|
58
|
+
interface.auth_with.check(request.env, {}); nil
|
59
|
+
end
|
60
|
+
return response if response
|
61
|
+
end
|
62
|
+
|
63
|
+
case request.path_info
|
64
|
+
when /^\/([a-zA-Z_0-9]+)/
|
65
|
+
method = $1
|
66
|
+
if info = interface.about_method(method)
|
67
|
+
if json = request.POST['__json__']
|
68
|
+
begin
|
69
|
+
data = JSON.parse(json)
|
70
|
+
result = interface.new(request.env).dispatch(data)
|
71
|
+
rescue => e
|
72
|
+
result = e.class.name + ": " + e.message
|
73
|
+
end
|
74
|
+
end
|
75
|
+
[200, {'Content-Type' => 'text/html'}, html5(method,describe(interface, request, info, :result => result), :request => request) ]
|
76
|
+
else
|
77
|
+
[404, {'Content-Type' => 'text/html'}, html5("Method not found", "<h1>No such method</h1>", :request => request)]
|
78
|
+
end
|
79
|
+
else
|
80
|
+
body = RedCloth.new(interface.to_textile).to_html.gsub(/\<h3\>(.*?)\<\/h3\>/, '<h3><a href="'+request.script_name+'/\1">\1</a></h3>')
|
81
|
+
[200, {'Content-Type' => 'text/html'},
|
82
|
+
html5('Interface: '+interface.name.to_s, body, :request => request)]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
# Returns HTML page describing method
|
86
|
+
def describe interface, request, info, options = {}
|
87
|
+
params = {}
|
88
|
+
if info[:params]
|
89
|
+
info[:params].each do |param|
|
90
|
+
params[param[:name]] = case param[:type]
|
91
|
+
when 'String'
|
92
|
+
""
|
93
|
+
when 'Boolean', 'false'
|
94
|
+
false
|
95
|
+
when 'true'
|
96
|
+
true
|
97
|
+
when 'null'
|
98
|
+
nil
|
99
|
+
when 'Number', 'Integer'
|
100
|
+
0
|
101
|
+
when /^Array/
|
102
|
+
[]
|
103
|
+
else
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
<<-EOS
|
109
|
+
<h1>Method Info: #{info[:name]}</h1>
|
110
|
+
#{RedCloth.new(interface.method_to_textile(info)).to_html}
|
111
|
+
|
112
|
+
<hr>
|
113
|
+
|
114
|
+
<h2>Test method</h2>
|
115
|
+
<div class="row">
|
116
|
+
<div class="span6">
|
117
|
+
<form method="POST" action="#{request.script_name}/#{info[:name]}">
|
118
|
+
<textarea name="__json__" cols="60" rows="8" class="span6">
|
119
|
+
#{CGI.escapeHTML((request.POST['__json__'] || JSON.pretty_unparse({'jsonrpc'=>'2.0', 'method' => info[:name], 'id' => 1, 'params' => params})).strip)}
|
120
|
+
</textarea>
|
121
|
+
<div class="form-actions">
|
122
|
+
<input type="submit" class="btn btn-primary" value="Call Method">
|
123
|
+
</div>
|
124
|
+
</form>
|
125
|
+
</div>
|
126
|
+
<div class="span6">
|
127
|
+
<h3>Result</h3>
|
128
|
+
<xmp>
|
129
|
+
#{options[:result]}
|
130
|
+
</xmp>
|
131
|
+
</div>
|
132
|
+
</div>
|
133
|
+
|
134
|
+
EOS
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'jsonrpc2'
|
2
|
+
require 'jsonrpc2/accept'
|
3
|
+
require 'jsonrpc2/textile'
|
4
|
+
require 'jsonrpc2/auth'
|
5
|
+
require 'jsonrpc2/html'
|
6
|
+
require 'jsonrpc2/types'
|
7
|
+
require 'json'
|
8
|
+
require 'base64'
|
9
|
+
|
10
|
+
module JSONRPC2
|
11
|
+
# Authentication failed error - only used if transport level authentication isn't.
|
12
|
+
# e.g. BasicAuth returns a 401 HTTP response, rather than throw this.
|
13
|
+
class AuthFail < RuntimeError; end
|
14
|
+
|
15
|
+
# API error - thrown when an error is detected, e.g. params of wrong type.
|
16
|
+
class APIFail < RuntimeError; end
|
17
|
+
|
18
|
+
# Base class for JSONRPC2 interface
|
19
|
+
class Interface
|
20
|
+
class << self
|
21
|
+
|
22
|
+
# @!group Authentication
|
23
|
+
|
24
|
+
# Get/set authenticator object for API interface - see {JSONRPC2::Auth} and {JSONRPC2::BasicAuth}
|
25
|
+
#
|
26
|
+
# @param [#check] args An object that responds to check(environment, json_call_data)
|
27
|
+
# @return [#check, nil] Currently set object or nil
|
28
|
+
def auth_with *args
|
29
|
+
if args.empty?
|
30
|
+
return @auth_with
|
31
|
+
else
|
32
|
+
@auth_with = args[0]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!endgroup
|
37
|
+
|
38
|
+
# @!group Rack-related
|
39
|
+
|
40
|
+
# Rack compatible call handler
|
41
|
+
#
|
42
|
+
# @param [Hash] environment Rack environment hash
|
43
|
+
# @return [Array<Fixnum, Hash<String,String>, Array<String>>] Rack-compatible response
|
44
|
+
def call(environment)
|
45
|
+
request = Rack::Request.new(environment)
|
46
|
+
catch :rack_response do
|
47
|
+
case JSONRPC2::HTTPUtils.which(environment['HTTP_ACCEPT'], %w[text/html application/json-rpc application/json])
|
48
|
+
when 'text/html', nil
|
49
|
+
JSONRPC2::HTML.call(self, request)
|
50
|
+
when 'application/json-rpc', 'application/json'
|
51
|
+
environment['rack.input'].rewind
|
52
|
+
data = JSON.parse(environment['rack.input'].read)
|
53
|
+
self.new(environment).rack_dispatch(data)
|
54
|
+
else
|
55
|
+
[406, {'Content-Type' => 'text/html'},
|
56
|
+
["<!DOCTYPE html><html><head><title>Media type mismatch</title></head><body>I am unable to acquiesce to your request</body></html>"]]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @!endgroup
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create new interface object
|
66
|
+
#
|
67
|
+
# @param [Hash] env Rack environment
|
68
|
+
def initialize(env)
|
69
|
+
@env = env
|
70
|
+
end
|
71
|
+
# Internal
|
72
|
+
def rack_dispatch(rpcData)
|
73
|
+
catch(:rack_response) do
|
74
|
+
json = dispatch(rpcData)
|
75
|
+
[200, {'Content-Type' => 'application/json-rpc'}, [json]]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
# Dispatch call to api method(s)
|
81
|
+
#
|
82
|
+
# @param [Hash,Array] rpc_data Array of calls or Hash containing one call
|
83
|
+
# @return [Hash,Array] Depends on input, but either a hash result or an array of results corresponding to calls.
|
84
|
+
def dispatch(rpc_data)
|
85
|
+
case rpc_data
|
86
|
+
when Array
|
87
|
+
rpc_data.map { |rpc| dispatch_single(rpc) }.to_json
|
88
|
+
else
|
89
|
+
dispatch_single(rpc_data).to_json
|
90
|
+
end
|
91
|
+
end
|
92
|
+
# JSON result helper
|
93
|
+
def response_ok(id, result)
|
94
|
+
{ 'jsonrpc' => '2.0', 'result' => result, 'id' => id }
|
95
|
+
end
|
96
|
+
# JSON error helper
|
97
|
+
def response_error(code, message, data)
|
98
|
+
{ 'jsonrpc' => '2.0', 'error' => { 'code' => code, 'message' => message, 'data' => data }, 'id' => @id }
|
99
|
+
end
|
100
|
+
# Check call validity and authentication & make a single method call
|
101
|
+
#
|
102
|
+
# @param [Hash] rpc JSON-RPC-2 call
|
103
|
+
def dispatch_single(rpc)
|
104
|
+
unless rpc.has_key?('id') && rpc.has_key?('method') && rpc['jsonrpc'].eql?('2.0')
|
105
|
+
@id = nil
|
106
|
+
return response_error(-32600, 'Invalid request', nil)
|
107
|
+
end
|
108
|
+
@id = rpc['id']
|
109
|
+
@method = rpc['method']
|
110
|
+
@rpc = rpc
|
111
|
+
|
112
|
+
begin
|
113
|
+
if self.class.auth_with
|
114
|
+
self.class.auth_with.check(@env, rpc) or raise AuthFail, "Invalid credentials"
|
115
|
+
end
|
116
|
+
|
117
|
+
call(rpc['method'], rpc['id'], rpc['params'])
|
118
|
+
rescue AuthFail => e
|
119
|
+
response_error(-32000, "AuthFail: #{e.class}: #{e.message}", {}) # XXX: Change me
|
120
|
+
rescue APIFail => e
|
121
|
+
response_error(-32000, "APIFail: #{e.class}: #{e.message}", {}) # XXX: Change me
|
122
|
+
rescue Exception => e
|
123
|
+
response_error(-32000, "#{e.class}: #{e.message}", e.backtrace) # XXX: Change me
|
124
|
+
end
|
125
|
+
end
|
126
|
+
# List API methods
|
127
|
+
#
|
128
|
+
# @return [Array] List of api method names
|
129
|
+
def api_methods
|
130
|
+
public_methods(false).map(&:to_s) - ['rack_dispatch']
|
131
|
+
end
|
132
|
+
|
133
|
+
# Call method, checking param and return types
|
134
|
+
#
|
135
|
+
# @param [String] method Method name
|
136
|
+
# @param [Integer] id Method call ID - for response
|
137
|
+
# @param [Hash] params Method parameters
|
138
|
+
# @return [Hash] JSON response
|
139
|
+
def call(method, id, params)
|
140
|
+
if api_methods.include?(method)
|
141
|
+
begin
|
142
|
+
Types.valid_params?(self.class, method, params)
|
143
|
+
rescue Exception => e
|
144
|
+
return response_error(-32602, "Invalid params - #{e.message}", {})
|
145
|
+
end
|
146
|
+
|
147
|
+
if self.method(method).arity.zero?
|
148
|
+
result = send(method)
|
149
|
+
else
|
150
|
+
result = send(method, params)
|
151
|
+
end
|
152
|
+
|
153
|
+
begin
|
154
|
+
Types.valid_result?(self.class, method, result)
|
155
|
+
rescue Exception => e
|
156
|
+
return response_error(-32602, "Invalid result - #{e.message}", {})
|
157
|
+
end
|
158
|
+
|
159
|
+
response_ok(id, result)
|
160
|
+
else
|
161
|
+
response_error(-32601, "Unknown method `#{method.inspect}'", {})
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class << self
|
166
|
+
# Store parameter in internal hash when building API
|
167
|
+
def ___append_param name, type, options
|
168
|
+
@params ||= []
|
169
|
+
unless options.has_key?(:required)
|
170
|
+
options[:required] = true
|
171
|
+
end
|
172
|
+
@params << options.merge({ :name => name, :type => type })
|
173
|
+
end
|
174
|
+
private :___append_param
|
175
|
+
|
176
|
+
# @!group DSL
|
177
|
+
|
178
|
+
# Define a named parameter of type #type for next method
|
179
|
+
#
|
180
|
+
# @param [String] name parameter name
|
181
|
+
# @param [String] type description of type see {Types}
|
182
|
+
def param name, type, desc = nil, options = nil
|
183
|
+
if options.nil? && desc.is_a?(Hash)
|
184
|
+
options, desc = desc, nil
|
185
|
+
end
|
186
|
+
options ||= {}
|
187
|
+
options[:desc] = desc if desc.is_a?(String)
|
188
|
+
|
189
|
+
___append_param name, type, options
|
190
|
+
end
|
191
|
+
|
192
|
+
# Define an optional parameter for next method
|
193
|
+
def optional name, type, desc = nil, options = nil
|
194
|
+
if options.nil? && desc.is_a?(Hash)
|
195
|
+
options, desc = desc, nil
|
196
|
+
end
|
197
|
+
options ||= {}
|
198
|
+
options[:desc] = desc if desc.is_a?(String)
|
199
|
+
|
200
|
+
___append_param(name, type, options.merge(:required => false))
|
201
|
+
end
|
202
|
+
|
203
|
+
# Define type of return value for next method
|
204
|
+
def result type, desc = nil
|
205
|
+
@result = { :type => type, :desc => desc }
|
206
|
+
end
|
207
|
+
|
208
|
+
# Set description for next method
|
209
|
+
def desc str
|
210
|
+
@desc = str
|
211
|
+
end
|
212
|
+
|
213
|
+
# Add an example for next method
|
214
|
+
def example desc, code
|
215
|
+
@examples ||= []
|
216
|
+
@examples << { :desc => desc, :code => code }
|
217
|
+
end
|
218
|
+
|
219
|
+
# Define a custom type
|
220
|
+
def type name, *fields
|
221
|
+
@types ||= {}
|
222
|
+
type = JsonObjectType.new(name, fields)
|
223
|
+
|
224
|
+
if block_given?
|
225
|
+
yield(type)
|
226
|
+
end
|
227
|
+
|
228
|
+
@types[name] = type
|
229
|
+
end
|
230
|
+
|
231
|
+
# Group methods
|
232
|
+
def section name, summary=nil
|
233
|
+
@sections ||= []
|
234
|
+
@sections << {:name => name, :summary => summary}
|
235
|
+
|
236
|
+
@current_section = name
|
237
|
+
if block_given?
|
238
|
+
yield
|
239
|
+
@current_section = nil
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Exclude next method from documentation
|
244
|
+
def nodoc
|
245
|
+
@nodoc = true
|
246
|
+
end
|
247
|
+
|
248
|
+
# Set interface title
|
249
|
+
def title str = nil
|
250
|
+
@title = str if str
|
251
|
+
end
|
252
|
+
|
253
|
+
# Sets introduction for interface
|
254
|
+
def introduction str = nil
|
255
|
+
@introduction = str if str
|
256
|
+
end
|
257
|
+
|
258
|
+
# @!endgroup
|
259
|
+
|
260
|
+
# Catch methods added to class & store documentation
|
261
|
+
def method_added(name)
|
262
|
+
return if self == JSONRPC2::Interface
|
263
|
+
@about ||= {}
|
264
|
+
method = {}
|
265
|
+
method[:params] = @params if @params
|
266
|
+
method[:returns] = @result if @result
|
267
|
+
method[:desc] = @desc if @desc
|
268
|
+
method[:examples] = @examples if @examples
|
269
|
+
|
270
|
+
if method.empty?
|
271
|
+
if public_methods(false).include?(name)
|
272
|
+
unless @nodoc
|
273
|
+
#logger.info("#{name} has no API documentation... :(")
|
274
|
+
end
|
275
|
+
else
|
276
|
+
#logger.debug("#{name} isn't public - so no API")
|
277
|
+
end
|
278
|
+
else
|
279
|
+
method[:name] = name
|
280
|
+
method[:section] = @current_section
|
281
|
+
method[:index] = @about.size
|
282
|
+
@about[name.to_s] = method
|
283
|
+
end
|
284
|
+
|
285
|
+
@result = nil
|
286
|
+
@params = nil
|
287
|
+
@desc = nil
|
288
|
+
@examples = nil
|
289
|
+
@nodoc = false
|
290
|
+
end
|
291
|
+
private :method_added
|
292
|
+
attr_reader :about, :types
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
extend JSONRPC2::TextileEmitter
|
297
|
+
end
|
298
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'redcloth'
|
2
|
+
|
3
|
+
module JSONRPC2
|
4
|
+
# Textile documentation output functions
|
5
|
+
module TextileEmitter
|
6
|
+
# Returns interface description in textile
|
7
|
+
def to_textile
|
8
|
+
return nil if @about.nil? or @about.empty?
|
9
|
+
str = ""
|
10
|
+
if @title
|
11
|
+
str << "h1. #{@title}\n"
|
12
|
+
else
|
13
|
+
str << "h1. #{name}\n"
|
14
|
+
end
|
15
|
+
if @introduction
|
16
|
+
str << "\nh2. Introduction\n\n#{@introduction}\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
unless @types.nil? or @types.empty?
|
20
|
+
str << "\nh2. Types\n"
|
21
|
+
@types.sort_by { |k,v| k }.each do |k,type|
|
22
|
+
str << "\nh5. #{k} type\n"
|
23
|
+
|
24
|
+
str << "\n|_. Field |_. Type |_. Required? |_. Description |"
|
25
|
+
type.fields.each do |field|
|
26
|
+
str << "\n| @#{field[:name]}@ | @#{field[:type]}@ | #{field[:required] ? 'Yes' : 'No'} | #{field[:desc]} |"
|
27
|
+
end
|
28
|
+
str << "\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@sections.each do |section|
|
33
|
+
str << "\nh2. #{section[:name]}\n"
|
34
|
+
if section[:summary]
|
35
|
+
str << "\n#{section[:summary]}\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
str += to_textile_group(section).to_s
|
39
|
+
end
|
40
|
+
miscfn = to_textile_group({:name => nil})
|
41
|
+
if miscfn
|
42
|
+
str << "\nh2. Misc functions\n"
|
43
|
+
str << miscfn
|
44
|
+
end
|
45
|
+
str
|
46
|
+
end
|
47
|
+
# Returns method description in textile
|
48
|
+
def method_to_textile(info)
|
49
|
+
str = ''
|
50
|
+
str << "\nh3. #{info[:name]}\n"
|
51
|
+
str << "\n#{info[:desc]}\n" if info[:desc]
|
52
|
+
str << "\nh5. Params\n"
|
53
|
+
if info[:params].nil?
|
54
|
+
str << "\n* _None_\n"
|
55
|
+
elsif info[:params].is_a?(Array)
|
56
|
+
str << "\n|_. Name |_. Type |_. Required |_. Description |\n"
|
57
|
+
info[:params].each do |param|
|
58
|
+
str << "| @#{param[:name]}@ | @#{param[:type]}@ | #{param[:required] ? 'Yes' : 'No'} | #{param[:desc]} |\n"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if res = info[:returns]
|
63
|
+
str << "\nh5. Result\n"
|
64
|
+
str << "\n* @#{res[:type]}@"
|
65
|
+
str << " - #{res[:desc]}" if res[:desc]
|
66
|
+
str << "\n"
|
67
|
+
else
|
68
|
+
str << "\nh5. Result\n"
|
69
|
+
str << "\n* @null@"
|
70
|
+
end
|
71
|
+
|
72
|
+
if examples = info[:examples]
|
73
|
+
str << "\nh5. Sample usage\n"
|
74
|
+
|
75
|
+
nice_json = lambda do |data|
|
76
|
+
JSON.pretty_unparse(data).gsub(/\n\n+/,"\n").gsub(/[{]\s+[}]/m, '{ }').gsub(/\[\s+\]/m, '[ ]')
|
77
|
+
end
|
78
|
+
examples.each do |ex|
|
79
|
+
str << "\n#{ex[:desc]}\n"
|
80
|
+
code = ex[:code]
|
81
|
+
if code.is_a?(String)
|
82
|
+
str << "\nbc. #{ex[:code]}\n"
|
83
|
+
elsif code.is_a?(Hash) && code.has_key?(:params) && (code.has_key?(:result) || code.has_key?(:error))
|
84
|
+
|
85
|
+
str << "\nbc. "
|
86
|
+
if code[:result] # ie. we expect success
|
87
|
+
unless JSONRPC2::Types.valid_params?(self, info[:name], code[:params])
|
88
|
+
raise "Invalid example params for #{info[:name]} / #{ex[:desc]}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
input = { 'jsonrpc' => 2.0, 'method' => info[:name], 'params' => code[:params], 'id' => 0 }
|
92
|
+
str << "--> #{nice_json.call(input)}\n"
|
93
|
+
|
94
|
+
if code[:error]
|
95
|
+
error = { 'jsonrpc' => 2.0, 'error' => code[:error], 'id' => 0 }
|
96
|
+
str << "<-- #{nice_json.call(error)}\n"
|
97
|
+
elsif code[:result]
|
98
|
+
unless JSONRPC2::Types.valid_result?(self, info[:name], code[:result])
|
99
|
+
raise "Invalid result example for #{info[:name]} / #{ex[:desc]}"
|
100
|
+
end
|
101
|
+
|
102
|
+
result = { 'jsonrpc' => 2.0, 'result' => code[:result], 'id' => 0 }
|
103
|
+
str << "<-- #{nice_json.call(result)}\n"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
str
|
110
|
+
end
|
111
|
+
# Gets method docs for method #name
|
112
|
+
def about_method(name)
|
113
|
+
@about[name.to_s]
|
114
|
+
end
|
115
|
+
# Returns documentation #section contents in textile
|
116
|
+
def to_textile_group(section)
|
117
|
+
list = @about.values.select { |info| info[:section] == section[:name] }
|
118
|
+
|
119
|
+
return nil if list.empty?
|
120
|
+
|
121
|
+
str = ''
|
122
|
+
|
123
|
+
list.sort_by { |info| info[:index] }.each do |info|
|
124
|
+
str << method_to_textile(info)
|
125
|
+
end
|
126
|
+
|
127
|
+
str
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|