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 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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Client
4
+ ANSWER = 42
5
+ end
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clamo
4
+ VERSION = "0.1.0"
5
+ end
data/lib/clamo.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "clamo/version"
4
+ require_relative "clamo/jsonrpc"
5
+ require_relative "clamo/server"
6
+
7
+ module Clamo
8
+ # class Error < StandardError; end
9
+ end
data/sig/clamo.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Clamo
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []