clamo 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: debb0ed8e3b749ff9e0213c9c97e821668f45d179c7e72d1f4a1a506cd710b75
4
- data.tar.gz: dcd24d91533bfe8c50bb5456745b342fc35cab1d38714b3552df36e2fdf34df0
3
+ metadata.gz: 103be8c6254e2ed646ebe13671e38dfea1af22f94bd8c082dc11ef6efe287260
4
+ data.tar.gz: 2094ae4b5697e41421d6014345ba34205a1db1b512907e341e66fe90b7d1b44e
5
5
  SHA512:
6
- metadata.gz: 7e2df3792feceedc6c98c92b29ddb25e67db2a57972ee08b20e71b13327b4c1296269ef7c873b7e9181aa042d03a2ab7849dde8498750088d2419789a1e33254
7
- data.tar.gz: c49ce4742815d16fab1b9cbcf30f621ae2429b515bab8e245f43c069a9be70bbbfe519b6cd8868cf5275c29dd3d1f7e2aa3f54f57e1973b2557c3203871c3614
6
+ metadata.gz: 73c85c552789c203b2ab88849501911784789c45fb027e821158bb6f3b359d2cc61acbdddd910f7f0bf802899ed070ced035f0ee8eeb736b6fc410ca1277caa6
7
+ data.tar.gz: b3a4295676b10ca536eb75c34d42ef3103b1900a73f748a39d486ab07ec3ac48a77d12f074ea393bb2276a2b3aa4f3521818e9127d919cde1631a3bc37752c40
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module GPTJsonRpcServer
6
+ PROTOCOL_VERSION = "2.0"
7
+
8
+ def self.handle_request(object:, request_json:)
9
+ requests = JSON.parse(request_json)
10
+ if requests.is_a?(Array)
11
+ responses = requests.map do |request|
12
+ Thread.new { process_request(object, request) }
13
+ end.map(&:value).compact
14
+ responses.empty? ? nil : responses.to_json
15
+ else
16
+ response = process_request(object, requests)
17
+ response&.to_json
18
+ end
19
+ rescue JSON::ParserError
20
+ { jsonrpc: PROTOCOL_VERSION, error: { code: -32_700, message: "Parse error" }, id: nil }.to_json
21
+ end
22
+
23
+ def self.process_request(object, request)
24
+ method = request["method"]
25
+ params = request["params"]
26
+ id = request["id"]
27
+
28
+ unless valid_id?(id)
29
+ return { jsonrpc: PROTOCOL_VERSION, error: { code: -32_600, message: "Invalid Request" }, id: nil }
30
+ end
31
+
32
+ unless method.is_a?(String) && object.public_methods(false).include?(method.to_sym)
33
+ return { jsonrpc: PROTOCOL_VERSION, error: { code: -32_601, message: "Method not found" }, id: id }
34
+ end
35
+
36
+ method_object = object.method(method)
37
+ param_list = method_object.parameters
38
+ valid_params = case params
39
+ when Array
40
+ param_list.none? { |type, _| type == :rest } && params.size == param_list.count do |type, _|
41
+ %i[req opt rest].include?(type)
42
+ end
43
+ when Hash
44
+ required_params = param_list.slice(:keyreq).map(&:last)
45
+ required_params.all? { |key| params.key?(key) } && param_list.all? do |type, name|
46
+ type != :keyreq || params.key?(name)
47
+ end
48
+ when NilClass
49
+ param_list.empty?
50
+ else
51
+ false
52
+ end
53
+
54
+ unless valid_params
55
+ return { jsonrpc: PROTOCOL_VERSION, error: { code: -32_602, message: "Invalid params" },
56
+ id: id }
57
+ end
58
+
59
+ if id.nil?
60
+ # Treat id: null as a notification
61
+ Thread.new { safe_call_method(method_object, params) }
62
+ return nil
63
+ end
64
+
65
+ begin
66
+ result = safe_call_method(method_object, params)
67
+ { jsonrpc: PROTOCOL_VERSION, result: result, id: id }
68
+ rescue StandardError => e
69
+ { jsonrpc: PROTOCOL_VERSION, error: { code: -32_603, message: "Internal error", data: e.message }, id: id }
70
+ end
71
+ end
72
+
73
+ def self.safe_call_method(method_object, params)
74
+ if params.is_a?(Array)
75
+ method_object.call(*params)
76
+ elsif params.is_a?(Hash)
77
+ method_object.call(**params)
78
+ else
79
+ method_object.call
80
+ end
81
+ end
82
+
83
+ def self.valid_id?(id)
84
+ return false unless id.is_a?(String) || id.is_a?(Numeric) || id.nil?
85
+ return false if id.is_a?(Numeric) && id != id.to_i
86
+
87
+ true
88
+ end
89
+ end
90
+
91
+ # Example usage
92
+
93
+ class MyService
94
+ def add(a, b)
95
+ a + b
96
+ end
97
+
98
+ def subtract(a:, b:)
99
+ a - b
100
+ end
101
+
102
+ private
103
+
104
+ def private_method
105
+ "This should not be exposed"
106
+ end
107
+ end
108
+
109
+ service = MyService.new
110
+
111
+ # Example JSON-RPC requests
112
+ single_request_positional = {
113
+ jsonrpc: "2.0",
114
+ method: "add",
115
+ params: [1, 2],
116
+ id: 1
117
+ }.to_json
118
+
119
+ single_request_keyword = {
120
+ jsonrpc: "2.0",
121
+ method: "subtract",
122
+ params: { a: 5, b: 3 },
123
+ id: 2
124
+ }.to_json
125
+
126
+ batch_request = [
127
+ { jsonrpc: "2.0", method: "add", params: [1, 2], id: 1 },
128
+ { jsonrpc: "2.0", method: "subtract", params: { a: 5, b: 3 }, id: 2 },
129
+ { jsonrpc: "2.0", method: "add", params: [7, 3], id: 3 }
130
+ ].to_json
131
+
132
+ notification_request = {
133
+ jsonrpc: "2.0",
134
+ method: "add",
135
+ params: [1, 2]
136
+ }.to_json
137
+
138
+ invalid_id_null_request = {
139
+ jsonrpc: "2.0",
140
+ method: "add",
141
+ params: [1, 2],
142
+ id: nil
143
+ }.to_json
144
+
145
+ invalid_id_object_request = {
146
+ jsonrpc: "2.0",
147
+ method: "add",
148
+ params: [1, 2],
149
+ id: {}
150
+ }.to_json
151
+
152
+ invalid_method_request = {
153
+ jsonrpc: "2.0",
154
+ method: {},
155
+ params: [1, 2],
156
+ id: 1
157
+ }.to_json
158
+
159
+ # Handling single request with positional parameters
160
+ response_json_positional = GPTJsonRpcServer.handle_request(object: service, request_json: single_request_positional)
161
+ puts response_json_positional # Output: {"jsonrpc":"2.0","result":3,"id":1}
162
+
163
+ # Handling single request with keyword parameters
164
+ response_json_keyword = GPTJsonRpcServer.handle_request(object: service, request_json: single_request_keyword)
165
+ puts response_json_keyword # Output: {"jsonrpc":"2.0","result":2,"id":2}
166
+
167
+ # Handling batch request
168
+ batch_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: batch_request)
169
+ puts batch_response_json # Output: [{"jsonrpc":"2.0","result":3,"id":1},{"jsonrpc":"2.0","result":2,"id":2},{"jsonrpc":"2.0","result":10,"id":3}]
170
+
171
+ # Handling notification request (no response expected)
172
+ notification_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: notification_request)
173
+ puts notification_response_json.nil? # Output: true
174
+
175
+ # Handling request with id = null (treated as a notification)
176
+ invalid_id_null_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: invalid_id_null_request)
177
+ puts invalid_id_null_response_json.nil? # Output: true
178
+
179
+ # Handling request with id as an object (should return an error)
180
+ invalid_id_object_response_json = GPTJsonRpcServer.handle_request(object: service,
181
+ request_json: invalid_id_object_request)
182
+ puts invalid_id_object_response_json # Output: {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}
183
+
184
+ # Handling request with method as an object (should return an error)
185
+ invalid_method_response_json = GPTJsonRpcServer.handle_request(object: service, request_json: invalid_method_request)
186
+ puts invalid_method_response_json # Output: {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":1}
data/lib/clamo/jsonrpc.rb CHANGED
@@ -37,7 +37,7 @@ module Clamo
37
37
 
