metarpc 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 298f8b1a42cd14096c6aea8b808c6b90fd04fdc2fec1c2a9999d4f91c1195582
4
+ data.tar.gz: d4ead54bba04fe30a8016c572d4871eecc6a1a5fbce8d6e0a4062dd9dd6931da
5
+ SHA512:
6
+ metadata.gz: 227de707133d3ebfbf4db2184ff7ae50162b8c395f56544bd7c0fbcdda5accec9a8fb5e8a457bfd5a85f0c70b487686286cd78ab1556c37df00f921a14b20cfb
7
+ data.tar.gz: 22de052bee3742e3495b47edfc68f79e0e916f4e00f52fc5db1749459230cacaeffee3618b1f097b6bb659894ea533914dd9209428db59bdaf7d7955147df847
@@ -0,0 +1,126 @@
1
+ module MetaRPC
2
+ # Module used to describe callable JSON RPC methods
3
+ module Callable
4
+ def self.included(klass)
5
+ super
6
+ klass.send(:extend, ClassMethods)
7
+ end
8
+
9
+ def json_rpc_error(code)
10
+ raise RPCError.new(code), "Failed to call JSON RPC method : #{code}"
11
+ end
12
+
13
+ def validate_item(item, contract_type)
14
+ case contract_type
15
+ when Hash
16
+ validate_hash(item, contract_type)
17
+ when Array
18
+ validate_array(item, contract_type)
19
+ when Class
20
+ raise json_rpc_error(:invalid_params) unless item.is_a?(contract_type)
21
+ when String
22
+ klass, nullable, spread = if contract_type.end_with?('!')
23
+ [contract_type.delete('!').constantize, true, false]
24
+
25
+ elsif contract_type.end_with?('!...')
26
+ [contract_type.delete('!...').constantize, true, true]
27
+
28
+ elsif contract_type.end_with?('...')
29
+ [contract_type.delete('...').constantize, false, true]
30
+
31
+ else
32
+ [contract_type.constantize, false, false]
33
+ end
34
+
35
+ if spread
36
+ raise json_rpc_error(:invalid_params) unless item.is_a?(Array)
37
+
38
+ item.each do |sub_item|
39
+ next if sub_item.nil? && nullable
40
+ raise json_rpc_error(:invalid_params) unless sub_item.present? && sub_item.is_a?(klass)
41
+ end
42
+ else
43
+ return if item.nil? && nullable
44
+ raise json_rpc_error(:invalid_params) unless item.present? && item.is_a?(klass)
45
+ end
46
+ else
47
+ raise json_rpc_error(:internal_error)
48
+ end
49
+ rescue NameError
50
+ raise json_rpc_error(:internal_error)
51
+ end
52
+
53
+ def validate_array(args, contract)
54
+ contract.each_with_index do |contract_type, i|
55
+ validate_item(args[i], contract_type)
56
+ end
57
+ end
58
+
59
+ def validate_hash(args, contract)
60
+ contract.each do |arg_name, contract_type|
61
+ raise json_rpc_error(:invalid_params) unless args.key?(arg_name)
62
+
63
+ validate_item(args[arg_name], contract_type)
64
+ end
65
+ end
66
+
67
+ # Injected class methods
68
+ module ClassMethods
69
+ def json_rpc_methods
70
+ @__json_rpc_hooked_methods
71
+ end
72
+
73
+ def handle_json_callable(method_name)
74
+ old_method = instance_method(method_name)
75
+ return if (@__json_rpc_hooked_methods ||= {}).key?(method_name)
76
+
77
+ @__ignoring_added_methods = true
78
+ params_contract = @__json_rpc_method_params
79
+ error_handler = @__on_json_rpc_error
80
+
81
+ define_method method_name do |*args, &block|
82
+ case params_contract
83
+ when nil
84
+ raise json_rpc_error(:invalid_params) unless args.empty?
85
+ when Array
86
+ validate_item(args, params_contract)
87
+ when Hash
88
+ validate_item(args[0], params_contract)
89
+ else
90
+ raise json_rpc_error(:internal_error)
91
+ end
92
+
93
+ old_method.bind(self).call(*args, &block)
94
+ rescue RPCError => err
95
+ raise err
96
+
97
+ rescue StandardError => err
98
+ error_handler&.call(err)
99
+ raise json_rpc_error(:server_error)
100
+ end
101
+
102
+ @__ignoring_added_methods = false
103
+ @__json_rpc_hooked_methods[method_name] = {
104
+ description: @__json_rpc_method_description,
105
+ params: @__json_rpc_method_params
106
+ }
107
+ end
108
+
109
+ def on_json_rpc_error(&func)
110
+ @__on_json_rpc_error = func
111
+ end
112
+
113
+ def json_rpc_method(description, params: nil)
114
+ @__next_method_is_json_rpc = true
115
+ @__json_rpc_method_description = description
116
+ @__json_rpc_method_params = params
117
+ end
118
+
119
+ def method_added(method_name)
120
+ super
121
+ handle_json_callable(method_name) if @__next_method_is_json_rpc && !@__ignoring_added_methods
122
+ @__next_method_is_json_rpc = false
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,96 @@
1
+ module MetaRPC
2
+ # Used to interpret RPC requests and execute them securely
3
+ class Interpreter
4
+ attr_reader :input
5
+
6
+ def initialize(raw_request)
7
+ raw_input = JSON.parse(raw_request)
8
+
9
+ @input = if raw_input.is_a?(Array)
10
+ raw_input.map(&method(:validate_json_rpc_call))
11
+ else
12
+ validate_json_rpc_call(raw_input)
13
+ end
14
+ rescue JSON::ParserError
15
+ raise_error(:parse_error)
16
+ end
17
+
18
+ def execute(executor)
19
+ return input.map { |inp| execute_method(executor, inp) } if input.is_a?(Array)
20
+
21
+ execute_method(executor, input)
22
+ end
23
+
24
+ def execute_method(executor, inp)
25
+ raise_error(:method_not_found) unless executor.class.json_rpc_methods.key?(inp[:method])
26
+
27
+ result = if inp[:params].is_a?(Array)
28
+ executor.public_send(inp[:method], *inp[:params])
29
+ elsif inp[:params].is_a?(Hash)
30
+ executor.public_send(inp[:method], inp[:params])
31
+ elsif inp[:params].nil?
32
+ executor.public_send(inp[:method])
33
+ else
34
+ raise_error(:internal_error)
35
+ end
36
+
37
+ {
38
+ jsonrpc: '2.0',
39
+ result: result,
40
+ id: inp[:id]
41
+ }
42
+ rescue RPCError => err
43
+ {
44
+ jsonrpc: '2.0',
45
+ error: err.to_h,
46
+ id: inp[:id]
47
+ }
48
+ end
49
+
50
+ def validate_json_rpc_call(item)
51
+ # Containing the required key
52
+ valid = item.key?('jsonrpc') && item.key?('method')
53
+
54
+ # Does not contain unknown keys
55
+ valid &&= (item.keys - %w[jsonrpc method params id]).empty?
56
+
57
+ # Defining the correct JSONRPC version
58
+ valid &&= item['jsonrpc'] == '2.0'
59
+
60
+ # Defining a correct ID
61
+ valid &&= valid_id(item['id'])
62
+
63
+ # Defining a correct params object
64
+ valid &&= valid_params(item['params'])
65
+
66
+ # Defining a correct method name
67
+ valid &&= valid_method_name(item['method'])
68
+
69
+ raise_error(:invalid_request) unless valid
70
+
71
+ item.deep_symbolize_keys.tap do |h|
72
+ h[:method] = h[:method].to_sym
73
+ end
74
+ end
75
+
76
+ def valid_id(id)
77
+ return true if id.nil?
78
+
79
+ id.is_a?(String) || id.nil? || id.is_a?(Integer) || id.is_a?(Float)
80
+ end
81
+
82
+ def valid_params(params)
83
+ return true if params.nil?
84
+
85
+ params.is_a?(Array) || params.is_a?(Hash)
86
+ end
87
+
88
+ def valid_method_name(method)
89
+ method.is_a?(String) && !method.start_with?('rpc.')
90
+ end
91
+
92
+ def raise_error(code)
93
+ raise RPCError.new(code), 'Error on interpreter'
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,41 @@
1
+ module MetaRPC
2
+ # RPC possible errors
3
+ class RPCError < StandardError
4
+ attr_reader :code
5
+
6
+ CODE_TO_MESSAGE = {
7
+ parse_error: {
8
+ code: -32_700,
9
+ message: 'Invalid JSON was received by the server.'
10
+ },
11
+ invalid_request: {
12
+ code: -32_600,
13
+ message: 'The JSON sent is not a valid Request object.'
14
+ },
15
+ method_not_found: {
16
+ code: -32_601,
17
+ message: 'The method does not exist / is not available.'
18
+ },
19
+ invalid_params: {
20
+ code: -32_602,
21
+ message: 'Invalid method parameter(s).'
22
+ },
23
+ internal_error: {
24
+ code: -32_603,
25
+ message: 'Internal JSON-RPC error.'
26
+ },
27
+ server_error: {
28
+ code: -32_000,
29
+ message: 'Server error occurred.'
30
+ }
31
+ }.freeze
32
+
33
+ def initialize(code)
34
+ @code = code
35
+ end
36
+
37
+ def to_h
38
+ CODE_TO_MESSAGE[code]
39
+ end
40
+ end
41
+ end
data/lib/metarpc.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'json'
2
+ require 'active_support/all'
3
+
4
+ require_relative 'metarpc/rpc_error'
5
+ require_relative 'metarpc/callable'
6
+ require_relative 'metarpc/interpreter'
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: metarpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Thesaurio Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '5'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">"
28
+ - !ruby/object:Gem::Version
29
+ version: '5'
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rspec
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.8'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.8'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rubocop
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.63.1
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.63.1
61
+ description: Build a RPC API with ease
62
+ email: hello@thesaur.io
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - lib/metarpc.rb
68
+ - lib/metarpc/callable.rb
69
+ - lib/metarpc/interpreter.rb
70
+ - lib/metarpc/rpc_error.rb
71
+ homepage: http://rubygems.org/gems/hola
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 3.0.1
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: A classy way of handling RPC calls in Ruby
94
+ test_files: []