jsonrpc-middleware 0.2.0 → 0.4.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/.aiexclude +1 -0
- data/.claude/commands/test.md +561 -0
- data/.claude/settings.local.json +2 -1
- data/.rubocop.yml +3 -0
- data/.tool-versions +1 -1
- data/CHANGELOG.md +22 -2
- data/README.md +47 -18
- data/Rakefile +1 -3
- data/examples/README.md +2 -1
- data/examples/rack/Gemfile.lock +6 -6
- data/examples/rack/app.rb +1 -4
- data/examples/rack-echo/Gemfile.lock +6 -6
- data/examples/rack-echo/app.rb +1 -4
- data/examples/rack-single-file/config.ru +1 -4
- data/examples/rails/Gemfile.lock +4 -4
- data/examples/rails/app/controllers/jsonrpc_controller.rb +1 -4
- data/examples/rails-single-file/README.md +23 -0
- data/examples/rails-single-file/config.ru +1 -4
- data/examples/rails-single-file-routing/README.md +53 -0
- data/examples/rails-single-file-routing/config.ru +62 -0
- data/examples/sinatra-classic/Gemfile.lock +8 -8
- data/examples/sinatra-classic/app.rb +1 -6
- data/examples/sinatra-modular/Gemfile.lock +8 -8
- data/examples/sinatra-modular/app.rb +1 -4
- data/lib/jsonrpc/batch_request.rb +32 -0
- data/lib/jsonrpc/railtie/method_constraint.rb +35 -0
- data/lib/jsonrpc/railtie.rb +1 -0
- data/lib/jsonrpc/version.rb +1 -1
- data/lib/jsonrpc.rb +7 -4
- metadata +10 -6
- data/examples/rails/.ruby-version +0 -1
data/README.md
CHANGED
@@ -1,12 +1,23 @@
|
|
1
|
-
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://jsonrpc-middleware.com" target="_blank">
|
3
|
+
<picture>
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="./.github/images/logo-dark.svg">
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="./.github/images/logo-light.svg">
|
6
|
+
<img alt="JSON-RPC Middleware Logo" src="./.github/images/logo-light.svg" width="600" height="120" style="max-width: 100%;">
|
7
|
+
</picture>
|
8
|
+
</a>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<div align="center">
|
2
12
|
|
3
13
|
[](https://badge.fury.io/rb/jsonrpc-middleware)
|
4
14
|

|
5
15
|
[](https://qlty.sh/gh/wilsonsilva/projects/jsonrpc-middleware)
|
6
16
|
[](https://qlty.sh/gh/wilsonsilva/projects/jsonrpc-middleware)
|
7
17
|
|
8
|
-
|
9
|
-
|
18
|
+
</div>
|
19
|
+
|
20
|
+
A Rack middleware implementing the JSON-RPC 2.0 protocol that integrates easily with all Rack-based applications (Rails, Sinatra, Hanami, etc).
|
10
21
|
|
11
22
|
## Table of contents
|
12
23
|
|
@@ -30,6 +41,24 @@ calls encoded in JSON.
|
|
30
41
|
- **Request validation**: Define request parameter specifications and validations
|
31
42
|
- **Helpers**: Convenient helper methods to simplify request and response processing
|
32
43
|
|
44
|
+
## 🏗️ Architecture
|
45
|
+
|
46
|
+
The gem integrates seamlessly into your Rack-based application:
|
47
|
+
|
48
|
+
```mermaid
|
49
|
+
block-beta
|
50
|
+
columns 4
|
51
|
+
|
52
|
+
App["Your app"]:4
|
53
|
+
Rails:1 Sinatra:1 RackApp["Other Rack-compatible framework"]:2
|
54
|
+
Middleware["JSON-RPC Middleware"]:4
|
55
|
+
Rack["Rack"]:4
|
56
|
+
HTTP["HTTP"]:4
|
57
|
+
|
58
|
+
classDef middlewareStyle fill:#ff6b6b,stroke:#d63031,stroke-width:2px,color:#fff
|
59
|
+
class Middleware middlewareStyle
|
60
|
+
```
|
61
|
+
|
33
62
|
## 📦 Installation
|
34
63
|
|
35
64
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -78,31 +107,26 @@ class App
|
|
78
107
|
@env = env
|
79
108
|
|
80
109
|
if jsonrpc_request?
|
81
|
-
|
82
|
-
jsonrpc_response(
|
110
|
+
sum = add(jsonrpc_request.params)
|
111
|
+
jsonrpc_response(sum)
|
83
112
|
elsif jsonrpc_notification?
|
84
|
-
|
113
|
+
add(jsonrpc_notification.params)
|
85
114
|
jsonrpc_notification_response
|
86
115
|
else
|
87
|
-
|
88
|
-
jsonrpc_batch_response(
|
116
|
+
results = add_in_batches(jsonrpc_batch)
|
117
|
+
jsonrpc_batch_response(results)
|
89
118
|
end
|
90
119
|
end
|
91
120
|
|
92
121
|
private
|
93
122
|
|
94
|
-
def
|
95
|
-
params = request_or_notification.params
|
96
|
-
|
123
|
+
def add(params)
|
97
124
|
addends = params.is_a?(Array) ? params : params['addends'] # Handle positional and named arguments
|
98
125
|
addends.sum
|
99
126
|
end
|
100
127
|
|
101
|
-
def
|
102
|
-
batch.
|
103
|
-
result = handle_single(request_or_notification)
|
104
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
105
|
-
end.compact
|
128
|
+
def add_in_batches(batch)
|
129
|
+
batch.process_each { |request_or_notification| add(request_or_notification.params) }
|
106
130
|
end
|
107
131
|
end
|
108
132
|
|
@@ -110,9 +134,14 @@ use JSONRPC::Middleware
|
|
110
134
|
run App.new
|
111
135
|
```
|
112
136
|
|
113
|
-
This will give you a fully-featured JSON-RPC server
|
137
|
+
This will give you a fully-featured JSON-RPC server, capable of:
|
138
|
+
- Handling JSON-RPC requests, notifications __and batches__
|
139
|
+
- Validating the allowed JSON-RPC methods (e.g. allow only `add`)
|
140
|
+
- Validating the JSON-RPC method parameters (e.g. allow only non-empty arrays of numbers)
|
141
|
+
- Accept positional and named parameters (`params: [5, 5]`, `params: { addends: [5, 5] }`)
|
142
|
+
- Respond successfully or erroneously, according to the specification
|
114
143
|
|
115
|
-
For more advanced setups, check the [examples](https://github.com/wilsonsilva/jsonrpc-middleware/blob/main/examples/README.md).
|
144
|
+
For more advanced setups, or other frameworks such as Rails or Sinatra, check the [examples](https://github.com/wilsonsilva/jsonrpc-middleware/blob/main/examples/README.md).
|
116
145
|
|
117
146
|
## 📚 Documentation
|
118
147
|
|
data/Rakefile
CHANGED
@@ -14,9 +14,7 @@ yardstick_options = YAML.load_file('.yardstick.yml')
|
|
14
14
|
|
15
15
|
Bundler::Audit::Task.new
|
16
16
|
RSpec::Core::RakeTask.new(:spec)
|
17
|
-
RuboCop::RakeTask.new
|
18
|
-
task.requires << 'rubocop-yard'
|
19
|
-
end
|
17
|
+
RuboCop::RakeTask.new
|
20
18
|
YARD::Rake::YardocTask.new
|
21
19
|
YardJunk::Rake.define_task
|
22
20
|
Yardstick::Rake::Measurement.new(:yardstick_measure, yardstick_options)
|
data/examples/README.md
CHANGED
@@ -7,8 +7,9 @@ This directory contains example implementations of JSON-RPC servers using the js
|
|
7
7
|
- [**rack-echo**](./rack-echo/) - Echo server using Rack with helpers
|
8
8
|
- [**rack-single-file**](./rack-single-file/) - Minimal single-file example with bundler/inline
|
9
9
|
- [**rack**](./rack/) - Calculator server using pure Rack
|
10
|
-
- [**rails**](./rails
|
10
|
+
- [**rails**](./rails/) - Calculator server using Rails
|
11
11
|
- [**rails-single-file**](./rails-single-file/) - Echo server using Rails with bundler/inline
|
12
|
+
- [**rails-single-file-routing**](./rails-single-file-routing/) - Echo server using Rails with method-specific routing
|
12
13
|
- [**sinatra-classic**](./sinatra-classic/) - Calculator server using classic Sinatra
|
13
14
|
- [**sinatra-modular**](./sinatra-modular/) - Calculator server using modular Sinatra
|
14
15
|
|
data/examples/rack/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
jsonrpc-middleware (0.
|
4
|
+
jsonrpc-middleware (0.3.0)
|
5
5
|
dry-validation (~> 1.11)
|
6
6
|
zeitwerk (~> 2.7)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
bigdecimal (3.
|
11
|
+
bigdecimal (3.2.2)
|
12
12
|
concurrent-ruby (1.3.5)
|
13
13
|
dry-configurable (1.3.0)
|
14
14
|
dry-core (~> 1.1)
|
@@ -32,7 +32,7 @@ GEM
|
|
32
32
|
dry-logic (~> 1.5)
|
33
33
|
dry-types (~> 1.8)
|
34
34
|
zeitwerk (~> 2.6)
|
35
|
-
dry-types (1.8.
|
35
|
+
dry-types (1.8.3)
|
36
36
|
bigdecimal (~> 3.0)
|
37
37
|
concurrent-ruby (~> 1.0)
|
38
38
|
dry-core (~> 1.0)
|
@@ -49,10 +49,10 @@ GEM
|
|
49
49
|
nio4r (2.7.4)
|
50
50
|
puma (6.6.0)
|
51
51
|
nio4r (~> 2.0)
|
52
|
-
rack (3.1.
|
52
|
+
rack (3.1.16)
|
53
53
|
rackup (2.2.1)
|
54
54
|
rack (>= 3)
|
55
|
-
zeitwerk (2.7.
|
55
|
+
zeitwerk (2.7.3)
|
56
56
|
|
57
57
|
PLATFORMS
|
58
58
|
arm64-darwin-24
|
@@ -65,4 +65,4 @@ DEPENDENCIES
|
|
65
65
|
rackup
|
66
66
|
|
67
67
|
BUNDLED WITH
|
68
|
-
2.
|
68
|
+
2.7.0
|
data/examples/rack/app.rb
CHANGED
@@ -40,9 +40,6 @@ class App
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def handle_batch(batch)
|
43
|
-
batch.
|
44
|
-
result = handle_single(request_or_notification)
|
45
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
46
|
-
end.compact
|
43
|
+
batch.process_each { |request_or_notification| handle_single(request_or_notification) }
|
47
44
|
end
|
48
45
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
jsonrpc-middleware (0.
|
4
|
+
jsonrpc-middleware (0.2.0)
|
5
5
|
dry-validation (~> 1.11)
|
6
6
|
zeitwerk (~> 2.7)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
bigdecimal (3.
|
11
|
+
bigdecimal (3.2.2)
|
12
12
|
concurrent-ruby (1.3.5)
|
13
13
|
dry-configurable (1.3.0)
|
14
14
|
dry-core (~> 1.1)
|
@@ -32,7 +32,7 @@ GEM
|
|
32
32
|
dry-logic (~> 1.5)
|
33
33
|
dry-types (~> 1.8)
|
34
34
|
zeitwerk (~> 2.6)
|
35
|
-
dry-types (1.8.
|
35
|
+
dry-types (1.8.3)
|
36
36
|
bigdecimal (~> 3.0)
|
37
37
|
concurrent-ruby (~> 1.0)
|
38
38
|
dry-core (~> 1.0)
|
@@ -49,10 +49,10 @@ GEM
|
|
49
49
|
nio4r (2.7.4)
|
50
50
|
puma (6.6.0)
|
51
51
|
nio4r (~> 2.0)
|
52
|
-
rack (3.1.
|
52
|
+
rack (3.1.16)
|
53
53
|
rackup (2.2.1)
|
54
54
|
rack (>= 3)
|
55
|
-
zeitwerk (2.7.
|
55
|
+
zeitwerk (2.7.3)
|
56
56
|
|
57
57
|
PLATFORMS
|
58
58
|
arm64-darwin-24
|
@@ -65,4 +65,4 @@ DEPENDENCIES
|
|
65
65
|
rackup
|
66
66
|
|
67
67
|
BUNDLED WITH
|
68
|
-
2.
|
68
|
+
2.7.0
|
data/examples/rack-echo/app.rb
CHANGED
@@ -27,10 +27,7 @@ class App
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def handle_batch(batch)
|
30
|
-
batch.
|
31
|
-
result = handle_single(request_or_notification)
|
32
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
33
|
-
end.compact
|
30
|
+
batch.process_each { |request_or_notification| handle_single(request_or_notification) }
|
34
31
|
end
|
35
32
|
end
|
36
33
|
|
@@ -43,10 +43,7 @@ class App
|
|
43
43
|
def handle_single(request_or_notification) = request_or_notification.params
|
44
44
|
|
45
45
|
def handle_batch(batch)
|
46
|
-
batch.
|
47
|
-
result = handle_single(request_or_notification)
|
48
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
49
|
-
end.compact
|
46
|
+
batch.process_each { |request_or_notification| handle_single(request_or_notification) }
|
50
47
|
end
|
51
48
|
end
|
52
49
|
|
data/examples/rails/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
jsonrpc-middleware (0.
|
4
|
+
jsonrpc-middleware (0.2.0)
|
5
5
|
dry-validation (~> 1.11)
|
6
6
|
zeitwerk (~> 2.7)
|
7
7
|
|
@@ -126,13 +126,13 @@ GEM
|
|
126
126
|
dry-initializer (~> 3.2)
|
127
127
|
dry-schema (~> 1.14)
|
128
128
|
zeitwerk (~> 2.6)
|
129
|
-
erb (5.0.
|
129
|
+
erb (5.0.2)
|
130
130
|
erubi (1.13.1)
|
131
131
|
globalid (1.2.1)
|
132
132
|
activesupport (>= 6.1)
|
133
133
|
i18n (1.14.7)
|
134
134
|
concurrent-ruby (~> 1.0)
|
135
|
-
io-console (0.8.
|
135
|
+
io-console (0.8.1)
|
136
136
|
irb (1.15.2)
|
137
137
|
pp (>= 0.6.0)
|
138
138
|
rdoc (>= 4.0.0)
|
@@ -258,4 +258,4 @@ DEPENDENCIES
|
|
258
258
|
rails (~> 8.0.2)
|
259
259
|
|
260
260
|
BUNDLED WITH
|
261
|
-
2.
|
261
|
+
2.7.0
|
@@ -36,9 +36,6 @@ class JsonrpcController < ApplicationController
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def handle_batch(batch)
|
39
|
-
batch.
|
40
|
-
result = handle_single(request_or_notification)
|
41
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
42
|
-
end.compact
|
39
|
+
batch.process_each { |request_or_notification| handle_single(request_or_notification) }
|
43
40
|
end
|
44
41
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Rails Single File
|
2
|
+
|
3
|
+
An echo server using Rails with bundler/inline in a single file.
|
4
|
+
|
5
|
+
## Running
|
6
|
+
|
7
|
+
```sh
|
8
|
+
bundle exec rackup
|
9
|
+
```
|
10
|
+
|
11
|
+
## API
|
12
|
+
|
13
|
+
The server implements an echo API with this procedure:
|
14
|
+
|
15
|
+
- `echo` - Returns the input message
|
16
|
+
|
17
|
+
## Example Requests
|
18
|
+
|
19
|
+
```sh
|
20
|
+
curl -X POST http://localhost:9292 \
|
21
|
+
-H "Content-Type: application/json" \
|
22
|
+
-d '{"jsonrpc": "2.0", "method": "echo", "params": {"message": "Hello, World!"}, "id": 1}'
|
23
|
+
```
|
@@ -59,10 +59,7 @@ class JsonrpcController < ActionController::Base
|
|
59
59
|
def handle_single(request_or_notification) = request_or_notification.params
|
60
60
|
|
61
61
|
def handle_batch(batch)
|
62
|
-
batch.
|
63
|
-
result = handle_single(request_or_notification)
|
64
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
65
|
-
end.compact
|
62
|
+
batch.process_each { |request_or_notification| handle_single(request_or_notification) }
|
66
63
|
end
|
67
64
|
end
|
68
65
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Rails Single File Routing
|
2
|
+
|
3
|
+
Demonstrates routing JSON-RPC methods to different Rails controller actions.
|
4
|
+
|
5
|
+
## Highlights
|
6
|
+
|
7
|
+
Uses constraints to route JSON-RPC requests to different Rails controller actions:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class App < Rails::Application
|
11
|
+
# ...
|
12
|
+
routes.append do
|
13
|
+
post '/', to: 'jsonrpc#echo', constraints: JSONRPC::MethodConstraint.new('echo')
|
14
|
+
post '/', to: 'jsonrpc#ping', constraints: JSONRPC::MethodConstraint.new('ping')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class JsonrpcController < ActionController::Base
|
19
|
+
# POST /
|
20
|
+
def echoc
|
21
|
+
render jsonrpc: jsonrpc_request.params
|
22
|
+
end
|
23
|
+
|
24
|
+
# POST /
|
25
|
+
def ping
|
26
|
+
render jsonrpc: 'pong'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
## Running
|
32
|
+
|
33
|
+
```sh
|
34
|
+
bundle exec rackup
|
35
|
+
```
|
36
|
+
|
37
|
+
## API
|
38
|
+
|
39
|
+
The server implements an echo API with these procedures:
|
40
|
+
|
41
|
+
- `echo` - Returns the input message
|
42
|
+
- `ping` - Returns "pong"
|
43
|
+
|
44
|
+
## Example Requests
|
45
|
+
|
46
|
+
```sh
|
47
|
+
curl -X POST http://localhost:9292 \
|
48
|
+
-H "Content-Type: application/json" \
|
49
|
+
-d '{"jsonrpc": "2.0", "method": "echo", "params": {"message": "Hello, World!"}, "id": 1}'
|
50
|
+
curl -X POST http://localhost:9292 \
|
51
|
+
-H "Content-Type: application/json" \
|
52
|
+
-d '{"jsonrpc": "2.0""method": "ping", "params": {}, "id": 2}'
|
53
|
+
```
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile(true) do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
|
8
|
+
gem 'rails', '~> 8.0.2'
|
9
|
+
gem 'puma', '~> 6.6.0'
|
10
|
+
gem 'jsonrpc-middleware', path: '../../', require: 'jsonrpc'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rails'
|
14
|
+
require 'action_controller/railtie'
|
15
|
+
|
16
|
+
JSONRPC.configure do |config|
|
17
|
+
config.rescue_internal_errors = true # set to +false+ if you want to raise JSONRPC::InternalError manually
|
18
|
+
|
19
|
+
procedure(:echo) do
|
20
|
+
params do
|
21
|
+
required(:message).filled(:string)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
procedure(:ping) do
|
26
|
+
params do
|
27
|
+
# no params
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Define the application
|
33
|
+
class App < Rails::Application
|
34
|
+
config.root = __dir__
|
35
|
+
config.cache_classes = true
|
36
|
+
config.eager_load = true
|
37
|
+
config.active_support.deprecation = :stderr
|
38
|
+
config.consider_all_requests_local = true
|
39
|
+
config.active_support.to_time_preserves_timezone = :zone
|
40
|
+
config.logger = nil
|
41
|
+
config.hosts.clear
|
42
|
+
|
43
|
+
routes.append do
|
44
|
+
post '/', to: 'jsonrpc#echo', constraints: JSONRPC::MethodConstraint.new('echo')
|
45
|
+
post '/', to: 'jsonrpc#ping', constraints: JSONRPC::MethodConstraint.new('ping')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Define the JSONRPC controller
|
50
|
+
class JsonrpcController < ActionController::Base
|
51
|
+
def echo
|
52
|
+
render jsonrpc: jsonrpc_request.params
|
53
|
+
end
|
54
|
+
|
55
|
+
def ping
|
56
|
+
render jsonrpc: 'pong'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
App.initialize!
|
61
|
+
|
62
|
+
run App
|
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
jsonrpc-middleware (0.
|
4
|
+
jsonrpc-middleware (0.2.0)
|
5
5
|
dry-validation (~> 1.11)
|
6
6
|
zeitwerk (~> 2.7)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
base64 (0.
|
11
|
+
base64 (0.3.0)
|
12
12
|
bigdecimal (3.2.2)
|
13
13
|
concurrent-ruby (1.3.5)
|
14
14
|
dry-configurable (1.3.0)
|
@@ -47,18 +47,18 @@ GEM
|
|
47
47
|
dry-schema (~> 1.14)
|
48
48
|
zeitwerk (~> 2.6)
|
49
49
|
logger (1.7.0)
|
50
|
-
multi_json (1.
|
50
|
+
multi_json (1.16.0)
|
51
51
|
mustermann (3.0.3)
|
52
52
|
ruby2_keywords (~> 0.0.1)
|
53
53
|
nio4r (2.7.4)
|
54
54
|
puma (6.6.0)
|
55
55
|
nio4r (~> 2.0)
|
56
|
-
rack (3.1.
|
56
|
+
rack (3.1.16)
|
57
57
|
rack-protection (4.1.1)
|
58
58
|
base64 (>= 0.1.0)
|
59
59
|
logger (>= 1.6.0)
|
60
60
|
rack (>= 3.0.0, < 4)
|
61
|
-
rack-session (2.1.
|
61
|
+
rack-session (2.1.1)
|
62
62
|
base64 (>= 0.1.0)
|
63
63
|
rack (>= 3.0.0)
|
64
64
|
rackup (2.2.1)
|
@@ -77,8 +77,8 @@ GEM
|
|
77
77
|
rack-protection (= 4.1.1)
|
78
78
|
sinatra (= 4.1.1)
|
79
79
|
tilt (~> 2.0)
|
80
|
-
tilt (2.6.
|
81
|
-
zeitwerk (2.7.
|
80
|
+
tilt (2.6.1)
|
81
|
+
zeitwerk (2.7.3)
|
82
82
|
|
83
83
|
PLATFORMS
|
84
84
|
arm64-darwin-24
|
@@ -92,4 +92,4 @@ DEPENDENCIES
|
|
92
92
|
sinatra-contrib
|
93
93
|
|
94
94
|
BUNDLED WITH
|
95
|
-
2.
|
95
|
+
2.7.0
|
@@ -26,8 +26,6 @@ post '/' do
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
private
|
30
|
-
|
31
29
|
def handle_single(request_or_notification)
|
32
30
|
params = request_or_notification.params
|
33
31
|
|
@@ -47,8 +45,5 @@ def handle_single(request_or_notification)
|
|
47
45
|
end
|
48
46
|
|
49
47
|
def handle_batch(batch)
|
50
|
-
batch.
|
51
|
-
result = handle_single(request_or_notification)
|
52
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
53
|
-
end.compact
|
48
|
+
batch.process_each { |request_or_notification| handle_single(request_or_notification) }
|
54
49
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
jsonrpc-middleware (0.
|
4
|
+
jsonrpc-middleware (0.2.0)
|
5
5
|
dry-validation (~> 1.11)
|
6
6
|
zeitwerk (~> 2.7)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
base64 (0.
|
11
|
+
base64 (0.3.0)
|
12
12
|
bigdecimal (3.2.2)
|
13
13
|
concurrent-ruby (1.3.5)
|
14
14
|
dry-configurable (1.3.0)
|
@@ -47,18 +47,18 @@ GEM
|
|
47
47
|
dry-schema (~> 1.14)
|
48
48
|
zeitwerk (~> 2.6)
|
49
49
|
logger (1.7.0)
|
50
|
-
multi_json (1.
|
50
|
+
multi_json (1.16.0)
|
51
51
|
mustermann (3.0.3)
|
52
52
|
ruby2_keywords (~> 0.0.1)
|
53
53
|
nio4r (2.7.4)
|
54
54
|
puma (6.6.0)
|
55
55
|
nio4r (~> 2.0)
|
56
|
-
rack (3.1.
|
56
|
+
rack (3.1.16)
|
57
57
|
rack-protection (4.1.1)
|
58
58
|
base64 (>= 0.1.0)
|
59
59
|
logger (>= 1.6.0)
|
60
60
|
rack (>= 3.0.0, < 4)
|
61
|
-
rack-session (2.1.
|
61
|
+
rack-session (2.1.1)
|
62
62
|
base64 (>= 0.1.0)
|
63
63
|
rack (>= 3.0.0)
|
64
64
|
rackup (2.2.1)
|
@@ -77,8 +77,8 @@ GEM
|
|
77
77
|
rack-protection (= 4.1.1)
|
78
78
|
sinatra (= 4.1.1)
|
79
79
|
tilt (~> 2.0)
|
80
|
-
tilt (2.6.
|
81
|
-
zeitwerk (2.7.
|
80
|
+
tilt (2.6.1)
|
81
|
+
zeitwerk (2.7.3)
|
82
82
|
|
83
83
|
PLATFORMS
|
84
84
|
arm64-darwin-24
|
@@ -92,4 +92,4 @@ DEPENDENCIES
|
|
92
92
|
sinatra-contrib
|
93
93
|
|
94
94
|
BUNDLED WITH
|
95
|
-
2.
|
95
|
+
2.7.0
|
@@ -49,9 +49,6 @@ class App < Sinatra::Base
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def handle_batch(batch)
|
52
|
-
batch.
|
53
|
-
result = handle_single(request_or_notification)
|
54
|
-
JSONRPC::Response.new(id: request_or_notification.id, result:) if request_or_notification.is_a?(JSONRPC::Request)
|
55
|
-
end.compact
|
52
|
+
batch.process_each { |request_or_notification| handle_single(request_or_notification) }
|
56
53
|
end
|
57
54
|
end
|
@@ -137,6 +137,38 @@ module JSONRPC
|
|
137
137
|
requests.empty?
|
138
138
|
end
|
139
139
|
|
140
|
+
# Handles each request/notification in the batch and returns responses
|
141
|
+
#
|
142
|
+
# @api public
|
143
|
+
#
|
144
|
+
# @example Handle batch with a block
|
145
|
+
# batch.process_each do |request_or_notification|
|
146
|
+
# # Process the request/notification
|
147
|
+
# result = some_processing(request_or_notification.params)
|
148
|
+
# result
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# @yield [request_or_notification] Yields each request/notification in the batch
|
152
|
+
#
|
153
|
+
# @yieldparam request_or_notification [JSONRPC::Request, JSONRPC::Notification] a request or notification
|
154
|
+
# in the batch
|
155
|
+
#
|
156
|
+
# @yieldreturn [Object] the result of processing the request. Notifications yield no results.
|
157
|
+
#
|
158
|
+
# @return [Array<JSONRPC::Response>] responses for requests only (notifications return no response)
|
159
|
+
#
|
160
|
+
def process_each
|
161
|
+
raise ArgumentError, 'Block required' unless block_given?
|
162
|
+
|
163
|
+
flat_map do |request_or_notification|
|
164
|
+
result = yield(request_or_notification)
|
165
|
+
|
166
|
+
if request_or_notification.is_a?(JSONRPC::Request)
|
167
|
+
JSONRPC::Response.new(id: request_or_notification.id, result:)
|
168
|
+
end
|
169
|
+
end.compact
|
170
|
+
end
|
171
|
+
|
140
172
|
private
|
141
173
|
|
142
174
|
# Validates the requests array
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# This constraint allows Rails routes to be matched based on the JSON-RPC
|
5
|
+
# method name in the request, enabling method-specific routing.
|
6
|
+
#
|
7
|
+
# @example Using in Rails routes
|
8
|
+
# post '/', to: 'jsonrpc#echo', constraints: JSONRPC::Railtie::MethodConstraint.new('echo')
|
9
|
+
# post '/', to: 'jsonrpc#ping', constraints: JSONRPC::Railtie::MethodConstraint.new('ping')
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
class MethodConstraint
|
14
|
+
# Initialize a new method constraint
|
15
|
+
#
|
16
|
+
# @param jsonrpc_method_name [String] The JSON-RPC method name to match against
|
17
|
+
#
|
18
|
+
def initialize(jsonrpc_method_name)
|
19
|
+
@jsonrpc_method_name = jsonrpc_method_name
|
20
|
+
end
|
21
|
+
|
22
|
+
# Check if the request matches the configured method name
|
23
|
+
#
|
24
|
+
# @param request [ActionDispatch::Request] The Rails request object
|
25
|
+
# @return [Boolean] true if the JSON-RPC method matches, false otherwise
|
26
|
+
#
|
27
|
+
def matches?(request)
|
28
|
+
jsonrpc_request = request.env['jsonrpc.request']
|
29
|
+
|
30
|
+
return false unless jsonrpc_request
|
31
|
+
|
32
|
+
jsonrpc_request.method == @jsonrpc_method_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|