38
38
  def proper_params_if_any?(request)
39
39
  if request.key?("params")
40
- request["params"].is_a?(Array)
40
+ request["params"].is_a?(Array) || request["params"].is_a?(Hash)
41
41
  else
42
42
  true
43
43
  end
@@ -47,8 +47,11 @@ module Clamo
47
47
  request.is_a?(Hash) &&
48
48
  proper_pragma?(request) &&
49
49
  proper_method?(request) &&
50
- proper_id_if_any?(request) &&
51
- proper_params_if_any?(request)
50
+ proper_id_if_any?(request)
51
+ end
52
+
53
+ def valid_params?(request)
54
+ proper_params_if_any?(request)
52
55
  end
53
56
 
54
57
  def build_request **opts
data/lib/clamo/server.rb CHANGED
@@ -41,7 +41,7 @@ module Clamo
41
41
  when Array
42
42
  object.send method.to_sym, *params
43
43
  when Hash
44
- object.send method.to_sym, **params
44
+ object.send method.to_sym, **params.transform_keys(&:to_sym)
45
45
  when NilClass
46
46
  object.send method.to_sym
47
47
  else
@@ -86,6 +86,13 @@ module Clamo
86
86
  )
87
87
  end
88
88
 
89
+ unless JSONRPC.valid_params?(request)
90
+ return JSONRPC.build_error_response_from(
91
+ id: request["id"],
92
+ descriptor: JSONRPC::ProtocolErrors::INVALID_PARAMS
93
+ )
94
+ end
95
+
89
96
  unless request.key?("id") # notification - no result needed
