activerpc 0.1.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/MIT-LICENSE +20 -0
- data/README.md +95 -0
- data/Rakefile +32 -0
- data/app/controllers/active_rpc/rpc_controller.rb +56 -0
- data/config/routes.rb +3 -0
- data/lib/active_rpc/configuration.rb +5 -0
- data/lib/active_rpc/engine.rb +5 -0
- data/lib/active_rpc/errors.rb +77 -0
- data/lib/active_rpc/operation.rb +21 -0
- data/lib/active_rpc/request.rb +19 -0
- data/lib/active_rpc/response.rb +27 -0
- data/lib/active_rpc/version.rb +3 -0
- data/lib/active_rpc.rb +24 -0
- data/lib/activerpc.rb +1 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 10ba3417ba62f8c6113543c46d4fc9f869a85c18a7fdc91160fc9a918043dc64
|
4
|
+
data.tar.gz: d59077929216fca8f7ab8067cfe643d7c72fdeca5f0aa359db955bd932a098d6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2abecebe8eea82db866c8e51789956fde73c34af67a51355e7c919b66259bd3e8eb036b920b4749f8e30c857f057aaad646d72956db9b2c1f987293c15204d51
|
7
|
+
data.tar.gz: 1a77d1a284b54598eab87f67c2bca9007946b059edbd47a94362c0691b2586a7769b74b6283ef5007541454d5269b8a2e644d52d6f6ebc6dfab75cbcb4b6e1a1
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2019 John Maxwell
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# ActiveRpc
|
2
|
+
Rails-native JSONRPC 2.0 Server Library
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
ActiveRpc is a simple mechanism to service JSONRPC requests from within
|
6
|
+
a Rails installation without sacrificing your existing Rails workflow.
|
7
|
+
It supports custom before/after/around actions on the RPC controller,
|
8
|
+
and insists upon validation of payloads for inbound method invocations,
|
9
|
+
using standard Rails tools and toolchains.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'activerpc'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
```bash
|
20
|
+
$ bundle
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
```bash
|
25
|
+
$ gem install activerpc
|
26
|
+
```
|
27
|
+
|
28
|
+
Once you've installed the gem, you can add an initializer to configure things
|
29
|
+
such as controller lifecycle actions.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
ActiveRpc.configure do |config|
|
33
|
+
config.before_action = MyBeforeAction # something that responds to `.before`
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
The JSONRPC spec supports two kinds of parameters being passed to a method. We
|
39
|
+
feel strongly that only the Hash/Object form should be used rather than the
|
40
|
+
Array/positional form. The reason for this is that it is significantly less
|
41
|
+
brittle as changes happen over time, and much easier to reason about what the job
|
42
|
+
is whilst in flight.
|
43
|
+
|
44
|
+
Further than that, we also believe that all invocations should be subject to
|
45
|
+
suitable validation before processing. So...
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class MyRpcMethod < ActiveRpc::Operation
|
49
|
+
operation_name 'rpc.method.name'
|
50
|
+
fields :name, :age
|
51
|
+
|
52
|
+
validates :name, presence: true
|
53
|
+
validates :age, presence: true, numericality: true, only_integer: true, greater_than: 18
|
54
|
+
|
55
|
+
def call
|
56
|
+
"Hello #{name}, you are #{age} years old."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
ActiveRpc will instantiate your method class, and if it passes validation
|
62
|
+
it will then call `#call` on it, returning the outcome of that method as the
|
63
|
+
JSONRPC result field.
|
64
|
+
|
65
|
+
JSONRPC supports returning application-semantic errors, to trigger one of those
|
66
|
+
you should raise an error in your `#call` method.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
def call
|
70
|
+
raise OperationFailure.new(code: my_code, message: 'no thanks') if name == 'Nigel'
|
71
|
+
|
72
|
+
"Hello #{name}, you are #{age} years old."
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
Please note, in development mode you might have issues with the operation classes
|
77
|
+
being autoloaded. The new bootloader in Rails 6 will solve this, but until then you
|
78
|
+
can add something similar to the below to an initialiser.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# Require all operatons in app/operations as dependencies for autoloading
|
82
|
+
Rails.application.config.to_prepare do
|
83
|
+
Dir[Rails.root.join('app', 'operations', '*.rb')].each do |file|
|
84
|
+
require_dependency(file)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
## Contributing
|
90
|
+
Fork the project, make some changes and open a PR! For major changes of direction
|
91
|
+
please talk to us first, we can't guarantee to merge PRs that don't reflect the
|
92
|
+
usage we have of this project.
|
93
|
+
|
94
|
+
## License
|
95
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Activerpc'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ActiveRpc
|
2
|
+
class RpcController < ActionController::Base
|
3
|
+
after_action ActiveRpc.config.after_action
|
4
|
+
around_action ActiveRpc.config.around_action
|
5
|
+
before_action ActiveRpc.config.before_action
|
6
|
+
|
7
|
+
def create
|
8
|
+
res = nil
|
9
|
+
begin
|
10
|
+
res = case body
|
11
|
+
when Array then body.map(&method(:process_item)).map(&:to_hash)
|
12
|
+
when Hash then process_item(body).to_hash
|
13
|
+
end
|
14
|
+
rescue JSON::ParserError => ex
|
15
|
+
res = ActiveRpc::Response.new do |r|
|
16
|
+
r.error = Errors::ParseError.new(message: ex.to_s)
|
17
|
+
end.to_hash
|
18
|
+
end
|
19
|
+
|
20
|
+
render status: :ok, json: res
|
21
|
+
end
|
22
|
+
|
23
|
+
private def process_item(item)
|
24
|
+
req = ActiveRpc::Request.new(id: item['id'], method: item['method'], params: item['params'])
|
25
|
+
res = ActiveRpc::Response.from_request(req)
|
26
|
+
|
27
|
+
begin
|
28
|
+
raise TypeError, 'invalid JSON-RPC request' unless req.valid?
|
29
|
+
|
30
|
+
executor = ActiveRpc.get_executor(req.method)
|
31
|
+
raise NoMethodError, "undefined operation `#{req.method}'" unless executor
|
32
|
+
|
33
|
+
ex = executor.new(req.params)
|
34
|
+
raise ArgumentError, 'invalid payload' unless ex.valid?
|
35
|
+
res.result = ex.call
|
36
|
+
rescue TypeError => ex
|
37
|
+
res.error = Errors::ClientError.new(message: ex.to_s)
|
38
|
+
rescue NoMethodError => ex
|
39
|
+
res.error = Errors::NoMethodError.new(message: ex.to_s)
|
40
|
+
rescue ArgumentError => ex
|
41
|
+
res.error = Errors::ArgumentError.new(message: ex.to_s)
|
42
|
+
rescue OperationFailure => ex
|
43
|
+
res.error = ex.rpc_error
|
44
|
+
# todo: handle rpc-level errors
|
45
|
+
rescue => ex
|
46
|
+
res.error = Errors::InternalError.new(message: ex.to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
res
|
50
|
+
end
|
51
|
+
|
52
|
+
private def body
|
53
|
+
@body ||= JSON.load(request.body.read)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module ActiveRpc
|
2
|
+
class OperationFailure < RuntimeError
|
3
|
+
attr_reader :code, :message, :data
|
4
|
+
|
5
|
+
def initialize(code:, message: nil, data: nil)
|
6
|
+
@code = code
|
7
|
+
@message = message
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def rpc_error
|
12
|
+
Errors::OperationFailure.new(code: code, message: message, data: data)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Error
|
17
|
+
attr_accessor :message, :data
|
18
|
+
attr_reader :code
|
19
|
+
def initialize(code:, data: nil, message: nil)
|
20
|
+
@message = message
|
21
|
+
@code = code
|
22
|
+
@data = data
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
{
|
27
|
+
'code' => code,
|
28
|
+
'data' => data,
|
29
|
+
'message' => message
|
30
|
+
}.compact
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Errors
|
35
|
+
class ClientError < Error
|
36
|
+
def initialize(message:)
|
37
|
+
super(message: message, code: -32600)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class NoMethodError < Error
|
42
|
+
def initialize(message:)
|
43
|
+
super(message: message, code: -32601)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ParseError < Error
|
48
|
+
def initialize(message:)
|
49
|
+
super(message: message, code: -32700)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ArgumentError < Error
|
54
|
+
def initialize(message:)
|
55
|
+
super(message: message, code: -32602)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class InternalError < Error
|
60
|
+
def initialize(message:)
|
61
|
+
super(message: message, code: -32603)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ServerError < Error
|
66
|
+
def initialize(message:)
|
67
|
+
super(message: message, code: -32000)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class OperationFailure < Error
|
72
|
+
def initialize(code:, message:, data: nil)
|
73
|
+
super(message: message, code: code, data: data)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveRpc
|
2
|
+
class Operation
|
3
|
+
include ActiveModel::Model
|
4
|
+
include ActiveModel::Validations
|
5
|
+
include ActiveModel::Validations::Callbacks
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def fields(*names)
|
9
|
+
attr_accessor(*names)
|
10
|
+
end
|
11
|
+
|
12
|
+
def operation_name(value)
|
13
|
+
ActiveRpc.operation_map[value] = self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
raise 'Must Implement'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveRpc
|
2
|
+
class Request
|
3
|
+
attr_reader :id, :method, :params
|
4
|
+
|
5
|
+
def initialize(id:, method:, params:)
|
6
|
+
@id, @method, @params = id, method, params
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
[
|
11
|
+
id.present?,
|
12
|
+
id.is_a?(String) || id.is_a?(Integer),
|
13
|
+
method.present?,
|
14
|
+
method.is_a?(String),
|
15
|
+
params.nil? || params.is_a?(Array) || params.is_a?(Hash),
|
16
|
+
].all?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ActiveRpc
|
2
|
+
class Response
|
3
|
+
attr_accessor :error, :result
|
4
|
+
attr_reader :id
|
5
|
+
|
6
|
+
def initialize(id:)
|
7
|
+
@id = id
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash
|
11
|
+
{
|
12
|
+
'jsonrpc' => '2.0',
|
13
|
+
'id' => id
|
14
|
+
}.tap do |h|
|
15
|
+
if error
|
16
|
+
h['error'] = error.to_hash
|
17
|
+
else
|
18
|
+
h['result'] = result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.from_request(req)
|
24
|
+
new(id: req.id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/active_rpc.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_rpc/engine'
|
2
|
+
require 'active_rpc/errors'
|
3
|
+
require 'active_rpc/configuration'
|
4
|
+
require 'active_rpc/operation'
|
5
|
+
require 'active_rpc/request'
|
6
|
+
require 'active_rpc/response'
|
7
|
+
|
8
|
+
module ActiveRpc
|
9
|
+
cattr_accessor :config
|
10
|
+
|
11
|
+
def self.configure
|
12
|
+
self.config ||= Configuration.new
|
13
|
+
yield(config)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def self.get_executor(method)
|
18
|
+
operation_map[method]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.operation_map
|
22
|
+
@operation_map ||= {}
|
23
|
+
end
|
24
|
+
end
|
data/lib/activerpc.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "active_rpc"
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerpc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Maxwell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.2.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 4.2.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: sqlite3
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.3'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.3.6
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.3'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.3.6
|
53
|
+
description: Simple, dependable JSONRpc 2.0 Server library for Rails
|
54
|
+
email:
|
55
|
+
- john@musicglue.com
|
56
|
+
executables: []
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- MIT-LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- app/controllers/active_rpc/rpc_controller.rb
|
64
|
+
- config/routes.rb
|
65
|
+
- lib/active_rpc.rb
|
66
|
+
- lib/active_rpc/configuration.rb
|
67
|
+
- lib/active_rpc/engine.rb
|
68
|
+
- lib/active_rpc/errors.rb
|
69
|
+
- lib/active_rpc/operation.rb
|
70
|
+
- lib/active_rpc/request.rb
|
71
|
+
- lib/active_rpc/response.rb
|
72
|
+
- lib/active_rpc/version.rb
|
73
|
+
- lib/activerpc.rb
|
74
|
+
homepage: https://github.com/musicglue/activerpc
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.7.6
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: JSONRpc 2.0 Server library for Rails
|
98
|
+
test_files: []
|