clamo 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/.rubocop.yml +18 -0
- data/README.md +29 -0
- data/Rakefile +12 -0
- data/lib/clamo/client.rb +5 -0
- data/lib/clamo/jsonrpc.rb +101 -0
- data/lib/clamo/server.rb +123 -0
- data/lib/clamo/version.rb +5 -0
- data/lib/clamo.rb +9 -0
- data/sig/clamo.rbs +4 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: debb0ed8e3b749ff9e0213c9c97e821668f45d179c7e72d1f4a1a506cd710b75
|
4
|
+
data.tar.gz: dcd24d91533bfe8c50bb5456745b342fc35cab1d38714b3552df36e2fdf34df0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e2df3792feceedc6c98c92b29ddb25e67db2a57972ee08b20e71b13327b4c1296269ef7c873b7e9181aa042d03a2ab7849dde8498750088d2419789a1e33254
|
7
|
+
data.tar.gz: c49ce4742815d16fab1b9cbcf30f621ae2429b515bab8e245f43c069a9be70bbbfe519b6cd8868cf5275c29dd3d1f7e2aa3f54f57e1973b2557c3203871c3614
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
TargetRubyVersion: 3.0
|
4
|
+
|
5
|
+
Metrics/MethodLength:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
Style/Documentation:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Style/MultilineBlockChain:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Style/StringLiterals:
|
15
|
+
EnforcedStyle: double_quotes
|
16
|
+
|
17
|
+
Style/StringLiteralsInInterpolation:
|
18
|
+
EnforcedStyle: double_quotes
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Clamo
|
2
|
+
|
3
|
+
JSON-RPC protocol toolkit for Ruby.
|
4
|
+
|
5
|
+
Consume, Serve or test JSON-RPC endpoints with Clamo.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install the gem and add to the application's Gemfile by executing:
|
10
|
+
|
11
|
+
$ bundle add clamo
|
12
|
+
|
13
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
14
|
+
|
15
|
+
$ gem install clamo
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
TODO: Write usage instructions here
|
20
|
+
|
21
|
+
## Development
|
22
|
+
|
23
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
24
|
+
|
25
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rubakas/clamo
|
data/Rakefile
ADDED
data/lib/clamo/client.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clamo
|
4
|
+
module JSONRPC
|
5
|
+
PROTOCOL_VERSION_PRAGMA = { jsonrpc: "2.0" }.freeze
|
6
|
+
|
7
|
+
module ProtocolErrors
|
8
|
+
ErrorDescriptor = Data.define(:code, :message)
|
9
|
+
SERVER_ERROR_CODE_RANGE = ((-32_099)..(-32_000))
|
10
|
+
|
11
|
+
PARSE_ERROR = ErrorDescriptor.new(code: -32_700, message: "Parse error")
|
12
|
+
INVALID_REQUEST = ErrorDescriptor.new(code: -32_600, message: "Invalid request")
|
13
|
+
METHOD_NOT_FOUND = ErrorDescriptor.new(code: -32_601, message: "Method not found")
|
14
|
+
INVALID_PARAMS = ErrorDescriptor.new(code: -32_602, message: "Invalid params")
|
15
|
+
INTERNAL_ERROR = ErrorDescriptor.new(code: -32_603, message: "Internal error")
|
16
|
+
SERVER_ERROR = ErrorDescriptor.new(code: -32_000, message: "Server error")
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def proper_pragma?(request)
|
21
|
+
request["jsonrpc"] == "2.0"
|
22
|
+
end
|
23
|
+
|
24
|
+
def proper_method?(request)
|
25
|
+
request["method"].is_a?(String)
|
26
|
+
end
|
27
|
+
|
28
|
+
def proper_id_if_any?(request)
|
29
|
+
if request.key?("id")
|
30
|
+
request["id"].is_a?(String) ||
|
31
|
+
request["id"].is_a?(Integer) ||
|
32
|
+
request["id"].is_a?(NilClass)
|
33
|
+
else
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def proper_params_if_any?(request)
|
39
|
+
if request.key?("params")
|
40
|
+
request["params"].is_a?(Array)
|
41
|
+
else
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid_request?(request)
|
47
|
+
request.is_a?(Hash) &&
|
48
|
+
proper_pragma?(request) &&
|
49
|
+
proper_method?(request) &&
|
50
|
+
proper_id_if_any?(request) &&
|
51
|
+
proper_params_if_any?(request)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_request **opts
|
55
|
+
# raise if no method present
|
56
|
+
# raise if params present, but not an array
|
57
|
+
{ jsonrpc: "2.0",
|
58
|
+
method: opts[:method] }
|
59
|
+
.merge({ params: opts[:params] })
|
60
|
+
.merge(opts.key?(:id) ? { id: opts[:id] } : {})
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_result_response(id:, result:)
|
64
|
+
{}.merge(PROTOCOL_VERSION_PRAGMA)
|
65
|
+
.merge({ result: result })
|
66
|
+
.merge({ id: id })
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_error_response **opts
|
70
|
+
# raise if no error code present
|
71
|
+
# raise if no error message present
|
72
|
+
{ jsonrpc: "2.0",
|
73
|
+
id: opts[:id],
|
74
|
+
error: {
|
75
|
+
code: opts.dig(:error, :code),
|
76
|
+
message: opts.dig(:error, :message),
|
77
|
+
data: opts.dig(:error, :data)
|
78
|
+
}.reject { |k, _| k == :data && !opts[:error].key?(:data) } }
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_error_response_from **opts
|
82
|
+
# raise unless opts[:descriptor]
|
83
|
+
opts.merge(
|
84
|
+
{ error:
|
85
|
+
{ code: opts[:descriptor].code,
|
86
|
+
message: opts[:descriptor].message } }
|
87
|
+
)
|
88
|
+
.then { |hash| build_error_response(**hash) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_error_response_parse_error **opts
|
92
|
+
opts.merge(
|
93
|
+
{ error:
|
94
|
+
{ code: ProtocolErrors::PARSE_ERROR.code,
|
95
|
+
message: ProtocolErrors::PARSE_ERROR.message } }
|
96
|
+
)
|
97
|
+
.then { |hash| build_error_response(**hash) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/clamo/server.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Clamo
|
6
|
+
module Server
|
7
|
+
class << self
|
8
|
+
# Clamo::Server.unparsed_dispatch_to_object(
|
9
|
+
# request: request_body,
|
10
|
+
# object: MyModule
|
11
|
+
# )
|
12
|
+
def unparsed_dispatch_to_object(request:, object:, **opts)
|
13
|
+
# TODO: raise unless object is present?
|
14
|
+
begin
|
15
|
+
parsed = JSON.parse(request)
|
16
|
+
rescue JSON::JSONError # TODO: any error
|
17
|
+
return JSONRPC.build_error_response_parse_error
|
18
|
+
end
|
19
|
+
|
20
|
+
parsed_dispatch_to_object(request: parsed, object: object, **opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parsed_dispatch_to_object(request:, object:, **opts)
|
24
|
+
response_for(request: request, object: object, **opts) do |method, params|
|
25
|
+
dispatch_to_ruby(
|
26
|
+
object: object,
|
27
|
+
method: method,
|
28
|
+
params: params # consider splating
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_known?(object:, method:)
|
34
|
+
(object.public_methods - Object.methods)
|
35
|
+
.map(&:to_sym)
|
36
|
+
.include?(method.to_sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
def dispatch_to_ruby(object:, method:, params:)
|
40
|
+
case params
|
41
|
+
when Array
|
42
|
+
object.send method.to_sym, *params
|
43
|
+
when Hash
|
44
|
+
object.send method.to_sym, **params
|
45
|
+
when NilClass
|
46
|
+
object.send method.to_sym
|
47
|
+
else
|
48
|
+
# TODO: raise
|
49
|
+
raise "WTF"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def response_for(request:, object:, **opts, &block)
|
54
|
+
case request
|
55
|
+
when Array # batch request
|
56
|
+
Parallel.map(request, **opts) do |item|
|
57
|
+
response_for_single_request(
|
58
|
+
request: item,
|
59
|
+
object: object,
|
60
|
+
block: block
|
61
|
+
)
|
62
|
+
end.compact
|
63
|
+
when Hash # single request
|
64
|
+
response_for_single_request(
|
65
|
+
request: request,
|
66
|
+
object: object,
|
67
|
+
block: block
|
68
|
+
)
|
69
|
+
else
|
70
|
+
JSONRPC.build_error_response_from(
|
71
|
+
id: nil,
|
72
|
+
descriptor: JSONRPC::ProtocolErrors::INVALID_REQUEST
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def yield_to_execution(block:, method:, params:)
|
78
|
+
block.yield method, params
|
79
|
+
end
|
80
|
+
|
81
|
+
def response_for_single_request(request:, object:, block:)
|
82
|
+
unless JSONRPC.valid_request?(request)
|
83
|
+
return JSONRPC.build_error_response_from(
|
84
|
+
id: request["id"],
|
85
|
+
descriptor: JSONRPC::ProtocolErrors::INVALID_REQUEST
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
unless request.key?("id") # notification - no result needed
|
90
|
+
# TODO: block.call off the current thread
|
91
|
+
Thread.new do
|
92
|
+
yield_to_execution(
|
93
|
+
block: block,
|
94
|
+
method: request["method"],
|
95
|
+
params: request["params"]
|
96
|
+
)
|
97
|
+
rescue StandardError
|
98
|
+
# TODO: add exception handler
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
return nil
|
103
|
+
end
|
104
|
+
|
105
|
+
unless method_known?(object: object, method: request["method"])
|
106
|
+
return JSONRPC.build_error_response_from(
|
107
|
+
id: request["id"],
|
108
|
+
descriptor: JSONRPC::ProtocolErrors::METHOD_NOT_FOUND
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
JSONRPC.build_result_response(
|
113
|
+
id: request["id"],
|
114
|
+
result: yield_to_execution(
|
115
|
+
block: block,
|
116
|
+
method: request["method"],
|
117
|
+
params: request["params"]
|
118
|
+
)
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/clamo.rb
ADDED
data/sig/clamo.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: clamo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andriy Tyurnikov
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parallel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.24.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.24.0
|
27
|
+
description: JSON-RPC Client/Server tooling for Ruby
|
28
|
+
email:
|
29
|
+
- Andriy.Tyurnikov@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".rubocop.yml"
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/clamo.rb
|
38
|
+
- lib/clamo/client.rb
|
39
|
+
- lib/clamo/jsonrpc.rb
|
40
|
+
- lib/clamo/server.rb
|
41
|
+
- lib/clamo/version.rb
|
42
|
+
- sig/clamo.rbs
|
43
|
+
homepage: https://github.com/andriytyurnikov/clamo
|
44
|
+
licenses: []
|
45
|
+
metadata:
|
46
|
+
homepage_uri: https://github.com/andriytyurnikov/clamo
|
47
|
+
source_code_uri: https://github.com/andriytyurnikov/clamo
|
48
|
+
changelog_uri: https://github.com/andriytyurnikov/clamo
|
49
|
+
rubygems_mfa_required: 'false'
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 3.0.0
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubygems_version: 3.5.9
|
66
|
+
signing_key:
|
67
|
+
specification_version: 4
|
68
|
+
summary: JSON-RPC Client/Server tooling for Ruby
|
69
|
+
test_files: []
|