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 +7 -0
- data/lib/metarpc/callable.rb +126 -0
- data/lib/metarpc/interpreter.rb +96 -0
- data/lib/metarpc/rpc_error.rb +41 -0
- data/lib/metarpc.rb +6 -0
- metadata +94 -0
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
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: []
|