protocol-jsonrpc 0.1.0 → 0.2.1
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/CHANGELOG.md +12 -0
- data/README.md +131 -81
- data/lib/protocol/jsonrpc/batch.rb +45 -0
- data/lib/protocol/jsonrpc/connection.rb +7 -9
- data/lib/protocol/jsonrpc/error.rb +22 -10
- data/lib/protocol/jsonrpc/{error_message.rb → error_response.rb} +5 -5
- data/lib/protocol/jsonrpc/frame.rb +41 -25
- data/lib/protocol/jsonrpc/invalid_message.rb +33 -0
- data/lib/protocol/jsonrpc/message.rb +28 -51
- data/lib/protocol/jsonrpc/notification.rb +43 -0
- data/lib/protocol/jsonrpc/{request_message.rb → request.rb} +25 -6
- data/lib/protocol/jsonrpc/{response_message.rb → response.rb} +3 -7
- data/lib/protocol/jsonrpc/version.rb +1 -1
- data/lib/protocol/jsonrpc.rb +14 -9
- metadata +7 -5
- data/lib/protocol/jsonrpc/notification_message.rb +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b54b4f7232fe6c0daa9a4e589f4db5edd2aba983cbcfc7a4ab880cfaaa2c2ffa
|
|
4
|
+
data.tar.gz: f8df39a2b673213f62e9681e1500d7f7b487aa4c77d925253e3dc019c6c0f8d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee456605301d8af0745a57b453583ba115f711f63a93b24463ad86fafaa32e862832d81359654eb48ded753ba6aa1568e251855ec115858984bed24b6dc4277a
|
|
7
|
+
data.tar.gz: 6683a84ec2f1bdc098dfb09caf187cf34763896067f10711b232f54971b8566a3ffedf13224e3a2b9fbb726d799d4e249b9b761f03b92e938d168402d7fecc5f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.1] - 2025-06-01
|
|
4
|
+
|
|
5
|
+
- If a reply block for a Notification raises, suppress the error as defined in the spec
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2025-06-01
|
|
8
|
+
|
|
9
|
+
**Breaking changes**: As I work towarsd a 1.0.0 release, I've changed the interface to ensure a uniform interface for [JSON-RPC 2.0 batch processing](https://www.jsonrpc.org/specification#batch). I believe we have a much more robust implementation now, so I will try to stay more consistent, but please be cautious upgrading until 1.0.0 is released and we finalize the interface.
|
|
10
|
+
|
|
11
|
+
- Adds full support for batch processing with uniform reply block interface.
|
|
12
|
+
- Better error handling, though this is my biggest area of improvement.
|
|
13
|
+
- InvalidMessage is now returned when a message is invalid, allowing the receiver to inspect and handle the message, but still benefiting from automatic replies.
|
|
14
|
+
|
|
3
15
|
## [0.1.0] - 2025-04-20
|
|
4
16
|
|
|
5
17
|
- Initial release
|
data/README.md
CHANGED
|
@@ -23,10 +23,27 @@ gem install protocol-jsonrpc
|
|
|
23
23
|
|
|
24
24
|
Protocol::Jsonrpc has several core concepts:
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
### `Protocol::Jsonrpc::Message`
|
|
27
|
+
|
|
28
|
+
This is the type which represents JSON-RPC message structures.
|
|
29
|
+
|
|
30
|
+
Each of the 4 JSONRPC message types has their own class which include the `Message` module.
|
|
31
|
+
|
|
32
|
+
- `Protocol::Jsonrpc::ErrorResponse`
|
|
33
|
+
- `Protocol::Jsonrpc::Notification`
|
|
34
|
+
- `Protocol::Jsonrpc::Request`
|
|
35
|
+
- `Protocol::Jsonrpc::Response`
|
|
36
|
+
|
|
37
|
+
### `Protocol::Jsonrpc::Framer`
|
|
38
|
+
|
|
39
|
+
This provides one implementation for an object that splits JSONRPC messages off of some sort of socket or communication layer.
|
|
40
|
+
|
|
41
|
+
The provided implementation wraps an underlying bi-directional stream (like a unixsocket) for reading and writing JSON-RPC messages.
|
|
42
|
+
|
|
43
|
+
### `Protocol::Jsonrpc::Connection`
|
|
44
|
+
|
|
45
|
+
This wraps a framer and provides higher-level methods for communication.
|
|
46
|
+
|
|
30
47
|
|
|
31
48
|
## Basic Usage
|
|
32
49
|
|
|
@@ -35,9 +52,8 @@ Protocol::Jsonrpc has several core concepts:
|
|
|
35
52
|
Here's a basic example showing how to create a JSON-RPC connection over a socket pair:
|
|
36
53
|
|
|
37
54
|
```ruby
|
|
38
|
-
require
|
|
39
|
-
require
|
|
40
|
-
require 'socket'
|
|
55
|
+
require "protocol/jsonrpc"
|
|
56
|
+
require "socket"
|
|
41
57
|
|
|
42
58
|
# Create a socket pair for testing
|
|
43
59
|
client_socket, server_socket = UNIXSocket.pair
|
|
@@ -47,12 +63,12 @@ client = Protocol::Jsonrpc::Connection.new(Protocol::Jsonrpc::Framer.new(client_
|
|
|
47
63
|
server = Protocol::Jsonrpc::Connection.new(Protocol::Jsonrpc::Framer.new(server_socket))
|
|
48
64
|
|
|
49
65
|
# Client sends a request
|
|
50
|
-
subtract = Protocol::Jsonrpc::
|
|
66
|
+
subtract = Protocol::Jsonrpc::Request.new(method: "subtract", params: [42, 23])
|
|
51
67
|
client.write(subtract)
|
|
52
68
|
|
|
53
69
|
# Server reads the request
|
|
54
70
|
message = server.read
|
|
55
|
-
# => <#Protocol::Jsonrpc::
|
|
71
|
+
# => <#Protocol::Jsonrpc::Request id:"...", method: "subtract", params: [42, 23]>
|
|
56
72
|
|
|
57
73
|
# Server processes the request (calculating the result)
|
|
58
74
|
result = message.params.inject(:-) if message.method == "subtract"
|
|
@@ -69,13 +85,14 @@ client.close
|
|
|
69
85
|
server.close
|
|
70
86
|
```
|
|
71
87
|
|
|
72
|
-
###
|
|
88
|
+
### Server Implementation
|
|
73
89
|
|
|
74
|
-
|
|
90
|
+
Here's a server implementation showing how to handle different message types:
|
|
75
91
|
|
|
76
92
|
```ruby
|
|
77
93
|
require 'protocol/jsonrpc'
|
|
78
94
|
require 'protocol/jsonrpc/connection'
|
|
95
|
+
require 'protocol/jsonrpc/framer'
|
|
79
96
|
require 'socket'
|
|
80
97
|
|
|
81
98
|
server = TCPServer.new('localhost', 4567)
|
|
@@ -90,39 +107,27 @@ handlers = {
|
|
|
90
107
|
"divide" => ->(params) { params.reduce(:/) }
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
|
|
94
|
-
|
|
110
|
+
def handle_request(method, params)
|
|
111
|
+
puts "Received request: #{method}"
|
|
112
|
+
if handlers.key?(method)
|
|
113
|
+
handlers[method].call(params)
|
|
114
|
+
else
|
|
115
|
+
raise Protocol::Jsonrpc::MethodNotFoundError.new
|
|
116
|
+
end
|
|
117
|
+
end
|
|
95
118
|
|
|
96
119
|
# Main server loop
|
|
97
120
|
begin
|
|
98
121
|
while (message = connection.read)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
connection.write(message.reply(result))
|
|
106
|
-
else
|
|
107
|
-
error = Protocol::Jsonrpc::MethodNotFoundError.new
|
|
108
|
-
connection.write(message.reply(error))
|
|
122
|
+
response = message.reply do |message|
|
|
123
|
+
if message.request?
|
|
124
|
+
handle_request(message.method, message.params)
|
|
125
|
+
elsif message.notification?
|
|
126
|
+
puts "Notification: #{message.method}"
|
|
127
|
+
# Handle notification (no response needed)
|
|
109
128
|
end
|
|
110
|
-
|
|
111
|
-
when Protocol::Jsonrpc::NotificationMessage
|
|
112
|
-
puts "Notification: #{message.method}"
|
|
113
|
-
# Handle notification (no response needed)
|
|
114
|
-
|
|
115
|
-
when Protocol::Jsonrpc::ResponseMessage
|
|
116
|
-
puts "Response: #{message.result}"
|
|
117
|
-
# Process response for an earlier request
|
|
118
|
-
request = pending_requests.delete(message.id)
|
|
119
|
-
request.call(message) if request
|
|
120
|
-
|
|
121
|
-
when Protocol::Jsonrpc::ErrorMessage
|
|
122
|
-
puts "Error: #{message.error.message}"
|
|
123
|
-
request = pending_requests.delete(message.id)
|
|
124
|
-
request.call(message) if request
|
|
125
129
|
end
|
|
130
|
+
connection.write(response)
|
|
126
131
|
end
|
|
127
132
|
rescue Errno::EPIPE, IOError => e
|
|
128
133
|
puts "Connection closed: #{e.message}"
|
|
@@ -139,14 +144,14 @@ end
|
|
|
139
144
|
|
|
140
145
|
```ruby
|
|
141
146
|
# Create a request with positional parameters
|
|
142
|
-
request = Protocol::Jsonrpc::
|
|
147
|
+
request = Protocol::Jsonrpc::Request.new(
|
|
143
148
|
method: "subtract",
|
|
144
149
|
params: [42, 23],
|
|
145
150
|
id: 1 # Optional, auto-generated if not provided
|
|
146
151
|
)
|
|
147
152
|
|
|
148
153
|
# Create a request with named parameters
|
|
149
|
-
request = Protocol::Jsonrpc::
|
|
154
|
+
request = Protocol::Jsonrpc::Request.new(
|
|
150
155
|
method: "subtract",
|
|
151
156
|
params: { minuend: 42, subtrahend: 23 },
|
|
152
157
|
id: 2
|
|
@@ -158,9 +163,9 @@ request = Protocol::Jsonrpc::RequestMessage.new(
|
|
|
158
163
|
Notifications are similar to requests but don't expect a response:
|
|
159
164
|
|
|
160
165
|
```ruby
|
|
161
|
-
notification = Protocol::Jsonrpc::
|
|
166
|
+
notification = Protocol::Jsonrpc::Notification.new(
|
|
162
167
|
method: "update",
|
|
163
|
-
params:
|
|
168
|
+
params: { a: 1 }
|
|
164
169
|
)
|
|
165
170
|
```
|
|
166
171
|
|
|
@@ -172,8 +177,15 @@ Typically created by replying to a request:
|
|
|
172
177
|
# From a request object
|
|
173
178
|
response = request.reply(19)
|
|
174
179
|
|
|
180
|
+
response = request.reply do |message|
|
|
181
|
+
message.params.sum if message.request? && message.method == "add"
|
|
182
|
+
if message.method == "fail"
|
|
183
|
+
raise "Oops, we failed" # gets turned into a error response
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
175
187
|
# Or directly
|
|
176
|
-
response = Protocol::Jsonrpc::
|
|
188
|
+
response = Protocol::Jsonrpc::Response.new(
|
|
177
189
|
result: 19,
|
|
178
190
|
id: 1
|
|
179
191
|
)
|
|
@@ -188,79 +200,117 @@ For error responses:
|
|
|
188
200
|
error = Protocol::Jsonrpc::InvalidParamsError.new("Invalid parameters")
|
|
189
201
|
error_response = request.reply(error)
|
|
190
202
|
|
|
191
|
-
#
|
|
192
|
-
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
# Create from a reply that raises
|
|
204
|
+
response = request.reply do |message|
|
|
205
|
+
raise "Oops" # Returns an InternalError response
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Error types represent the standard JSON-RPC error codes:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
Protocol::Jsonrpc::ParseError
|
|
213
|
+
Protocol::Jsonrpc::InvalidRequestError
|
|
214
|
+
Protocol::Jsonrpc::MethodNotFoundError
|
|
215
|
+
Protocol::Jsonrpc::InvalidParamsError
|
|
216
|
+
Protocol::Jsonrpc::InternalError
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Most of the errors happen automatically, but some must be triggered manually.
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
response = request.reply do |message|
|
|
223
|
+
raise Protocol::Jsonrpc::MethodNotFoundError, "We don't support any methods"
|
|
224
|
+
end
|
|
198
225
|
```
|
|
199
226
|
|
|
200
227
|
## Batch Processing
|
|
201
228
|
|
|
202
|
-
JSON-RPC supports batch requests and responses:
|
|
229
|
+
JSON-RPC supports batch requests and responses. The library returns a `Protocol::Jsonrpc::Batch` that acts like an array and provides a `reply` method for processing:
|
|
203
230
|
|
|
204
231
|
```ruby
|
|
232
|
+
# Send a batch request (client side)
|
|
205
233
|
batch = [
|
|
206
|
-
Protocol::Jsonrpc::
|
|
207
|
-
Protocol::Jsonrpc::
|
|
208
|
-
Protocol::Jsonrpc::
|
|
234
|
+
Protocol::Jsonrpc::Request.new(method: "sum", params: [1, 2, 4]),
|
|
235
|
+
Protocol::Jsonrpc::Notification.new(method: "notify_hello", params: [7]),
|
|
236
|
+
Protocol::Jsonrpc::Request.new(method: "subtract", params: [42, 23])
|
|
209
237
|
]
|
|
210
238
|
|
|
211
|
-
# Send batch request
|
|
212
239
|
client.write(batch)
|
|
213
240
|
|
|
214
241
|
# Process batch on server
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
242
|
+
batch = server.read
|
|
243
|
+
|
|
244
|
+
# Process each message in the batch using reply
|
|
245
|
+
batch_response = batch.reply do |message|
|
|
246
|
+
case message
|
|
247
|
+
when Protocol::Jsonrpc::Request
|
|
248
|
+
# Handle request and return result
|
|
249
|
+
if message.method == "sum"
|
|
250
|
+
message.params.sum
|
|
251
|
+
elsif message.method == "subtract"
|
|
252
|
+
message.params.reduce(:-)
|
|
225
253
|
else
|
|
226
|
-
|
|
254
|
+
# raising during a reply block will automatically respond with an error
|
|
255
|
+
raise Protocol::Jsonrpc::MethodNotFoundError.new
|
|
227
256
|
end
|
|
228
|
-
when Protocol::Jsonrpc::
|
|
229
|
-
|
|
230
|
-
|
|
257
|
+
when Protocol::Jsonrpc::Notification
|
|
258
|
+
# Handle notification (return value is ignored)
|
|
259
|
+
handle_notification(message)
|
|
231
260
|
end
|
|
232
261
|
end
|
|
233
262
|
|
|
234
|
-
# Send batch response
|
|
235
|
-
server.write(batch_response)
|
|
263
|
+
# Send batch response (automatically includes responses for requests, not notifications)
|
|
264
|
+
server.write(batch_response)
|
|
236
265
|
```
|
|
237
266
|
|
|
267
|
+
Batch processing supports;
|
|
268
|
+
|
|
269
|
+
1. Consistent interface (`reply`) for both single and batch requests
|
|
270
|
+
2. Automatic error handling and response collection
|
|
271
|
+
3. Filters out nil responses (from notifications) automatically
|
|
272
|
+
4. Maintains protocol compliance by only responding to requests and handling malformed batches
|
|
273
|
+
|
|
238
274
|
## Custom Framers
|
|
239
275
|
|
|
240
276
|
The supplied Framer is designed for a bidirectional socket.
|
|
241
|
-
You can also supply your own framer:
|
|
277
|
+
You can also supply your own framer by implementing the following interface:
|
|
242
278
|
|
|
243
279
|
```ruby
|
|
244
280
|
class MyFramer
|
|
245
|
-
|
|
246
|
-
def close; end
|
|
247
|
-
|
|
248
|
-
# Return an object that response to unpack
|
|
281
|
+
# Return a Frame object that contains the raw JSON
|
|
249
282
|
def read_frame
|
|
283
|
+
# Read JSON data from your source (e.g., HTTP body, WebSocket, etc.)
|
|
284
|
+
raw_json = get_json_line_from_somewhere
|
|
285
|
+
|
|
286
|
+
# Return a Frame object
|
|
287
|
+
Protocol::Jsonrpc::Frame.new(raw_json: raw_json)
|
|
250
288
|
end
|
|
251
289
|
|
|
252
|
-
#
|
|
290
|
+
# Write a Frame object
|
|
253
291
|
def write_frame(frame)
|
|
292
|
+
# frame.raw_json contains the JSON string to send
|
|
293
|
+
send_json_somewhere(frame.raw_json)
|
|
254
294
|
end
|
|
255
|
-
end
|
|
256
295
|
|
|
296
|
+
# Flush any buffered data
|
|
297
|
+
def flush
|
|
298
|
+
# Implementation depends on your transport
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Close the connection
|
|
302
|
+
def close
|
|
303
|
+
# Clean up resources
|
|
304
|
+
end
|
|
305
|
+
end
|
|
257
306
|
|
|
258
307
|
client = Protocol::Jsonrpc::Connection.new(MyFramer.new)
|
|
259
|
-
client.read # calls read_frame, calling unpack on the returned object
|
|
260
308
|
|
|
261
|
-
|
|
262
|
-
|
|
309
|
+
# Read messages (calls framer.read_frame and unpacks the JSON)
|
|
310
|
+
message = client.read
|
|
263
311
|
|
|
312
|
+
# Write messages (packs to JSON and calls framer.write_frame)
|
|
313
|
+
client.write(Protocol::Jsonrpc::Notification.new(method: "hello", params: ["world"]))
|
|
264
314
|
```
|
|
265
315
|
|
|
266
316
|
## Development
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright 2025 by Martin Emde
|
|
5
|
+
|
|
6
|
+
module Protocol
|
|
7
|
+
module Jsonrpc
|
|
8
|
+
Batch = Data.define(:messages) do
|
|
9
|
+
def self.load(data)
|
|
10
|
+
return InvalidMessage.new(data: data.inspect) if data.empty?
|
|
11
|
+
|
|
12
|
+
messages = data.map { |message| Message.load(message) }
|
|
13
|
+
new(messages)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_a = messages
|
|
17
|
+
alias_method :to_ary, :to_a
|
|
18
|
+
|
|
19
|
+
def as_json = to_a.map(&:as_json)
|
|
20
|
+
|
|
21
|
+
def to_json(...) = JSON.generate(to_a.map(&:as_json), ...)
|
|
22
|
+
alias_method :to_s, :to_json
|
|
23
|
+
|
|
24
|
+
def reply(&block)
|
|
25
|
+
to_a.filter_map do |message|
|
|
26
|
+
message.reply(&block)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def method_missing(method, *args, **kwargs, &block)
|
|
33
|
+
if messages.respond_to?(method)
|
|
34
|
+
messages.send(method, *args, **kwargs, &block)
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def respond_to_missing?(method, include_private = false)
|
|
41
|
+
messages.respond_to?(method, include_private) || super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright 2025 by Martin Emde
|
|
5
5
|
|
|
6
|
-
require_relative "message"
|
|
7
|
-
|
|
8
6
|
module Protocol
|
|
9
7
|
module Jsonrpc
|
|
10
8
|
class Connection
|
|
@@ -22,24 +20,24 @@ module Protocol
|
|
|
22
20
|
@framer.close
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
# Read
|
|
26
|
-
# @yield [Protocol::Jsonrpc::Message]
|
|
27
|
-
# @return [Protocol::Jsonrpc::Message] The
|
|
23
|
+
# Read the next message or batch of messages from the framer
|
|
24
|
+
# @yield [Protocol::Jsonrpc::Message] Each message is yielded to the block
|
|
25
|
+
# @return [Protocol::Jsonrpc::Message, Protocol::Jsonrpc::Batch] The message or batch of messages
|
|
28
26
|
def read(&block)
|
|
29
27
|
flush
|
|
30
28
|
frame = read_frame
|
|
31
29
|
message = Message.load(frame.unpack)
|
|
32
30
|
yield message if block_given?
|
|
33
31
|
message
|
|
32
|
+
rescue => e
|
|
33
|
+
InvalidMessage.new(error: e)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Write a message to the framer
|
|
37
|
-
# @param message [Protocol::Jsonrpc::Message, Array<Protocol::Jsonrpc::Message
|
|
37
|
+
# @param message [Protocol::Jsonrpc::Message, Array<Protocol::Jsonrpc::Message>, Batch] The message(s) to write
|
|
38
38
|
# @return [Boolean] True if successful
|
|
39
39
|
def write(message)
|
|
40
|
-
|
|
41
|
-
write_frame(frame)
|
|
42
|
-
true
|
|
40
|
+
write_frame Frame.pack(message)
|
|
43
41
|
end
|
|
44
42
|
|
|
45
43
|
# Low level read a frame from the framer
|
|
@@ -15,7 +15,7 @@ module Protocol
|
|
|
15
15
|
INVALID_PARAMS = -32_602
|
|
16
16
|
INTERNAL_ERROR = -32_603
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
ERROR_MESSAGES = Hash.new("Error").merge(
|
|
19
19
|
PARSE_ERROR => "Parse error",
|
|
20
20
|
INVALID_REQUEST => "Invalid Request",
|
|
21
21
|
METHOD_NOT_FOUND => "Method not found",
|
|
@@ -24,8 +24,10 @@ module Protocol
|
|
|
24
24
|
).freeze
|
|
25
25
|
|
|
26
26
|
# Factory method to create the appropriate error type
|
|
27
|
+
# @param code [Integer] The JSON-RPC error code
|
|
28
|
+
# @param message [String] The error message
|
|
29
|
+
# @param data [#as_json, #to_json] Serializable data to return to the client
|
|
27
30
|
# @param id [String, Integer] The request ID
|
|
28
|
-
# @param error [Hash] The error data from the JSON-RPC response
|
|
29
31
|
# @return [Error] The appropriate error instance
|
|
30
32
|
def self.from_message(code:, message:, data: nil, id: nil)
|
|
31
33
|
case code
|
|
@@ -44,33 +46,43 @@ module Protocol
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
def self.wrap(error)
|
|
49
|
+
def self.wrap(error, data: nil, id: nil)
|
|
48
50
|
case error
|
|
51
|
+
in nil
|
|
52
|
+
InvalidRequestError.new(data:, id:)
|
|
53
|
+
in String
|
|
54
|
+
InternalError.new(error, data:, id:)
|
|
49
55
|
in Hash
|
|
50
56
|
error = error.transform_keys(&:to_sym)
|
|
51
|
-
from_message(**error)
|
|
57
|
+
from_message(id: id, data: data, **error)
|
|
52
58
|
in Jsonrpc::Error
|
|
59
|
+
error.data ||= data if data
|
|
60
|
+
error.id ||= id if id
|
|
53
61
|
error
|
|
54
62
|
in JSON::ParserError
|
|
55
|
-
ParseError.new(error.message, data:
|
|
63
|
+
ParseError.new("Parse error: #{error.message}", data:, id:)
|
|
56
64
|
in StandardError
|
|
57
|
-
InternalError.new(error.message, data:
|
|
58
|
-
|
|
65
|
+
InternalError.new(error.message, data:, id:)
|
|
66
|
+
in Exception
|
|
59
67
|
raise error
|
|
68
|
+
else
|
|
69
|
+
raise ArgumentError, "Unknown error type: #{error.class}"
|
|
60
70
|
end
|
|
61
71
|
end
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
attr_accessor :data, :id
|
|
74
|
+
attr_reader :code
|
|
64
75
|
|
|
65
76
|
def initialize(message = nil, data: nil, id: nil)
|
|
66
77
|
message = nil if message&.empty?
|
|
67
|
-
|
|
78
|
+
message ||= ERROR_MESSAGES[code]
|
|
79
|
+
super(message)
|
|
68
80
|
@data = data
|
|
69
81
|
@id = id
|
|
70
82
|
end
|
|
71
83
|
|
|
72
84
|
def reply(id: @id)
|
|
73
|
-
|
|
85
|
+
ErrorResponse.new(id:, error: self)
|
|
74
86
|
end
|
|
75
87
|
|
|
76
88
|
def to_h
|
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright 2025 by Martin Emde
|
|
5
5
|
|
|
6
|
-
require_relative "message"
|
|
7
|
-
|
|
8
6
|
module Protocol
|
|
9
7
|
module Jsonrpc
|
|
10
|
-
|
|
8
|
+
ErrorResponse = Data.define(:id, :error, :jsonrpc) do
|
|
11
9
|
include Message
|
|
12
10
|
|
|
13
|
-
def initialize(id:, error:)
|
|
11
|
+
def initialize(id:, error:, jsonrpc: JSONRPC_VERSION)
|
|
14
12
|
unless id.nil? || id.is_a?(String) || id.is_a?(Numeric)
|
|
15
13
|
raise InvalidRequestError.new("ID must be nil, string or number", id: id)
|
|
16
14
|
end
|
|
@@ -20,7 +18,9 @@ module Protocol
|
|
|
20
18
|
super
|
|
21
19
|
end
|
|
22
20
|
|
|
23
|
-
def to_h = super.merge(
|
|
21
|
+
def to_h = super.merge(error: error.to_h)
|
|
22
|
+
|
|
23
|
+
def error? = true
|
|
24
24
|
|
|
25
25
|
def response? = true
|
|
26
26
|
end
|
|
@@ -4,44 +4,60 @@
|
|
|
4
4
|
# Copyright 2025 by Martin Emde
|
|
5
5
|
|
|
6
6
|
require "json"
|
|
7
|
-
require_relative "error"
|
|
8
7
|
|
|
9
8
|
module Protocol
|
|
10
9
|
module Jsonrpc
|
|
11
10
|
# Frame represents the raw JSON data structure of a JSON-RPC message
|
|
12
11
|
# before it's validated and converted into a proper Message object.
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
# Handles translation between JSON strings and Ruby Hashes and
|
|
13
|
+
# reading and writing to a stream.
|
|
14
|
+
Frame = Data.define(:raw_json) do
|
|
15
|
+
class << self
|
|
16
|
+
# Read a frame from the stream
|
|
17
|
+
# @param stream [IO] An objects that responds to `gets` and returns a String
|
|
18
|
+
# @return [Frame, nil] The parsed frame or nil if the stream is empty
|
|
19
|
+
def read(stream)
|
|
20
|
+
raw_json = stream.gets
|
|
21
|
+
return nil if raw_json.nil?
|
|
22
|
+
new(raw_json: raw_json.strip)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Pack a message into a frame
|
|
26
|
+
# @param message [Message, Array<Message>] The message to pack
|
|
27
|
+
# @return [Frame] an instance that can be written to a stream
|
|
28
|
+
# @raise [ArgumentError] if the message is not a Message or Array of Messages
|
|
29
|
+
def pack(message)
|
|
30
|
+
if message.is_a?(Array)
|
|
31
|
+
new(raw_json: message.map { |msg| as_json(msg) }.to_json)
|
|
32
|
+
else
|
|
33
|
+
new(raw_json: as_json(message).to_json)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private def as_json(message)
|
|
38
|
+
return message if message.is_a?(Hash)
|
|
39
|
+
return message.as_json if message.respond_to?(:as_json)
|
|
40
|
+
raise ArgumentError, "Invalid message type: #{message.class}. Must be a Hash or respond to :as_json."
|
|
41
|
+
end
|
|
22
42
|
end
|
|
23
43
|
|
|
24
|
-
# Unpack the
|
|
44
|
+
# Unpack the raw_json into a Hash representing the JSON object
|
|
45
|
+
# Symbolizes the keys of the Hash.
|
|
25
46
|
# @return [Hash] The parsed JSON object
|
|
47
|
+
# @raise [ParseError] if the JSON is invalid
|
|
26
48
|
def unpack
|
|
27
|
-
JSON.parse(
|
|
28
|
-
rescue JSON::ParserError => e
|
|
29
|
-
raise ParseError.new("Failed to parse message: #{e.message}", data: json)
|
|
49
|
+
JSON.parse(raw_json, symbolize_names: true)
|
|
30
50
|
end
|
|
31
51
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
new(json: message.map { |msg| msg.is_a?(Message) ? msg.as_json : msg }.to_json)
|
|
36
|
-
when Hash, Message
|
|
37
|
-
new(json: message.to_json)
|
|
38
|
-
else
|
|
39
|
-
raise ArgumentError, "Invalid message type: #{message.class}"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
52
|
+
def to_json(...) = raw_json
|
|
53
|
+
|
|
54
|
+
def to_s = raw_json
|
|
42
55
|
|
|
56
|
+
# Write the frame to a stream
|
|
57
|
+
# @param stream [IO] The stream to write to
|
|
58
|
+
# @return [void]
|
|
43
59
|
def write(stream)
|
|
44
|
-
stream.write("#{
|
|
60
|
+
stream.write("#{raw_json}\n")
|
|
45
61
|
end
|
|
46
62
|
end
|
|
47
63
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright 2025 by Martin Emde
|
|
5
|
+
|
|
6
|
+
module Protocol
|
|
7
|
+
module Jsonrpc
|
|
8
|
+
# When the message received is not valid JSON or not a valid JSON-RPC message,
|
|
9
|
+
# this class is returned in place of a normal Message.
|
|
10
|
+
# The error that would have been raised is returned as the error.
|
|
11
|
+
# This simplifies batch processing because invalid messages in the batch
|
|
12
|
+
# can be processed as part of the batch rather than raising and interrupting
|
|
13
|
+
# the batch processing.
|
|
14
|
+
InvalidMessage = Data.define(:error, :id) do
|
|
15
|
+
include Message
|
|
16
|
+
|
|
17
|
+
def initialize(error: nil, data: nil, id: nil)
|
|
18
|
+
error = Error.wrap(error, data:, id:)
|
|
19
|
+
super(error:, id: error.id)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def invalid? = true
|
|
23
|
+
|
|
24
|
+
def as_json = raise "InvalidMessage cannot be serialized"
|
|
25
|
+
|
|
26
|
+
def reply(...) = ErrorResponse.new(id:, error:)
|
|
27
|
+
|
|
28
|
+
def to_json(...) = raise "InvalidMessage cannot be serialized"
|
|
29
|
+
|
|
30
|
+
def to_s = raise "InvalidMessage cannot be serialized"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -6,18 +6,15 @@
|
|
|
6
6
|
require "json"
|
|
7
7
|
require "securerandom"
|
|
8
8
|
require "timeout"
|
|
9
|
-
require_relative "error"
|
|
10
9
|
|
|
11
10
|
module Protocol
|
|
12
11
|
module Jsonrpc
|
|
13
|
-
#
|
|
12
|
+
# Protocol::Jsonrpc::Message provides operations for creating and validating JSON-RPC messages.
|
|
14
13
|
# This class handles the pure functional aspects of JSON-RPC like:
|
|
15
14
|
# - Creating properly formatted request/notification messages
|
|
16
15
|
# - Validating incoming messages
|
|
17
16
|
# - Parsing responses and errors
|
|
18
17
|
module Message
|
|
19
|
-
JSONRPC_VERSION = "2.0"
|
|
20
|
-
|
|
21
18
|
class << self
|
|
22
19
|
# Validate, and return the JSON-RPC message or batch
|
|
23
20
|
# @param data [Hash, Array] The parsed message
|
|
@@ -29,76 +26,56 @@ module Protocol
|
|
|
29
26
|
when Hash
|
|
30
27
|
from_hash(data)
|
|
31
28
|
when Array
|
|
32
|
-
|
|
29
|
+
Batch.load(data)
|
|
33
30
|
else
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# This is wrong. It seems like we need something more like
|
|
39
|
-
# an enumerator where we can run the full parse, handle, response
|
|
40
|
-
# cycle for each item in the array, aggregating the results and
|
|
41
|
-
# errors and then returning the array.
|
|
42
|
-
#
|
|
43
|
-
# The problem is that the errors should get raised and then
|
|
44
|
-
# handled which turns them into ErrorMessages and then the errors
|
|
45
|
-
# get returned to the client.
|
|
46
|
-
#
|
|
47
|
-
# Ideally this array handling would be invisible to the connection
|
|
48
|
-
# which would just handle one at a time with the array wrapper
|
|
49
|
-
# being applied by a single place that handles the batching.
|
|
50
|
-
def from_array(array)
|
|
51
|
-
raise InvalidRequestError.new("Empty batch", data: array.inspect) if array.empty?
|
|
52
|
-
|
|
53
|
-
array.map do |msg|
|
|
54
|
-
from_hash(msg)
|
|
55
|
-
rescue => e
|
|
56
|
-
Error.wrap(e)
|
|
31
|
+
InvalidMessage.new(data: data.inspect)
|
|
57
32
|
end
|
|
58
33
|
end
|
|
59
34
|
|
|
60
35
|
# @param parsed [Hash] The parsed message
|
|
61
36
|
# @return [Message] The parsed message
|
|
62
37
|
def from_hash(parsed)
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
return InvalidMessage.new(data: parsed.inspect) unless parsed.is_a?(Hash)
|
|
39
|
+
|
|
40
|
+
jsonrpc = parsed[:jsonrpc]
|
|
65
41
|
|
|
66
42
|
case parsed
|
|
67
|
-
in {
|
|
68
|
-
|
|
69
|
-
in {
|
|
70
|
-
|
|
71
|
-
in {
|
|
72
|
-
|
|
73
|
-
in {
|
|
74
|
-
|
|
43
|
+
in {id:, error:}
|
|
44
|
+
ErrorResponse.new(id:, error: Error.from_message(**error), jsonrpc:)
|
|
45
|
+
in {id:, result:}
|
|
46
|
+
Response.new(id:, result:, jsonrpc:)
|
|
47
|
+
in {id:, method:}
|
|
48
|
+
Request.new(id:, method:, params: parsed[:params], jsonrpc:)
|
|
49
|
+
in {method:}
|
|
50
|
+
Notification.new(method:, params: parsed[:params], jsonrpc:)
|
|
75
51
|
else
|
|
76
|
-
|
|
52
|
+
InvalidMessage.new(data: parsed.inspect)
|
|
77
53
|
end
|
|
54
|
+
rescue => error
|
|
55
|
+
InvalidMessage.new(error:, data: parsed.inspect)
|
|
78
56
|
end
|
|
79
57
|
end
|
|
80
58
|
|
|
81
|
-
def to_h = {jsonrpc: JSONRPC_VERSION}
|
|
82
|
-
|
|
83
|
-
def to_hash = to_h
|
|
84
|
-
|
|
85
59
|
def as_json = to_h
|
|
86
60
|
|
|
87
61
|
def to_json(...) = JSON.generate(as_json, ...)
|
|
88
62
|
|
|
89
63
|
def to_s = to_json
|
|
90
64
|
|
|
65
|
+
# Is this a request? (Request)
|
|
66
|
+
def request? = false
|
|
67
|
+
|
|
68
|
+
# Is this a notification? (Notification)
|
|
69
|
+
def notification? = false
|
|
70
|
+
|
|
71
|
+
# Is this a response to a request? (Error or Response)
|
|
91
72
|
def response? = false
|
|
92
73
|
|
|
93
|
-
|
|
74
|
+
# Is this an error response? (ErrorResponse)
|
|
75
|
+
def error? = false
|
|
94
76
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
ErrorMessage.new(id:, error: result_or_error)
|
|
98
|
-
else
|
|
99
|
-
ResponseMessage.new(id:, result: result_or_error)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
77
|
+
# Is this an invalid message? (InvalidMessage)
|
|
78
|
+
def invalid? = false
|
|
102
79
|
end
|
|
103
80
|
end
|
|
104
81
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright 2025 by Martin Emde
|
|
5
|
+
|
|
6
|
+
module Protocol
|
|
7
|
+
module Jsonrpc
|
|
8
|
+
Notification = Data.define(:method, :params, :jsonrpc) do
|
|
9
|
+
include Message
|
|
10
|
+
|
|
11
|
+
def initialize(method:, params: nil, jsonrpc: JSONRPC_VERSION)
|
|
12
|
+
super
|
|
13
|
+
|
|
14
|
+
unless method.is_a?(String)
|
|
15
|
+
raise InvalidRequestError.new("Method must be a string", data: method.inspect)
|
|
16
|
+
end
|
|
17
|
+
unless params.nil? || params.is_a?(Array) || params.is_a?(Hash)
|
|
18
|
+
raise InvalidRequestError.new("Params must be an array or object", data: params.inspect)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
h = super
|
|
24
|
+
h.delete(:params) if params.nil?
|
|
25
|
+
h
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Compatibility with the Message interface, Notifications have no ID
|
|
29
|
+
def id = nil
|
|
30
|
+
|
|
31
|
+
# Compatibility with the Request
|
|
32
|
+
# Yields the notification for processing but ignores the result
|
|
33
|
+
def reply(*, &)
|
|
34
|
+
yield self if block_given?
|
|
35
|
+
nil
|
|
36
|
+
rescue
|
|
37
|
+
nil # JSON-RPC 2.0 spec says notifications should never return, even on error.
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def notification? = true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
# Copyright 2025 by Martin Emde
|
|
5
5
|
|
|
6
6
|
require "securerandom"
|
|
7
|
-
require_relative "message"
|
|
8
7
|
|
|
9
8
|
module Protocol
|
|
10
9
|
module Jsonrpc
|
|
11
|
-
|
|
10
|
+
Request = Data.define(:method, :params, :id, :jsonrpc) do
|
|
12
11
|
include Message
|
|
13
12
|
|
|
14
|
-
def initialize(method:, params: nil, id: SecureRandom.uuid)
|
|
13
|
+
def initialize(method:, params: nil, id: SecureRandom.uuid, jsonrpc: JSONRPC_VERSION)
|
|
15
14
|
unless method.is_a?(String)
|
|
16
15
|
raise InvalidRequestError.new("Method must be a string", data: method.inspect)
|
|
17
16
|
end
|
|
@@ -26,12 +25,32 @@ module Protocol
|
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def to_h
|
|
29
|
-
h = super
|
|
30
|
-
h
|
|
28
|
+
h = super
|
|
29
|
+
h.delete(:params) if params.nil?
|
|
31
30
|
h
|
|
32
31
|
end
|
|
33
32
|
|
|
34
|
-
def
|
|
33
|
+
def request? = true
|
|
34
|
+
|
|
35
|
+
def reply(*args, &)
|
|
36
|
+
if args.empty? && block_given?
|
|
37
|
+
begin
|
|
38
|
+
result_or_error = yield self
|
|
39
|
+
rescue => error
|
|
40
|
+
return ErrorResponse.new(id:, error:)
|
|
41
|
+
end
|
|
42
|
+
elsif args.length == 1
|
|
43
|
+
result_or_error = args.first
|
|
44
|
+
else
|
|
45
|
+
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 0 or 1)"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if result_or_error.is_a?(StandardError)
|
|
49
|
+
ErrorResponse.new(id:, error: result_or_error)
|
|
50
|
+
else
|
|
51
|
+
Response.new(id:, result: result_or_error)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
35
54
|
end
|
|
36
55
|
end
|
|
37
56
|
end
|
|
@@ -3,23 +3,19 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright 2025 by Martin Emde
|
|
5
5
|
|
|
6
|
-
require_relative "message"
|
|
7
|
-
|
|
8
6
|
module Protocol
|
|
9
7
|
module Jsonrpc
|
|
10
|
-
|
|
8
|
+
Response = Data.define(:id, :result, :jsonrpc) do
|
|
11
9
|
include Message
|
|
12
10
|
|
|
13
|
-
def initialize(id:, result:)
|
|
11
|
+
def initialize(id:, result:, jsonrpc: JSONRPC_VERSION)
|
|
14
12
|
unless id.nil? || id.is_a?(String) || id.is_a?(Numeric)
|
|
15
|
-
raise InvalidRequestError.new("ID must be nil, string or number", id:)
|
|
13
|
+
raise InvalidRequestError.new("ID must be nil, string, or number", id:)
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
super
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
def to_h = super.merge(id:, result:)
|
|
22
|
-
|
|
23
19
|
def response? = true
|
|
24
20
|
end
|
|
25
21
|
end
|
data/lib/protocol/jsonrpc.rb
CHANGED
|
@@ -3,16 +3,21 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright 2025 by Martin Emde
|
|
5
5
|
|
|
6
|
-
require_relative "jsonrpc/version"
|
|
7
|
-
require_relative "jsonrpc/error"
|
|
8
|
-
require_relative "jsonrpc/message"
|
|
9
|
-
require_relative "jsonrpc/connection"
|
|
10
|
-
require_relative "jsonrpc/error_message"
|
|
11
|
-
require_relative "jsonrpc/notification_message"
|
|
12
|
-
require_relative "jsonrpc/request_message"
|
|
13
|
-
require_relative "jsonrpc/response_message"
|
|
14
|
-
|
|
15
6
|
module Protocol
|
|
16
7
|
module Jsonrpc
|
|
8
|
+
JSONRPC_VERSION = "2.0"
|
|
17
9
|
end
|
|
18
10
|
end
|
|
11
|
+
|
|
12
|
+
require_relative "jsonrpc/version"
|
|
13
|
+
require_relative "jsonrpc/error"
|
|
14
|
+
require_relative "jsonrpc/message"
|
|
15
|
+
require_relative "jsonrpc/error_response"
|
|
16
|
+
require_relative "jsonrpc/invalid_message"
|
|
17
|
+
require_relative "jsonrpc/notification"
|
|
18
|
+
require_relative "jsonrpc/request"
|
|
19
|
+
require_relative "jsonrpc/response"
|
|
20
|
+
require_relative "jsonrpc/frame"
|
|
21
|
+
require_relative "jsonrpc/framer"
|
|
22
|
+
require_relative "jsonrpc/connection"
|
|
23
|
+
require_relative "jsonrpc/batch"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protocol-jsonrpc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Martin Emde
|
|
@@ -34,15 +34,17 @@ files:
|
|
|
34
34
|
- LICENSE.txt
|
|
35
35
|
- README.md
|
|
36
36
|
- lib/protocol/jsonrpc.rb
|
|
37
|
+
- lib/protocol/jsonrpc/batch.rb
|
|
37
38
|
- lib/protocol/jsonrpc/connection.rb
|
|
38
39
|
- lib/protocol/jsonrpc/error.rb
|
|
39
|
-
- lib/protocol/jsonrpc/
|
|
40
|
+
- lib/protocol/jsonrpc/error_response.rb
|
|
40
41
|
- lib/protocol/jsonrpc/frame.rb
|
|
41
42
|
- lib/protocol/jsonrpc/framer.rb
|
|
43
|
+
- lib/protocol/jsonrpc/invalid_message.rb
|
|
42
44
|
- lib/protocol/jsonrpc/message.rb
|
|
43
|
-
- lib/protocol/jsonrpc/
|
|
44
|
-
- lib/protocol/jsonrpc/
|
|
45
|
-
- lib/protocol/jsonrpc/
|
|
45
|
+
- lib/protocol/jsonrpc/notification.rb
|
|
46
|
+
- lib/protocol/jsonrpc/request.rb
|
|
47
|
+
- lib/protocol/jsonrpc/response.rb
|
|
46
48
|
- lib/protocol/jsonrpc/version.rb
|
|
47
49
|
- sig/protocol/jsonrpc.rbs
|
|
48
50
|
homepage: https://github.com/martinemde/protocol-jsonrpc
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the MIT License.
|
|
4
|
-
# Copyright 2025 by Martin Emde
|
|
5
|
-
|
|
6
|
-
require_relative "message"
|
|
7
|
-
|
|
8
|
-
module Protocol
|
|
9
|
-
module Jsonrpc
|
|
10
|
-
NotificationMessage = Data.define(:method, :params) do
|
|
11
|
-
include Message
|
|
12
|
-
|
|
13
|
-
def initialize(method:, params: nil)
|
|
14
|
-
super
|
|
15
|
-
|
|
16
|
-
unless method.is_a?(String)
|
|
17
|
-
raise InvalidRequestError.new("Method must be a string", data: method.inspect)
|
|
18
|
-
end
|
|
19
|
-
unless params.nil? || params.is_a?(Array) || params.is_a?(Hash)
|
|
20
|
-
raise InvalidRequestError.new("Params must be an array or object", data: params.inspect)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def to_h
|
|
25
|
-
h = super.merge(method:)
|
|
26
|
-
h[:params] = params if params
|
|
27
|
-
h
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def id = nil
|
|
31
|
-
|
|
32
|
-
def reply = nil
|
|
33
|
-
|
|
34
|
-
def response? = false
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|