clamo 0.5.0 → 0.7.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 +4 -4
- data/.rubocop.yml +7 -1
- data/CHANGELOG.md +101 -0
- data/LICENSE +21 -0
- data/README.md +48 -37
- data/lib/clamo/jsonrpc.rb +62 -52
- data/lib/clamo/server.rb +128 -75
- data/lib/clamo/version.rb +1 -1
- data/lib/clamo.rb +0 -1
- metadata +14 -13
- data/lib/clamo/client.rb +0 -5
- data/lib/clamo/gpt_json_rpc_server.rb +0 -186
- data/sig/clamo.rbs +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 410f469e07b16f8273149b1eded89c94692a91c8b41cd7c47642e897ca8d97fb
|
|
4
|
+
data.tar.gz: 2d6be4be393cfd70d5bf22109943b7735de5ca0abe93b65a888fe1a372640faa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 385175b28fea11461b35a98860a6b8099bd9c88bc8dfef4f2603dc911a3d9e1709b51343d2b22ebb524ffc82aa03de7d8886a50b078c258d5fe28ec897a17296
|
|
7
|
+
data.tar.gz: 0c37f0971e24855610a91c8687d8e55ff32a03accf1effc73b6a1090118b46e3d78ffd9f0a7531418aa97a3154969bf7fb4d2dd0da9dc0cfe1b2b7aa94778cda
|
data/.rubocop.yml
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
AllCops:
|
|
2
2
|
NewCops: enable
|
|
3
|
-
TargetRubyVersion: 3.
|
|
3
|
+
TargetRubyVersion: 3.3
|
|
4
4
|
|
|
5
5
|
Metrics/MethodLength:
|
|
6
6
|
Enabled: false
|
|
7
7
|
|
|
8
|
+
Metrics/ModuleLength:
|
|
9
|
+
Enabled: false
|
|
10
|
+
|
|
11
|
+
Metrics/ClassLength:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
8
14
|
Style/Documentation:
|
|
9
15
|
Enabled: false
|
|
10
16
|
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [0.7.0] - 2026-03-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `Clamo::Server.timeout` — per-dispatch timeout with 30-second default; returns `-32000 Server error` on timeout for requests, calls `on_error` for notifications. Set to `nil` to disable.
|
|
12
|
+
- MIT LICENSE file and `spec.license` in gemspec
|
|
13
|
+
- Indifferent key access in JSONRPC validators (symbol and string keys both accepted)
|
|
14
|
+
- Tests for string ids, arity mismatch, single-item batches, and handle+timeout (77 tests / 105 assertions)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- `proper_pragma?`, `proper_method?`, `proper_id_if_any?` moved from public to private API on `Clamo::JSONRPC`
|
|
19
|
+
- Notifications with invalid params type now return `nil` instead of an error response (spec compliance)
|
|
20
|
+
- `parsed_dispatch_to_object` now validates `object:` argument (raises `ArgumentError` if nil)
|
|
21
|
+
- Single-item batches skip `Parallel.map` overhead
|
|
22
|
+
- Gemspec description expanded (no longer identical to summary)
|
|
23
|
+
- README updated with `Server.handle`, `timeout`, and `on_error` documentation
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
- `Clamo::Error` base exception class (unused)
|
|
28
|
+
- `sig/clamo.rbs` type signatures (misleadingly incomplete)
|
|
29
|
+
- Dead `else` branch in `dispatch_to_ruby`
|
|
30
|
+
|
|
31
|
+
## [0.6.0] - 2026-03-14
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `Clamo::Server.handle` — JSON string in, JSON string out entry point for HTTP/socket integrations
|
|
36
|
+
- `Clamo::Server.on_error` callback for notification failure reporting
|
|
37
|
+
- `Clamo::Error` base exception class
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Notifications now dispatch synchronously instead of spawning a background thread per call; callers control their own concurrency
|
|
42
|
+
- `build_error_response_from` accepts explicit `descriptor:` and `id:` keyword arguments instead of `**opts`
|
|
43
|
+
- `build_error_response_parse_error` takes no arguments (always returns `id: nil`)
|
|
44
|
+
- `parallel` dependency relaxed from `~> 1.27.0` to `~> 1.27`
|
|
45
|
+
- Minimum Ruby version raised from 3.0 to 3.3
|
|
46
|
+
|
|
47
|
+
### Removed
|
|
48
|
+
|
|
49
|
+
- `JSONRPC.valid_params?` (use `JSONRPC.proper_params_if_any?` directly)
|
|
50
|
+
- `JSONRPC::PROTOCOL_VERSION_PRAGMA` constant (unused)
|
|
51
|
+
- `JSONRPC::ProtocolErrors::SERVER_ERROR_CODE_RANGE` constant (unused)
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **Security:** replaced `send` with `public_send` to prevent remote invocation of private methods
|
|
56
|
+
- **Security:** `method_known?` now uses `public_methods(false)` to expose only explicitly defined methods
|
|
57
|
+
- Notifications now validate method existence before dispatch (previously skipped validation)
|
|
58
|
+
- Empty batch requests correctly return Invalid Request error per spec
|
|
59
|
+
- All-notification batches return `nil` instead of empty array per spec
|
|
60
|
+
- `build_error_response_from` no longer leaks the `:descriptor` key into the error response builder
|
|
61
|
+
|
|
62
|
+
### Internal
|
|
63
|
+
|
|
64
|
+
- `method_known?`, `dispatch_to_ruby`, `response_for` moved from public to private API
|
|
65
|
+
- `response_for_single_request` extracted into focused private helpers
|
|
66
|
+
- Test suite expanded from scaffold to 62 tests / 84 assertions covering validation, dispatch, security, error handling, batching, notifications, and argument edge cases
|
|
67
|
+
- CI matrix set to Ruby 3.3, 3.4, 4.0
|
|
68
|
+
|
|
69
|
+
## [0.5.0] - 2025-02-07
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- Updated README with detailed usage examples, error table, and batch/notification documentation
|
|
74
|
+
|
|
75
|
+
## [0.4.0] - 2025-02-07
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Corrected gem metadata URLs
|
|
80
|
+
|
|
81
|
+
## [0.3.0] - 2025-02-06
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- Batch request support via `parallel` gem
|
|
86
|
+
- Named parameter (Hash) dispatch
|
|
87
|
+
- JSON-RPC 2.0 validation (pragma, method, id, params)
|
|
88
|
+
- Protocol error constants (`PARSE_ERROR`, `INVALID_REQUEST`, etc.)
|
|
89
|
+
- RuboCop configuration
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
|
|
93
|
+
- Multiple version bumps during initial development
|
|
94
|
+
|
|
95
|
+
## [0.1.0] - 2025-02-06
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- Initial release
|
|
100
|
+
- Basic JSON-RPC 2.0 server with positional parameter dispatch
|
|
101
|
+
- `Clamo::JSONRPC` request/response builders
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Andriy Tyurnikov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
1
|
# Clamo
|
|
2
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
|
-
|
|
18
|
-
## Development
|
|
19
|
-
|
|
20
|
-
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.
|
|
21
|
-
|
|
22
|
-
To install this gem onto your local machi# Clamo
|
|
23
|
-
|
|
24
3
|
A Ruby implementation of [JSON-RPC 2.0](https://www.jsonrpc.org/specification) designed for simplicity and compliance with the specification.
|
|
25
4
|
|
|
26
5
|
|
|
@@ -36,26 +15,41 @@ module MyService
|
|
|
36
15
|
def self.add(a, b)
|
|
37
16
|
a + b
|
|
38
17
|
end
|
|
39
|
-
|
|
18
|
+
|
|
40
19
|
def self.subtract(a:, b:)
|
|
41
20
|
a - b
|
|
42
21
|
end
|
|
43
|
-
|
|
22
|
+
|
|
44
23
|
# Private methods won't be accessible via JSON-RPC
|
|
45
24
|
private_class_method def self.internal_method
|
|
46
25
|
# This won't be exposed
|
|
47
26
|
end
|
|
48
27
|
end
|
|
49
28
|
|
|
50
|
-
#
|
|
51
|
-
|
|
29
|
+
# JSON string in, JSON string out — the primary entry point for HTTP/socket integrations.
|
|
30
|
+
# Returns nil for notifications (no response expected).
|
|
31
|
+
json_response = Clamo::Server.handle(
|
|
32
|
+
request: '{"jsonrpc": "2.0", "method": "add", "params": [1, 2], "id": 1}',
|
|
33
|
+
object: MyService
|
|
34
|
+
)
|
|
35
|
+
# => '{"jsonrpc":"2.0","result":3,"id":1}'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If you need the parsed hash instead of a JSON string, use the lower-level methods:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# From a JSON string
|
|
52
42
|
response = Clamo::Server.unparsed_dispatch_to_object(
|
|
53
|
-
request:
|
|
43
|
+
request: '{"jsonrpc": "2.0", "method": "add", "params": [1, 2], "id": 1}',
|
|
54
44
|
object: MyService
|
|
55
45
|
)
|
|
46
|
+
# => {jsonrpc: "2.0", result: 3, id: 1}
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
# From a pre-parsed hash
|
|
49
|
+
response = Clamo::Server.parsed_dispatch_to_object(
|
|
50
|
+
request: { "jsonrpc" => "2.0", "method" => "add", "params" => [1, 2], "id" => 1 },
|
|
51
|
+
object: MyService
|
|
52
|
+
)
|
|
59
53
|
```
|
|
60
54
|
|
|
61
55
|
### Handling Different Parameter Types
|
|
@@ -88,7 +82,7 @@ batch_response = Clamo::Server.unparsed_dispatch_to_object(
|
|
|
88
82
|
)
|
|
89
83
|
|
|
90
84
|
puts batch_response
|
|
91
|
-
# => [{
|
|
85
|
+
# => [{jsonrpc: "2.0", result: 3, id: 1}, {jsonrpc: "2.0", result: 2, id: 2}]
|
|
92
86
|
```
|
|
93
87
|
|
|
94
88
|
### Notifications
|
|
@@ -118,7 +112,7 @@ request = Clamo::JSONRPC.build_request(
|
|
|
118
112
|
)
|
|
119
113
|
|
|
120
114
|
puts request
|
|
121
|
-
# => {:
|
|
115
|
+
# => {jsonrpc: "2.0", method: "add", params: [1, 2], id: 1}
|
|
122
116
|
```
|
|
123
117
|
|
|
124
118
|
## Error Handling
|
|
@@ -134,11 +128,32 @@ Clamo follows the JSON-RPC 2.0 specification for error handling:
|
|
|
134
128
|
| -32603 | Internal error | Internal JSON-RPC error |
|
|
135
129
|
| -32000 | Server error | Reserved for implementation-defined server errors |
|
|
136
130
|
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
### Timeout
|
|
134
|
+
|
|
135
|
+
Every method dispatch is wrapped in a timeout. The default is 30 seconds. Timed-out requests return a `-32000 Server error` response.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
Clamo::Server.timeout = 10 # seconds
|
|
139
|
+
Clamo::Server.timeout = nil # disable timeout
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Error Callback
|
|
143
|
+
|
|
144
|
+
Notifications don't return responses, so errors during notification dispatch are silent by default. Use `on_error` to capture them:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
Clamo::Server.on_error = ->(exception, method, params) {
|
|
148
|
+
Rails.logger.error("#{method} failed: #{exception.message}")
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
137
152
|
## Advanced Features
|
|
138
153
|
|
|
139
154
|
### Parallel Processing
|
|
140
155
|
|
|
141
|
-
Batch requests are processed in parallel using the [parallel](https://github.com/grosser/parallel) gem. You can pass options to `Parallel.map
|
|
156
|
+
Batch requests are processed in parallel using the [parallel](https://github.com/grosser/parallel) gem. You can pass options to `Parallel.map`:
|
|
142
157
|
|
|
143
158
|
```ruby
|
|
144
159
|
Clamo::Server.parsed_dispatch_to_object(
|
|
@@ -156,12 +171,8 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
|
156
171
|
|
|
157
172
|
## Contributing
|
|
158
173
|
|
|
159
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
174
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rubakas/clamo.
|
|
160
175
|
|
|
161
176
|
## License
|
|
162
177
|
|
|
163
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
164
|
-
|
|
165
|
-
## Contributing
|
|
166
|
-
|
|
167
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/rubakas/clamo
|
|
178
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/clamo/jsonrpc.rb
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Clamo
|
|
4
4
|
module JSONRPC
|
|
5
|
-
PROTOCOL_VERSION_PRAGMA = { jsonrpc: "2.0" }.freeze
|
|
6
|
-
|
|
7
5
|
module ProtocolErrors
|
|
8
6
|
ErrorDescriptor = Data.define(:code, :message)
|
|
9
|
-
SERVER_ERROR_CODE_RANGE = ((-32_099)..(-32_000))
|
|
10
7
|
|
|
11
8
|
PARSE_ERROR = ErrorDescriptor.new(code: -32_700, message: "Parse error")
|
|
12
9
|
INVALID_REQUEST = ErrorDescriptor.new(code: -32_600, message: "Invalid request")
|
|
@@ -17,27 +14,10 @@ module Clamo
|
|
|
17
14
|
end
|
|
18
15
|
|
|
19
16
|
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
17
|
def proper_params_if_any?(request)
|
|
39
|
-
if
|
|
40
|
-
|
|
18
|
+
if key_indifferent?(request, "params")
|
|
19
|
+
params = fetch_indifferent(request, "params")
|
|
20
|
+
params.is_a?(Array) || params.is_a?(Hash)
|
|
41
21
|
else
|
|
42
22
|
true
|
|
43
23
|
end
|
|
@@ -50,28 +30,24 @@ module Clamo
|
|
|
50
30
|
proper_id_if_any?(request)
|
|
51
31
|
end
|
|
52
32
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
end
|
|
33
|
+
def build_request(**opts)
|
|
34
|
+
raise ArgumentError, "method is required" unless opts.key?(:method)
|
|
56
35
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.merge({ params: opts[:params] })
|
|
63
|
-
.merge(opts.key?(:id) ? { id: opts[:id] } : {})
|
|
36
|
+
validate_params_type!(opts[:params]) if opts.key?(:params)
|
|
37
|
+
|
|
38
|
+
{ jsonrpc: "2.0", method: opts[:method] }
|
|
39
|
+
.then { |r| opts.key?(:params) ? r.merge(params: opts[:params]) : r }
|
|
40
|
+
.then { |r| opts.key?(:id) ? r.merge(id: opts[:id]) : r }
|
|
64
41
|
end
|
|
65
42
|
|
|
66
43
|
def build_result_response(id:, result:)
|
|
67
|
-
{}
|
|
68
|
-
.merge({ result: result })
|
|
69
|
-
.merge({ id: id })
|
|
44
|
+
{ jsonrpc: "2.0", result: result, id: id }
|
|
70
45
|
end
|
|
71
46
|
|
|
72
|
-
def build_error_response
|
|
73
|
-
|
|
74
|
-
|
|
47
|
+
def build_error_response(**opts)
|
|
48
|
+
raise ArgumentError, "error code is required" unless opts.dig(:error, :code)
|
|
49
|
+
raise ArgumentError, "error message is required" unless opts.dig(:error, :message)
|
|
50
|
+
|
|
75
51
|
{ jsonrpc: "2.0",
|
|
76
52
|
id: opts[:id],
|
|
77
53
|
error: {
|
|
@@ -81,23 +57,57 @@ module Clamo
|
|
|
81
57
|
}.reject { |k, _| k == :data && !opts[:error].key?(:data) } }
|
|
82
58
|
end
|
|
83
59
|
|
|
84
|
-
def build_error_response_from
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
60
|
+
def build_error_response_from(descriptor:, id: nil)
|
|
61
|
+
build_error_response(
|
|
62
|
+
id: id,
|
|
63
|
+
error: {
|
|
64
|
+
code: descriptor.code,
|
|
65
|
+
message: descriptor.message
|
|
66
|
+
}
|
|
90
67
|
)
|
|
91
|
-
.then { |hash| build_error_response(**hash) }
|
|
92
68
|
end
|
|
93
69
|
|
|
94
|
-
def build_error_response_parse_error
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
70
|
+
def build_error_response_parse_error
|
|
71
|
+
build_error_response(
|
|
72
|
+
id: nil,
|
|
73
|
+
error: {
|
|
74
|
+
code: ProtocolErrors::PARSE_ERROR.code,
|
|
75
|
+
message: ProtocolErrors::PARSE_ERROR.message
|
|
76
|
+
}
|
|
99
77
|
)
|
|
100
|
-
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def proper_pragma?(request)
|
|
83
|
+
fetch_indifferent(request, "jsonrpc") == "2.0"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def proper_method?(request)
|
|
87
|
+
fetch_indifferent(request, "method").is_a?(String)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def proper_id_if_any?(request)
|
|
91
|
+
if key_indifferent?(request, "id")
|
|
92
|
+
id = fetch_indifferent(request, "id")
|
|
93
|
+
id.is_a?(String) || id.is_a?(Integer) || id.is_a?(NilClass)
|
|
94
|
+
else
|
|
95
|
+
true
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def validate_params_type!(params)
|
|
100
|
+
return if params.is_a?(Array) || params.is_a?(Hash)
|
|
101
|
+
|
|
102
|
+
raise ArgumentError, "params must be an Array or Hash"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def fetch_indifferent(hash, key)
|
|
106
|
+
hash.fetch(key.to_s) { hash.fetch(key.to_sym, nil) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def key_indifferent?(hash, key)
|
|
110
|
+
hash.key?(key.to_s) || hash.key?(key.to_sym)
|
|
101
111
|
end
|
|
102
112
|
end
|
|
103
113
|
end
|
data/lib/clamo/server.rb
CHANGED
|
@@ -1,129 +1,182 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "parallel"
|
|
5
|
+
require "timeout"
|
|
4
6
|
|
|
5
7
|
module Clamo
|
|
6
8
|
module Server
|
|
7
9
|
class << self
|
|
10
|
+
# Global error callback for notification failures.
|
|
11
|
+
# This is module-level state shared across all callers.
|
|
12
|
+
# Set to any callable (lambda, method, proc) that accepts (exception, method, params).
|
|
13
|
+
#
|
|
14
|
+
# Clamo::Server.on_error = ->(e, method, params) { Rails.logger.error(e) }
|
|
15
|
+
#
|
|
16
|
+
attr_accessor :on_error
|
|
17
|
+
|
|
18
|
+
# Maximum seconds allowed for a single method dispatch. Defaults to 30.
|
|
19
|
+
# Set to nil to disable.
|
|
20
|
+
#
|
|
21
|
+
# Clamo::Server.timeout = 10
|
|
22
|
+
#
|
|
23
|
+
attr_writer :timeout
|
|
24
|
+
|
|
25
|
+
def timeout
|
|
26
|
+
return @timeout if defined?(@timeout)
|
|
27
|
+
|
|
28
|
+
30
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# JSON string in, JSON string out. Full round-trip for HTTP/socket integrations.
|
|
32
|
+
#
|
|
33
|
+
# Clamo::Server.handle(request: body, object: MyService)
|
|
34
|
+
#
|
|
35
|
+
def handle(request:, object:, **)
|
|
36
|
+
response = unparsed_dispatch_to_object(request: request, object: object, **)
|
|
37
|
+
response&.to_json
|
|
38
|
+
end
|
|
39
|
+
|
|
8
40
|
# Clamo::Server.unparsed_dispatch_to_object(
|
|
9
41
|
# request: request_body,
|
|
10
42
|
# object: MyModule
|
|
11
43
|
# )
|
|
12
|
-
def unparsed_dispatch_to_object(request:, object:, **
|
|
13
|
-
|
|
44
|
+
def unparsed_dispatch_to_object(request:, object:, **)
|
|
45
|
+
raise ArgumentError, "object is required" unless object
|
|
46
|
+
|
|
14
47
|
begin
|
|
15
48
|
parsed = JSON.parse(request)
|
|
16
|
-
rescue JSON::JSONError
|
|
49
|
+
rescue JSON::JSONError
|
|
17
50
|
return JSONRPC.build_error_response_parse_error
|
|
18
51
|
end
|
|
19
52
|
|
|
20
|
-
parsed_dispatch_to_object(request: parsed, object: object, **
|
|
53
|
+
parsed_dispatch_to_object(request: parsed, object: object, **)
|
|
21
54
|
end
|
|
22
55
|
|
|
23
56
|
def parsed_dispatch_to_object(request:, object:, **opts)
|
|
57
|
+
raise ArgumentError, "object is required" unless object
|
|
58
|
+
|
|
24
59
|
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
|
-
)
|
|
60
|
+
dispatch_to_ruby(object: object, method: method, params: params)
|
|
30
61
|
end
|
|
31
62
|
end
|
|
32
63
|
|
|
64
|
+
private
|
|
65
|
+
|
|
33
66
|
def method_known?(object:, method:)
|
|
34
|
-
|
|
35
|
-
.map(&:to_sym)
|
|
36
|
-
.include?(method.to_sym)
|
|
67
|
+
object.public_methods(false).map(&:to_sym).include?(method.to_sym)
|
|
37
68
|
end
|
|
38
69
|
|
|
39
70
|
def dispatch_to_ruby(object:, method:, params:)
|
|
40
71
|
case params
|
|
72
|
+
when Array then object.public_send(method.to_sym, *params)
|
|
73
|
+
when Hash then object.public_send(method.to_sym, **params.transform_keys(&:to_sym))
|
|
74
|
+
when NilClass then object.public_send(method.to_sym)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Extra keyword arguments (**) are forwarded to response_for_batch only,
|
|
79
|
+
# where they become options for Parallel.map (e.g., in_processes: 4).
|
|
80
|
+
# For single requests they are silently ignored.
|
|
81
|
+
def response_for(request:, object:, **, &block)
|
|
82
|
+
case request
|
|
41
83
|
when Array
|
|
42
|
-
object
|
|
84
|
+
response_for_batch(request: request, object: object, block: block, **)
|
|
43
85
|
when Hash
|
|
44
|
-
object
|
|
45
|
-
when NilClass
|
|
46
|
-
object.send method.to_sym
|
|
86
|
+
response_for_single_request(request: request, object: object, block: block)
|
|
47
87
|
else
|
|
48
|
-
|
|
49
|
-
raise "WTF"
|
|
88
|
+
JSONRPC.build_error_response_from(id: nil, descriptor: JSONRPC::ProtocolErrors::INVALID_REQUEST)
|
|
50
89
|
end
|
|
51
90
|
end
|
|
52
91
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
)
|
|
92
|
+
def response_for_single_request(request:, object:, block:)
|
|
93
|
+
error = validate_request_structure(request)
|
|
94
|
+
return error if error
|
|
95
|
+
|
|
96
|
+
unless method_known?(object: object, method: request["method"])
|
|
97
|
+
return request.key?("id") ? method_not_found_error(request) : nil
|
|
74
98
|
end
|
|
99
|
+
|
|
100
|
+
return dispatch_notification(request, block) unless request.key?("id")
|
|
101
|
+
|
|
102
|
+
dispatch_request(request, block)
|
|
75
103
|
end
|
|
76
104
|
|
|
77
|
-
def
|
|
78
|
-
|
|
105
|
+
def response_for_batch(request:, object:, block:, **opts)
|
|
106
|
+
if request.empty?
|
|
107
|
+
return JSONRPC.build_error_response_from(id: nil, descriptor: JSONRPC::ProtocolErrors::INVALID_REQUEST)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if request.size == 1
|
|
111
|
+
result = response_for_single_request(request: request.first, object: object, block: block)
|
|
112
|
+
return result ? [result] : nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
result = Parallel.map(request, **opts) do |item|
|
|
116
|
+
response_for_single_request(request: item, object: object, block: block)
|
|
117
|
+
end.compact
|
|
118
|
+
result.empty? ? nil : result
|
|
79
119
|
end
|
|
80
120
|
|
|
81
|
-
def
|
|
121
|
+
def validate_request_structure(request)
|
|
82
122
|
unless JSONRPC.valid_request?(request)
|
|
83
123
|
return JSONRPC.build_error_response_from(
|
|
84
|
-
id: request["id"],
|
|
124
|
+
id: request.is_a?(Hash) ? request["id"] : nil,
|
|
85
125
|
descriptor: JSONRPC::ProtocolErrors::INVALID_REQUEST
|
|
86
126
|
)
|
|
87
127
|
end
|
|
88
128
|
|
|
89
|
-
|
|
90
|
-
return JSONRPC.build_error_response_from(
|
|
91
|
-
id: request["id"],
|
|
92
|
-
descriptor: JSONRPC::ProtocolErrors::INVALID_PARAMS
|
|
93
|
-
)
|
|
94
|
-
end
|
|
129
|
+
return if JSONRPC.proper_params_if_any?(request)
|
|
95
130
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Thread.new do
|
|
99
|
-
yield_to_execution(
|
|
100
|
-
block: block,
|
|
101
|
-
method: request["method"],
|
|
102
|
-
params: request["params"]
|
|
103
|
-
)
|
|
104
|
-
rescue StandardError
|
|
105
|
-
# TODO: add exception handler
|
|
106
|
-
nil
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
return nil
|
|
110
|
-
end
|
|
131
|
+
# Notifications must never produce a response, even for invalid params
|
|
132
|
+
return nil unless request.key?("id")
|
|
111
133
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
id: request["id"],
|
|
115
|
-
descriptor: JSONRPC::ProtocolErrors::METHOD_NOT_FOUND
|
|
116
|
-
)
|
|
117
|
-
end
|
|
134
|
+
JSONRPC.build_error_response_from(id: request["id"], descriptor: JSONRPC::ProtocolErrors::INVALID_PARAMS)
|
|
135
|
+
end
|
|
118
136
|
|
|
137
|
+
def method_not_found_error(request)
|
|
138
|
+
JSONRPC.build_error_response_from(id: request["id"], descriptor: JSONRPC::ProtocolErrors::METHOD_NOT_FOUND)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def dispatch_notification(request, block)
|
|
142
|
+
with_timeout { block.yield request["method"], request["params"] }
|
|
143
|
+
nil
|
|
144
|
+
rescue StandardError => e
|
|
145
|
+
on_error&.call(e, request["method"], request["params"])
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def dispatch_request(request, block)
|
|
119
150
|
JSONRPC.build_result_response(
|
|
120
151
|
id: request["id"],
|
|
121
|
-
result:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
result: with_timeout { block.yield(request["method"], request["params"]) }
|
|
153
|
+
)
|
|
154
|
+
rescue Timeout::Error
|
|
155
|
+
JSONRPC.build_error_response(
|
|
156
|
+
id: request["id"],
|
|
157
|
+
error: {
|
|
158
|
+
code: JSONRPC::ProtocolErrors::SERVER_ERROR.code,
|
|
159
|
+
message: JSONRPC::ProtocolErrors::SERVER_ERROR.message,
|
|
160
|
+
data: "Request timed out"
|
|
161
|
+
}
|
|
126
162
|
)
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
JSONRPC.build_error_response(
|
|
165
|
+
id: request["id"],
|
|
166
|
+
error: {
|
|
167
|
+
code: JSONRPC::ProtocolErrors::INTERNAL_ERROR.code,
|
|
168
|
+
message: JSONRPC::ProtocolErrors::INTERNAL_ERROR.message,
|
|
169
|
+
data: e.message
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def with_timeout(&block)
|
|
175
|
+
if timeout
|
|
176
|
+
Timeout.timeout(timeout, &block)
|
|
177
|
+
else
|
|
178
|
+
block.call
|
|
179
|
+
end
|
|
127
180
|
end
|
|
128
181
|
end
|
|
129
182
|
end
|
data/lib/clamo/version.rb
CHANGED
data/lib/clamo.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clamo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andriy Tyurnikov
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: parallel
|
|
@@ -15,15 +15,16 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 1.27
|
|
18
|
+
version: '1.27'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 1.27
|
|
26
|
-
description: JSON-RPC
|
|
25
|
+
version: '1.27'
|
|
26
|
+
description: Minimal, spec-compliant JSON-RPC 2.0 server for Ruby with request validation,
|
|
27
|
+
method dispatch, batch processing, and notification support.
|
|
27
28
|
email:
|
|
28
29
|
- Andriy.Tyurnikov@gmail.com
|
|
29
30
|
executables: []
|
|
@@ -31,21 +32,21 @@ extensions: []
|
|
|
31
32
|
extra_rdoc_files: []
|
|
32
33
|
files:
|
|
33
34
|
- ".rubocop.yml"
|
|
35
|
+
- CHANGELOG.md
|
|
36
|
+
- LICENSE
|
|
34
37
|
- README.md
|
|
35
38
|
- Rakefile
|
|
36
39
|
- lib/clamo.rb
|
|
37
|
-
- lib/clamo/client.rb
|
|
38
|
-
- lib/clamo/gpt_json_rpc_server.rb
|
|
39
40
|
- lib/clamo/jsonrpc.rb
|
|
40
41
|
- lib/clamo/server.rb
|
|
41
42
|
- lib/clamo/version.rb
|
|
42
|
-
- sig/clamo.rbs
|
|
43
43
|
homepage: https://github.com/rubakas/clamo
|
|
44
|
-
licenses:
|
|
44
|
+
licenses:
|
|
45
|
+
- MIT
|
|
45
46
|
metadata:
|
|
46
47
|
homepage_uri: https://github.com/rubakas/clamo
|
|
47
48
|
source_code_uri: https://github.com/rubakas/clamo
|
|
48
|
-
changelog_uri: https://github.com/rubakas/clamo
|
|
49
|
+
changelog_uri: https://github.com/rubakas/clamo/blob/main/CHANGELOG.md
|
|
49
50
|
rubygems_mfa_required: 'true'
|
|
50
51
|
rdoc_options: []
|
|
51
52
|
require_paths:
|
|
@@ -54,14 +55,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
54
55
|
requirements:
|
|
55
56
|
- - ">="
|
|
56
57
|
- !ruby/object:Gem::Version
|
|
57
|
-
version: 3.
|
|
58
|
+
version: 3.3.0
|
|
58
59
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
60
|
requirements:
|
|
60
61
|
- - ">="
|
|
61
62
|
- !ruby/object:Gem::Version
|
|
62
63
|
version: '0'
|
|
63
64
|
requirements: []
|
|
64
|
-
rubygems_version:
|
|
65
|
+
rubygems_version: 4.0.3
|
|
65
66
|
specification_version: 4
|
|
66
|
-
summary: JSON-RPC
|
|
67
|
+
summary: JSON-RPC 2.0 server toolkit for Ruby
|
|
67
68
|
test_files: []
|
data/lib/clamo/client.rb
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
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/sig/clamo.rbs
DELETED