90
97
  # TODO: block.call off the current thread
91
98
  Thread.new do
data/lib/clamo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clamo
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clamo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andriy Tyurnikov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-05-04 00:00:00.000000000 Z
10
+ date: 2025-04-23 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: parallel
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: 1.24.0
18
+ version: 1.27.0
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: 1.24.0
25
+ version: 1.27.0
27
26
  description: JSON-RPC Client/Server tooling for Ruby
28
27
  email:
29
28
  - Andriy.Tyurnikov@gmail.com
@@ -36,6 +35,7 @@ files:
36
35
  - Rakefile
37
36
  - lib/clamo.rb
38
37
  - lib/clamo/client.rb
38
+ - lib/clamo/gpt_json_rpc_server.rb
39
39
  - lib/clamo/jsonrpc.rb
40
40
  - lib/clamo/server.rb
41
41
  - lib/clamo/version.rb
@@ -46,8 +46,7 @@ metadata:
46
46
  homepage_uri: https://github.com/andriytyurnikov/clamo
47
47
  source_code_uri: https://github.com/andriytyurnikov/clamo
48
48
  changelog_uri: https://github.com/andriytyurnikov/clamo
49
- rubygems_mfa_required: 'false'
50
- post_install_message:
49
+ rubygems_mfa_required: 'true'
51
50
  rdoc_options: []
52
51
  require_paths:
53
52
  - lib
@@ -62,8 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
61
  - !ruby/object:Gem::Version
63
62
  version: '0'
64
63
  requirements: []
65
- rubygems_version: 3.5.9
66
- signing_key:
64
+ rubygems_version: 3.6.5
67
65
  specification_version: 4
68
66
  summary: JSON-RPC Client/Server tooling for Ruby
69
67
  test_files: []