sr-jimson 0.11.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.
@@ -0,0 +1,23 @@
1
+ module Sr::Jimson
2
+ class Client
3
+ module Error
4
+ class InvalidResponse < StandardError
5
+ def initialize()
6
+ super('Invalid or empty response from server.')
7
+ end
8
+ end
9
+
10
+ class InvalidJSON < StandardError
11
+ def initialize(json)
12
+ super("Couldn't parse JSON string received from server:\n#{json}")
13
+ end
14
+ end
15
+
16
+ class ServerError < StandardError
17
+ def initialize(code, message)
18
+ super("Server error #{code}: #{message}")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Sr::Jimson
2
+ module Handler
3
+
4
+ def jimson_default_methods
5
+ self.instance_methods.map(&:to_s) - Object.methods.map(&:to_s)
6
+ end
7
+
8
+ def jimson_expose(*methods)
9
+ @jimson_exposed_methods ||= []
10
+ @jimson_exposed_methods += methods.map(&:to_s)
11
+ end
12
+
13
+ def jimson_exclude(*methods)
14
+ @jimson_excluded_methods ||= []
15
+ @jimson_excluded_methods += methods.map(&:to_s)
16
+ end
17
+
18
+ def jimson_exposed_methods
19
+ @jimson_exposed_methods ||= []
20
+ @jimson_excluded_methods ||= []
21
+ (jimson_default_methods - @jimson_excluded_methods + @jimson_exposed_methods).sort
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Sr::Jimson
2
+ class Request
3
+
4
+ attr_accessor :method, :params, :id
5
+ def initialize(method, params, id = nil)
6
+ @method = method
7
+ @params = params
8
+ @id = id
9
+ end
10
+
11
+ def to_h
12
+ h = {
13
+ 'jsonrpc' => '2.0',
14
+ 'method' => @method
15
+ }
16
+ h.merge!('params' => @params) if !!@params && !params.empty?
17
+ h.merge!('id' => id)
18
+ end
19
+
20
+ def to_json(*a)
21
+ MultiJson.encode(self.to_h)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module Sr::Jimson
2
+ class Response
3
+ attr_accessor :result, :error, :id
4
+
5
+ def initialize(id)
6
+ @id = id
7
+ end
8
+
9
+ def to_h
10
+ h = {'jsonrpc' => '2.0'}
11
+ h.merge!('result' => @result) if !!@result
12
+ h.merge!('error' => @error) if !!@error
13
+ h.merge!('id' => @id)
14
+ end
15
+
16
+ def is_error?
17
+ !!@error
18
+ end
19
+
20
+ def succeeded?
21
+ !!@result
22
+ end
23
+
24
+ def populate!(data)
25
+ @error = data['error'] if !!data['error']
26
+ @result = data['result'] if !!data['result']
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ require 'sr/jimson/router/map'
2
+ require 'forwardable'
3
+
4
+ module Sr::Jimson
5
+ class Router
6
+ extend Forwardable
7
+
8
+ ROUTER_DELEGATORS = [
9
+ :handler_for_method, :root, :namespace,
10
+ :jimson_methods, :strip_method_namespace
11
+ ]
12
+
13
+ def_delegators :@map, *ROUTER_DELEGATORS
14
+
15
+ def initialize
16
+ @map = Map.new
17
+ end
18
+
19
+ def draw(&block)
20
+ @map.instance_eval(&block)
21
+ self
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ module Sr::Jimson
2
+ class Router
3
+
4
+ #
5
+ # Provides a DSL for routing method namespaces to handlers.
6
+ # Only handles root-level and non-nested namespaces, e.g. 'foo.bar' or 'foo'.
7
+ #
8
+ class Map
9
+
10
+ def initialize
11
+ @routes = {}
12
+ end
13
+
14
+ #
15
+ # Set the root handler, i.e. the handler used for a bare method like 'foo'
16
+ #
17
+ def root(handler)
18
+ handler = handler.new if handler.is_a?(Class)
19
+ @routes[''] = handler
20
+ end
21
+
22
+ #
23
+ # Define the handler for a namespace
24
+ #
25
+ def namespace(ns, handler = nil, &block)
26
+ if !!handler
27
+ handler = handler.new if handler.is_a?(Class)
28
+ @routes[ns.to_s] = handler
29
+ else
30
+ # passed a block for nested namespacing
31
+ map = Sr::Jimson::Router::Map.new
32
+ @routes[ns.to_s] = map
33
+ map.instance_eval(&block)
34
+ end
35
+ end
36
+
37
+ #
38
+ # Return the handler for a (possibly namespaced) method name
39
+ #
40
+ def handler_for_method(method)
41
+ parts = method.split('.')
42
+ ns = (method.index('.') == nil ? '' : parts.first)
43
+ handler = @routes[ns]
44
+ if handler.is_a?(Sr::Jimson::Router::Map)
45
+ return handler.handler_for_method(parts[1..-1].join('.'))
46
+ end
47
+ handler
48
+ end
49
+
50
+ #
51
+ # Strip off the namespace part of a method and return the bare method name
52
+ #
53
+ def strip_method_namespace(method)
54
+ method.split('.').last
55
+ end
56
+
57
+ #
58
+ # Return an array of all methods on handlers in the map, fully namespaced
59
+ #
60
+ def jimson_methods
61
+ arr = @routes.keys.map do |ns|
62
+ prefix = (ns == '' ? '' : "#{ns}.")
63
+ handler = @routes[ns]
64
+ if handler.is_a?(Sr::Jimson::Router::Map)
65
+ handler.jimson_methods
66
+ else
67
+ handler.class.jimson_exposed_methods.map { |method| prefix + method }
68
+ end
69
+ end
70
+ arr.flatten
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,224 @@
1
+ require 'rack'
2
+ require 'rack/request'
3
+ require 'rack/response'
4
+ require 'multi_json'
5
+ require 'sr/jimson/handler'
6
+ require 'sr/jimson/router'
7
+ require 'sr/jimson/server/error'
8
+
9
+ module Sr::Jimson
10
+ class Server
11
+
12
+ class System
13
+ extend Handler
14
+
15
+ def initialize(router)
16
+ @router = router
17
+ end
18
+
19
+ def listMethods
20
+ @router.jimson_methods
21
+ end
22
+
23
+ def isAlive
24
+ true
25
+ end
26
+ end
27
+
28
+ JSON_RPC_VERSION = '2.0'
29
+
30
+ attr_accessor :router, :host, :port, :show_errors, :opts
31
+
32
+ #
33
+ # Create a Server with routes defined
34
+ #
35
+ def self.with_routes(opts = {}, &block)
36
+ router = Router.new
37
+ router.send(:draw, &block)
38
+ self.new(router, opts)
39
+ end
40
+
41
+ #
42
+ # +router_or_handler+ is an instance of Sr::Jimson::Router or extends Sr::Jimson::Handler
43
+ #
44
+ # +opts+ may include:
45
+ # * :host - the hostname or ip to bind to
46
+ # * :port - the port to listen on
47
+ # * :server - the rack handler to use, e.g. 'webrick' or 'thin'
48
+ # * :show_errors - true or false, send backtraces in error responses?
49
+ #
50
+ # Remaining options are forwarded to the underlying Rack server.
51
+ #
52
+ def initialize(router_or_handler, opts = {})
53
+ if !router_or_handler.is_a?(Router)
54
+ # arg is a handler, wrap it in a Router
55
+ @router = Router.new
56
+ @router.root router_or_handler
57
+ else
58
+ # arg is a router
59
+ @router = router_or_handler
60
+ end
61
+ @router.namespace 'system', System.new(@router)
62
+
63
+ @host = opts.delete(:host) || '0.0.0.0'
64
+ @port = opts.delete(:port) || 8999
65
+ @show_errors = opts.delete(:show_errors) || false
66
+ @opts = opts
67
+ end
68
+
69
+ #
70
+ # Starts the server so it can process requests
71
+ #
72
+ def start
73
+ Rack::Server.start(opts.merge(
74
+ :app => self,
75
+ :Host => @host,
76
+ :Port => @port
77
+ ))
78
+ end
79
+
80
+ #
81
+ # Entry point for Rack
82
+ #
83
+ def call(env)
84
+ req = Rack::Request.new(env)
85
+ resp = Rack::Response.new
86
+ return resp.finish if !req.post?
87
+ resp.write process(req.body.read)
88
+ resp.finish
89
+ end
90
+
91
+ def process(content)
92
+ begin
93
+ request = parse_request(content)
94
+ if request.is_a?(Array)
95
+ raise Server::Error::InvalidRequest.new if request.empty?
96
+ response = request.map { |req| handle_request(req) }
97
+ else
98
+ response = handle_request(request)
99
+ end
100
+ rescue Server::Error::ParseError, Server::Error::InvalidRequest => e
101
+ response = error_response(e)
102
+ rescue Server::Error => e
103
+ response = error_response(e, request)
104
+ rescue StandardError, Exception => e
105
+ response = error_response(Server::Error::InternalError.new(e))
106
+ end
107
+
108
+ response.compact! if response.is_a?(Array)
109
+
110
+ return nil if response.nil? || (response.respond_to?(:empty?) && response.empty?)
111
+
112
+ MultiJson.encode(response)
113
+ end
114
+
115
+ def handle_request(request)
116
+ response = nil
117
+ begin
118
+ if !validate_request(request)
119
+ response = error_response(Server::Error::InvalidRequest.new)
120
+ else
121
+ response = create_response(request)
122
+ end
123
+ rescue Server::Error => e
124
+ response = error_response(e, request)
125
+ end
126
+
127
+ response
128
+ end
129
+
130
+ def validate_request(request)
131
+ required_keys = %w(jsonrpc method)
132
+ required_types = {
133
+ 'jsonrpc' => [String],
134
+ 'method' => [String],
135
+ 'params' => [Hash, Array],
136
+ 'id' => [String, Fixnum, Bignum, NilClass]
137
+ }
138
+
139
+ return false if !request.is_a?(Hash)
140
+
141
+ required_keys.each do |key|
142
+ return false if !request.has_key?(key)
143
+ end
144
+
145
+ required_types.each do |key, types|
146
+ return false if request.has_key?(key) && !types.any? { |type| request[key].is_a?(type) }
147
+ end
148
+
149
+ return false if request['jsonrpc'] != JSON_RPC_VERSION
150
+
151
+ true
152
+ end
153
+
154
+ def create_response(request)
155
+ method = request['method']
156
+ params = request['params']
157
+ result = dispatch_request(method, params)
158
+
159
+ response = success_response(request, result)
160
+
161
+ # A Notification is a Request object without an "id" member.
162
+ # The Server MUST NOT reply to a Notification, including those
163
+ # that are within a batch request.
164
+ response = nil if !request.has_key?('id')
165
+
166
+ return response
167
+
168
+ rescue Server::Error => e
169
+ raise e
170
+ rescue ArgumentError
171
+ raise Server::Error::InvalidParams.new
172
+ rescue Exception, StandardError => e
173
+ raise Server::Error::ApplicationError.new(e, @show_errors)
174
+ end
175
+
176
+ def dispatch_request(method, params)
177
+ method_name = method.to_s
178
+ handler = @router.handler_for_method(method_name)
179
+ method_name = @router.strip_method_namespace(method_name)
180
+
181
+ if handler.nil? \
182
+ || !handler.class.jimson_exposed_methods.include?(method_name) \
183
+ || !handler.respond_to?(method_name)
184
+ raise Server::Error::MethodNotFound.new(method)
185
+ end
186
+
187
+ if params.nil?
188
+ return handler.send(method_name)
189
+ elsif params.is_a?(Hash)
190
+ return handler.send(method_name, params)
191
+ else
192
+ return handler.send(method_name, *params)
193
+ end
194
+ end
195
+
196
+ def error_response(error, request = nil)
197
+ resp = {
198
+ 'jsonrpc' => JSON_RPC_VERSION,
199
+ 'error' => error.to_h,
200
+ }
201
+ if !!request && request.has_key?('id')
202
+ resp['id'] = request['id']
203
+ else
204
+ resp['id'] = nil
205
+ end
206
+
207
+ resp
208
+ end
209
+
210
+ def success_response(request, result)
211
+ {
212
+ 'jsonrpc' => JSON_RPC_VERSION,
213
+ 'result' => result,
214
+ 'id' => request['id']
215
+ }
216
+ end
217
+
218
+ def parse_request(post)
219
+ MultiJson.decode(post)
220
+ rescue
221
+ raise Server::Error::ParseError.new
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,66 @@
1
+ module Sr::Jimson
2
+ class Server
3
+ class Error < StandardError
4
+ attr_accessor :code, :message
5
+
6
+ def initialize(code, message)
7
+ @code = code
8
+ @message = message
9
+ super(message)
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ 'code' => @code,
15
+ 'message' => @message
16
+ }
17
+ end
18
+
19
+ class ParseError < Error
20
+ def initialize
21
+ super(-32700, 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.')
22
+ end
23
+ end
24
+
25
+ class InvalidRequest < Error
26
+ def initialize
27
+ super(-32600, 'The JSON sent is not a valid Request object.')
28
+ end
29
+ end
30
+
31
+ class MethodNotFound < Error
32
+ def initialize(method)
33
+ super(-32601, "Method '#{method}' not found.")
34
+ end
35
+ end
36
+
37
+ class InvalidParams < Error
38
+ def initialize
39
+ super(-32602, 'Invalid method parameter(s).')
40
+ end
41
+ end
42
+
43
+ class InternalError < Error
44
+ def initialize(e)
45
+ super(-32603, "Internal server error: #{e}")
46
+ end
47
+ end
48
+
49
+ class ApplicationError < Error
50
+ def initialize(err, show_error = false)
51
+ msg = "Server application error"
52
+ msg += ': ' + err.message + ' at ' + err.backtrace.first if show_error
53
+ super(-32099, msg)
54
+ end
55
+ end
56
+
57
+ CODES = {
58
+ -32700 => ParseError,
59
+ -32600 => InvalidRequest,
60
+ -32601 => MethodNotFound,
61
+ -32602 => InvalidParams,
62
+ -32603 => InternalError
63
+ }
64
+ end
65
+ end
66
+ end