jsonrpc-middleware 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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.aiignore +6 -1
  3. data/.claude/agents/entire-search.md +25 -0
  4. data/.claude/agents/rbs-specialist.md +89 -0
  5. data/.claude/settings.json +84 -0
  6. data/.devcontainer/devcontainer.json +17 -0
  7. data/.dockerignore +16 -0
  8. data/.entire/.gitignore +5 -0
  9. data/.entire/settings.json +4 -0
  10. data/.rubocop.yml +26 -1
  11. data/.tool-versions +1 -1
  12. data/.yard-lint.yml +283 -0
  13. data/AGENTS.md +142 -0
  14. data/CHANGELOG.md +43 -0
  15. data/CLAUDE.md +2 -113
  16. data/Dockerfile +144 -0
  17. data/README.md +10 -17
  18. data/Rakefile +78 -26
  19. data/examples/README.md +9 -0
  20. data/examples/procedures.rb +3 -1
  21. data/examples/rack/Gemfile.lock +11 -2
  22. data/examples/rack-echo/Gemfile.lock +11 -2
  23. data/examples/rails/Gemfile.lock +18 -23
  24. data/examples/rails/config/initializers/jsonrpc.rb +1 -1
  25. data/examples/rails-routing-dsl/config.ru +5 -5
  26. data/examples/rails-single-file/config.ru +1 -1
  27. data/examples/rails-single-file-routing/README.md +38 -3
  28. data/examples/rails-single-file-routing/config.ru +18 -1
  29. data/examples/sinatra-classic/Gemfile.lock +11 -3
  30. data/examples/sinatra-modular/Gemfile.lock +11 -3
  31. data/lib/jsonrpc/batch_request.rb +9 -12
  32. data/lib/jsonrpc/batch_response.rb +7 -9
  33. data/lib/jsonrpc/configuration.rb +43 -4
  34. data/lib/jsonrpc/error.rb +8 -9
  35. data/lib/jsonrpc/errors/internal_error.rb +2 -0
  36. data/lib/jsonrpc/errors/invalid_params_error.rb +2 -0
  37. data/lib/jsonrpc/errors/invalid_request_error.rb +2 -0
  38. data/lib/jsonrpc/errors/method_not_found_error.rb +2 -0
  39. data/lib/jsonrpc/errors/parse_error.rb +2 -0
  40. data/lib/jsonrpc/helpers.rb +6 -0
  41. data/lib/jsonrpc/middleware.rb +15 -13
  42. data/lib/jsonrpc/notification.rb +8 -9
  43. data/lib/jsonrpc/parser.rb +22 -19
  44. data/lib/jsonrpc/railtie/batch_constraint.rb +1 -0
  45. data/lib/jsonrpc/railtie/mapper_extension.rb +2 -2
  46. data/lib/jsonrpc/railtie/method_constraint.rb +9 -0
  47. data/lib/jsonrpc/railtie/routes_dsl.rb +10 -15
  48. data/lib/jsonrpc/railtie.rb +4 -2
  49. data/lib/jsonrpc/request.rb +12 -84
  50. data/lib/jsonrpc/response.rb +11 -60
  51. data/lib/jsonrpc/types.rb +13 -0
  52. data/lib/jsonrpc/validator.rb +14 -4
  53. data/lib/jsonrpc/version.rb +1 -1
  54. data/lib/jsonrpc.rb +5 -0
  55. data/rbs_collection.lock.yaml +476 -0
  56. data/rbs_collection.yaml +21 -0
  57. data/sig/jsonrpc/batch_request.rbs +17 -0
  58. data/sig/jsonrpc/batch_response.rbs +17 -0
  59. data/sig/jsonrpc/configuration.rbs +18 -0
  60. data/sig/jsonrpc/error.rbs +17 -0
  61. data/sig/jsonrpc/errors/internal_error.rbs +5 -0
  62. data/sig/jsonrpc/errors/invalid_params_error.rbs +5 -0
  63. data/sig/jsonrpc/errors/invalid_request_error.rbs +5 -0
  64. data/sig/jsonrpc/errors/method_not_found_error.rbs +5 -0
  65. data/sig/jsonrpc/errors/parse_error.rbs +5 -0
  66. data/sig/jsonrpc/middleware.rbs +20 -3
  67. data/sig/jsonrpc/notification.rbs +15 -0
  68. data/sig/jsonrpc/parser.rbs +7 -1
  69. data/sig/jsonrpc/request.rbs +18 -0
  70. data/sig/jsonrpc/response.rbs +19 -0
  71. data/sig/jsonrpc/validator.rbs +8 -0
  72. data/sig/jsonrpc.rbs +3 -156
  73. data/sig/multi_json.rbs +17 -0
  74. data/sig/type_definitions.rbs +11 -0
  75. data/sig/zeitwerk.rbs +10 -0
  76. metadata +61 -9
  77. data/.claude/commands/document.md +0 -105
  78. data/.claude/commands/test.md +0 -561
  79. data/.claude/docs/yard.md +0 -602
  80. data/.claude/settings.local.json +0 -11
  81. data/.yardstick.yml +0 -22
data/Rakefile CHANGED
@@ -1,29 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'English' # For $CHILD_STATUS
3
4
  require 'bundler/audit/task'
4
5
  require 'bundler/gem_tasks'
5
6
  require 'rspec/core/rake_task'
6
7
  require 'rubocop/rake_task'
7
8
  require 'yaml'
9
+ require 'yard'
8
10
  require 'yard/rake/yardoc_task'
9
- require 'yard-junk/rake'
10
- require 'yardstick/rake/measurement'
11
- require 'yardstick/rake/verify'
12
-
13
- yardstick_options = YAML.load_file('.yardstick.yml')
14
11
 
15
12
  Bundler::Audit::Task.new
16
13
  RSpec::Core::RakeTask.new(:spec)
17
14
  RuboCop::RakeTask.new
18
15
  YARD::Rake::YardocTask.new
19
- YardJunk::Rake.define_task
20
- Yardstick::Rake::Measurement.new(:yardstick_measure, yardstick_options)
21
- Yardstick::Rake::Verify.new(:verify_measurements, yardstick_options)
22
16
 
23
17
  task default: %i[spec rubocop]
24
18
 
25
19
  # Remove the report on rake clobber
26
- CLEAN.include('measurements', 'doc', '.yardoc', 'tmp')
20
+ CLEAN.include('doc', '.yardoc', 'tmp')
27
21
 
28
22
  # Delete these files and folders when running rake clobber.
29
23
  CLOBBER.include('coverage', '.rspec_status')
@@ -32,11 +26,10 @@ desc 'Run spec with coverage'
32
26
  task :coverage do
33
27
  ENV['COVERAGE'] = 'true'
34
28
  Rake::Task['spec'].execute
35
- `open coverage/index.html`
36
29
  end
37
30
 
38
31
  desc 'Test, lint and perform security and documentation audits'
39
- task qa: %w[spec rubocop yard:junk verify_measurements bundle:audit]
32
+ task qa: %w[spec rubocop yard:lint bundle:audit]
40
33
 
41
34
  namespace :yard do
42
35
  desc 'Format YARD documentation'
@@ -94,59 +87,118 @@ namespace :yard do
94
87
  puts
95
88
  puts 'Done!'
96
89
  end
90
+
91
+ desc 'Lint YARD documentation'
92
+ task :lint do
93
+ system 'bundle exec yard-lint lib/'
94
+ end
97
95
  end
98
96
 
99
97
  namespace :examples do
100
98
  desc 'Run bundle install on all example folders'
101
99
  task :bundle_install do
102
100
  examples_dir = File.join(Dir.pwd, 'examples')
103
-
101
+
104
102
  unless Dir.exist?(examples_dir)
105
103
  puts 'Examples directory not found'
106
104
  exit 1
107
105
  end
108
-
106
+
109
107
  example_folders = Dir.glob(File.join(examples_dir, '*')).select { |path| Dir.exist?(path) }
110
-
108
+
111
109
  if example_folders.empty?
112
110
  puts 'No example folders found'
113
111
  return
114
112
  end
115
-
113
+
116
114
  puts "Found #{example_folders.length} example folders:"
117
115
  example_folders.each { |folder| puts " - #{File.basename(folder)}" }
118
116
  puts
119
-
117
+
120
118
  failed_folders = []
121
-
119
+
122
120
  example_folders.each do |folder|
123
121
  gemfile_path = File.join(folder, 'Gemfile')
124
-
122
+
125
123
  unless File.exist?(gemfile_path)
126
124
  puts "Skipping #{File.basename(folder)} - no Gemfile found"
127
125
  next
128
126
  end
129
-
127
+
130
128
  puts "Running bundle install in #{File.basename(folder)}..."
131
-
129
+
132
130
  Dir.chdir(folder) do
133
131
  system('bundle install')
134
-
135
- unless $?.success?
132
+
133
+ if $CHILD_STATUS.success?
134
+ puts " ✓ Successfully installed gems in #{File.basename(folder)}"
135
+ else
136
136
  failed_folders << File.basename(folder)
137
137
  puts " ✗ Failed to bundle install in #{File.basename(folder)}"
138
+ end
139
+ end
140
+
141
+ puts
142
+ end
143
+
144
+ if failed_folders.empty?
145
+ puts 'All example folders processed successfully!'
146
+ else
147
+ puts "Failed to process #{failed_folders.length} folders: #{failed_folders.join(", ")}"
148
+ exit 1
149
+ end
150
+ end
151
+
152
+ desc 'Run bundle update on all example folders'
153
+ task :bundle_update do
154
+ examples_dir = File.join(Dir.pwd, 'examples')
155
+
156
+ unless Dir.exist?(examples_dir)
157
+ puts 'Examples directory not found'
158
+ exit 1
159
+ end
160
+
161
+ example_folders = Dir.glob(File.join(examples_dir, '*')).select { |path| Dir.exist?(path) }
162
+
163
+ if example_folders.empty?
164
+ puts 'No example folders found'
165
+ return
166
+ end
167
+
168
+ puts "Found #{example_folders.length} example folders:"
169
+ example_folders.each { |folder| puts " - #{File.basename(folder)}" }
170
+ puts
171
+
172
+ failed_folders = []
173
+
174
+ example_folders.each do |folder|
175
+ gemfile_path = File.join(folder, 'Gemfile')
176
+
177
+ unless File.exist?(gemfile_path)
178
+ puts "Skipping #{File.basename(folder)} - no Gemfile found"
179
+ next
180
+ end
181
+
182
+ puts "Running bundle update in #{File.basename(folder)}..."
183
+
184
+ Dir.chdir(folder) do
185
+ system('bundle update --all')
186
+
187
+ if $CHILD_STATUS.success?
188
+ puts " ✓ Successfully updated gems in #{File.basename(folder)}"
138
189
  else
139
- puts " ✓ Successfully installed gems in #{File.basename(folder)}"
190
+ failed_folders << File.basename(folder)
191
+ puts " ✗ Failed to bundle update in #{File.basename(folder)}"
140
192
  end
141
193
  end
142
-
194
+
143
195
  puts
144
196
  end
145
-
197
+
146
198
  if failed_folders.empty?
147
199
  puts 'All example folders processed successfully!'
148
200
  else
149
- puts "Failed to process #{failed_folders.length} folders: #{failed_folders.join(', ')}"
201
+ puts "Failed to process #{failed_folders.length} folders: #{failed_folders.join(", ")}"
150
202
  exit 1
151
203
  end
152
204
  end
data/examples/README.md CHANGED
@@ -10,6 +10,7 @@ This directory contains example implementations of JSON-RPC servers using the js
10
10
  - [**rails**](./rails/) - Calculator server using Rails
11
11
  - [**rails-single-file**](./rails-single-file/) - Echo server using Rails with bundler/inline
12
12
  - [**rails-single-file-routing**](./rails-single-file-routing/) - Echo server using Rails with method-specific routing
13
+ - [**rails-routing-dsl**](./rails-routing-dsl/) - Smart home control server showcasing Rails JSON-RPC routing DSL
13
14
  - [**sinatra-classic**](./sinatra-classic/) - Calculator server using classic Sinatra
14
15
  - [**sinatra-modular**](./sinatra-modular/) - Calculator server using modular Sinatra
15
16
 
@@ -27,6 +28,14 @@ The echo examples implement:
27
28
 
28
29
  - `echo` - Returns the input message
29
30
 
31
+ The rails-routing-dsl example implements a smart home control API:
32
+
33
+ - `on` / `off` - Control main home automation system
34
+ - `lights.on` / `lights.off` - Control lights
35
+ - `climate.on` / `climate.off` - Control climate system
36
+ - `climate.fan.on` / `climate.fan.off` - Control climate fan
37
+ - Batch request support for multiple operations
38
+
30
39
  ## Running Examples
31
40
 
32
41
  Each example directory contains its own README with specific instructions. Generally:
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  JSONRPC.configure do |config|
4
+ # Logger instance (default: Logger.new($stdout, progname: 'JSONRPC'))
5
+ config.logger = Logger.new($stdout, progname: 'JSONRPC')
4
6
  config.log_internal_errors = true # Log internal error backtraces (default: true)
5
7
  config.log_request_validation_errors = true # Log JSON-RPC request validation errors (default: false)
6
8
  config.render_internal_errors = true # Render detailed internal error information in responses (default: true)
@@ -14,7 +16,7 @@ JSONRPC.configure do |config|
14
16
  end
15
17
 
16
18
  rule(:addends) do
17
- key.failure('must contain at least one addend') if value.empty?
19
+ key.failure('must contain at least two addends') if value.size < 2
18
20
  end
19
21
  end
20
22
 
@@ -1,8 +1,10 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.4.0)
4
+ jsonrpc-middleware (0.6.0)
5
+ dry-struct (~> 1.8)
5
6
  dry-validation (~> 1.11)
7
+ multi_json (~> 1.20)
6
8
  zeitwerk (~> 2.7)
7
9
 
8
10
  GEM
@@ -32,6 +34,11 @@ GEM
32
34
  dry-logic (~> 1.5)
33
35
  dry-types (~> 1.8)
34
36
  zeitwerk (~> 2.6)
37
+ dry-struct (1.8.0)
38
+ dry-core (~> 1.1)
39
+ dry-types (~> 1.8, >= 1.8.2)
40
+ ice_nine (~> 0.11)
41
+ zeitwerk (~> 2.6)
35
42
  dry-types (1.8.3)
36
43
  bigdecimal (~> 3.0)
37
44
  concurrent-ruby (~> 1.0)
@@ -45,7 +52,9 @@ GEM
45
52
  dry-initializer (~> 3.2)
46
53
  dry-schema (~> 1.14)
47
54
  zeitwerk (~> 2.6)
55
+ ice_nine (0.11.2)
48
56
  logger (1.7.0)
57
+ multi_json (1.21.1)
49
58
  nio4r (2.7.4)
50
59
  puma (6.6.0)
51
60
  nio4r (~> 2.0)
@@ -65,4 +74,4 @@ DEPENDENCIES
65
74
  rackup
66
75
 
67
76
  BUNDLED WITH
68
- 2.7.0
77
+ 4.0.8
@@ -1,8 +1,10 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.4.0)
4
+ jsonrpc-middleware (0.6.0)
5
+ dry-struct (~> 1.8)
5
6
  dry-validation (~> 1.11)
7
+ multi_json (~> 1.20)
6
8
  zeitwerk (~> 2.7)
7
9
 
8
10
  GEM
@@ -32,6 +34,11 @@ GEM
32
34
  dry-logic (~> 1.5)
33
35
  dry-types (~> 1.8)
34
36
  zeitwerk (~> 2.6)
37
+ dry-struct (1.8.0)
38
+ dry-core (~> 1.1)
39
+ dry-types (~> 1.8, >= 1.8.2)
40
+ ice_nine (~> 0.11)
41
+ zeitwerk (~> 2.6)
35
42
  dry-types (1.8.3)
36
43
  bigdecimal (~> 3.0)
37
44
  concurrent-ruby (~> 1.0)
@@ -45,7 +52,9 @@ GEM
45
52
  dry-initializer (~> 3.2)
46
53
  dry-schema (~> 1.14)
47
54
  zeitwerk (~> 2.6)
55
+ ice_nine (0.11.2)
48
56
  logger (1.7.0)
57
+ multi_json (1.21.1)
49
58
  nio4r (2.7.4)
50
59
  puma (6.6.0)
51
60
  nio4r (~> 2.0)
@@ -65,4 +74,4 @@ DEPENDENCIES
65
74
  rackup
66
75
 
67
76
  BUNDLED WITH
68
- 2.7.0
77
+ 4.0.8
@@ -1,8 +1,10 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.4.0)
4
+ jsonrpc-middleware (0.6.0)
5
+ dry-struct (~> 1.8)
5
6
  dry-validation (~> 1.11)
7
+ multi_json (~> 1.20)
6
8
  zeitwerk (~> 2.7)
7
9
 
8
10
  GEM
@@ -113,6 +115,11 @@ GEM
113
115
  dry-logic (~> 1.5)
114
116
  dry-types (~> 1.8)
115
117
  zeitwerk (~> 2.6)
118
+ dry-struct (1.8.0)
119
+ dry-core (~> 1.1)
120
+ dry-types (~> 1.8, >= 1.8.2)
121
+ ice_nine (~> 0.11)
122
+ zeitwerk (~> 2.6)
116
123
  dry-types (1.8.3)
117
124
  bigdecimal (~> 3.0)
118
125
  concurrent-ruby (~> 1.0)
@@ -132,6 +139,7 @@ GEM
132
139
  activesupport (>= 6.1)
133
140
  i18n (1.14.7)
134
141
  concurrent-ruby (~> 1.0)
142
+ ice_nine (0.11.2)
135
143
  io-console (0.8.1)
136
144
  irb (1.15.2)
137
145
  pp (>= 0.6.0)
@@ -148,7 +156,11 @@ GEM
148
156
  net-smtp
149
157
  marcel (1.0.4)
150
158
  mini_mime (1.1.5)
151
- minitest (5.25.5)
159
+ mini_portile2 (2.8.9)
160
+ minitest (6.0.2)
161
+ drb (~> 2.0)
162
+ prism (~> 1.5)
163
+ multi_json (1.21.1)
152
164
  net-imap (0.5.9)
153
165
  date
154
166
  net-protocol
@@ -159,25 +171,15 @@ GEM
159
171
  net-smtp (0.5.1)
160
172
  net-protocol
161
173
  nio4r (2.7.4)
162
- nokogiri (1.18.8-aarch64-linux-gnu)
163
- racc (~> 1.4)
164
- nokogiri (1.18.8-aarch64-linux-musl)
165
- racc (~> 1.4)
166
- nokogiri (1.18.8-arm-linux-gnu)
167
- racc (~> 1.4)
168
- nokogiri (1.18.8-arm-linux-musl)
174
+ nokogiri (1.18.8)
175
+ mini_portile2 (~> 2.8.2)
169
176
  racc (~> 1.4)
170
177
  nokogiri (1.18.8-arm64-darwin)
171
178
  racc (~> 1.4)
172
- nokogiri (1.18.8-x86_64-darwin)
173
- racc (~> 1.4)
174
- nokogiri (1.18.8-x86_64-linux-gnu)
175
- racc (~> 1.4)
176
- nokogiri (1.18.8-x86_64-linux-musl)
177
- racc (~> 1.4)
178
179
  pp (0.6.2)
179
180
  prettyprint
180
181
  prettyprint (0.2.0)
182
+ prism (1.9.0)
181
183
  psych (5.2.6)
182
184
  date
183
185
  stringio
@@ -242,14 +244,7 @@ GEM
242
244
  zeitwerk (2.7.3)
243
245
 
244
246
  PLATFORMS
245
- aarch64-linux-gnu
246
- aarch64-linux-musl
247
- arm-linux-gnu
248
- arm-linux-musl
249
247
  arm64-darwin
250
- x86_64-darwin
251
- x86_64-linux-gnu
252
- x86_64-linux-musl
253
248
 
254
249
  DEPENDENCIES
255
250
  debug
@@ -258,4 +253,4 @@ DEPENDENCIES
258
253
  rails (~> 8.0.2)
259
254
 
260
255
  BUNDLED WITH
261
- 2.7.0
256
+ 4.0.8
@@ -16,7 +16,7 @@ require_relative '../../../procedures'
16
16
  # end
17
17
  #
18
18
  # rule(:addends) do
19
- # key.failure('must contain at least one addend') if value.empty?
19
+ # key.failure('must contain at least two addends') if value.size < 2
20
20
  # end
21
21
  # end
22
22
  #
@@ -65,7 +65,7 @@ class App < Rails::Application
65
65
  end
66
66
 
67
67
  # Controller for main system operations
68
- class MainController < ActionController::Base
68
+ class MainController < ApplicationController
69
69
  def on
70
70
  render jsonrpc: { device: 'main_system', status: 'on' }
71
71
  end
@@ -76,7 +76,7 @@ class MainController < ActionController::Base
76
76
  end
77
77
 
78
78
  # Controller for lights operations
79
- class LightsController < ActionController::Base
79
+ class LightsController < ApplicationController
80
80
  def on
81
81
  render jsonrpc: { device: 'lights', status: 'on' }
82
82
  end
@@ -87,7 +87,7 @@ class LightsController < ActionController::Base
87
87
  end
88
88
 
89
89
  # Controller for climate operations
90
- class ClimateController < ActionController::Base
90
+ class ClimateController < ApplicationController
91
91
  def on
92
92
  render jsonrpc: { device: 'climate_system', status: 'on' }
93
93
  end
@@ -98,7 +98,7 @@ class ClimateController < ActionController::Base
98
98
  end
99
99
 
100
100
  # Controller for climate fan operations
101
- class FanController < ActionController::Base
101
+ class FanController < ApplicationController
102
102
  def on
103
103
  render jsonrpc: { device: 'fan', status: 'on' }
104
104
  end
@@ -109,7 +109,7 @@ class FanController < ActionController::Base
109
109
  end
110
110
 
111
111
  # Controller for batch operations
112
- class BatchController < ActionController::Base
112
+ class BatchController < ApplicationController
113
113
  def handle
114
114
  # Process each request in the batch and collect results
115
115
  results = jsonrpc_batch.process_each do |request_or_notification|
@@ -40,7 +40,7 @@ class App < Rails::Application
40
40
  end
41
41
 
42
42
  # Define the JSONRPC controller
43
- class JsonrpcController < ActionController::Base
43
+ class JsonrpcController < ApplicationController
44
44
  def handle
45
45
  if jsonrpc_request?
46
46
  result = handle_single(jsonrpc_request)
@@ -10,6 +10,10 @@ Uses constraints to route JSON-RPC requests to different Rails controller action
10
10
  class App < Rails::Application
11
11
  # ...
12
12
  routes.append do
13
+ # Handle batch requests
14
+ post '/', to: 'jsonrpc#ping_or_echo', constraints: JSONRPC::BatchConstraint.new
15
+
16
+ # Handle individual method requests
13
17
  post '/', to: 'jsonrpc#echo', constraints: JSONRPC::MethodConstraint.new('echo')
14
18
  post '/', to: 'jsonrpc#ping', constraints: JSONRPC::MethodConstraint.new('ping')
15
19
  end
@@ -25,6 +29,20 @@ class JsonrpcController < ActionController::Base
25
29
  def ping
26
30
  render jsonrpc: 'pong'
27
31
  end
32
+
33
+ # POST /
34
+ def ping_or_echo
35
+ results = jsonrpc_batch.process_each do |request_or_notification|
36
+ case request_or_notification.method
37
+ when 'echo'
38
+ request_or_notification.params
39
+ when 'ping'
40
+ 'pong'
41
+ end
42
+ end
43
+
44
+ render jsonrpc: results
45
+ end
28
46
  end
29
47
  ```
30
48
 
@@ -36,18 +54,35 @@ bundle exec rackup
36
54
 
37
55
  ## API
38
56
 
39
- The server implements an echo API with these procedures:
57
+ The server implements these procedures:
40
58
 
41
59
  - `echo` - Returns the input message
42
- - `ping` - Returns "pong"
60
+ - `ping` - Returns `'pong'`
43
61
 
44
62
  ## Example Requests
45
63
 
64
+ Echo request:
46
65
  ```sh
47
66
  curl -X POST http://localhost:9292 \
48
67
  -H "Content-Type: application/json" \
49
68
  -d '{"jsonrpc": "2.0", "method": "echo", "params": {"message": "Hello, World!"}, "id": 1}'
69
+ ```
70
+
71
+ Ping request:
72
+ ```sh
73
+ curl -X POST http://localhost:9292 \
74
+ -H "Content-Type: application/json" \
75
+ -d '{"jsonrpc": "2.0", "method": "ping", "params": {}, "id": 2}'
76
+ ```
77
+
78
+ Batch request with multiple methods:
79
+ ```sh
80
+ # Batch request with multiple methods
50
81
  curl -X POST http://localhost:9292 \
51
82
  -H "Content-Type: application/json" \
52
- -d '{"jsonrpc": "2.0""method": "ping", "params": {}, "id": 2}'
83
+ -d '[
84
+ {"jsonrpc": "2.0", "method": "echo", "params": {"message": "Hello from batch!"}, "id": 1},
85
+ {"jsonrpc": "2.0", "method": "ping", "params": {}, "id": 2},
86
+ {"jsonrpc": "2.0", "method": "echo", "params": {"message": "Another echo"}, "id": 3}
87
+ ]'
53
88
  ```
@@ -37,13 +37,17 @@ class App < Rails::Application
37
37
  config.hosts.clear
38
38
 
39
39
  routes.append do
40
+ # Handle batch requests
41
+ post '/', to: 'jsonrpc#ping_or_echo', constraints: JSONRPC::BatchConstraint.new
42
+
43
+ # Handle individual method requests
40
44
  post '/', to: 'jsonrpc#echo', constraints: JSONRPC::MethodConstraint.new('echo')
41
45
  post '/', to: 'jsonrpc#ping', constraints: JSONRPC::MethodConstraint.new('ping')
42
46
  end
43
47
  end
44
48
 
45
49
  # Define the JSONRPC controller
46
- class JsonrpcController < ActionController::Base
50
+ class JsonrpcController < ApplicationController
47
51
  def echo
48
52
  render jsonrpc: jsonrpc_request.params
49
53
  end
@@ -51,6 +55,19 @@ class JsonrpcController < ActionController::Base
51
55
  def ping
52
56
  render jsonrpc: 'pong'
53
57
  end
58
+
59
+ def ping_or_echo
60
+ results = jsonrpc_batch.process_each do |request_or_notification|
61
+ case request_or_notification.method
62
+ when 'echo'
63
+ request_or_notification.params
64
+ when 'ping'
65
+ 'pong'
66
+ end
67
+ end
68
+
69
+ render jsonrpc: results
70
+ end
54
71
  end
55
72
 
56
73
  App.initialize!
@@ -1,8 +1,10 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.4.0)
4
+ jsonrpc-middleware (0.6.0)
5
+ dry-struct (~> 1.8)
5
6
  dry-validation (~> 1.11)
7
+ multi_json (~> 1.20)
6
8
  zeitwerk (~> 2.7)
7
9
 
8
10
  GEM
@@ -33,6 +35,11 @@ GEM
33
35
  dry-logic (~> 1.5)
34
36
  dry-types (~> 1.8)
35
37
  zeitwerk (~> 2.6)
38
+ dry-struct (1.8.0)
39
+ dry-core (~> 1.1)
40
+ dry-types (~> 1.8, >= 1.8.2)
41
+ ice_nine (~> 0.11)
42
+ zeitwerk (~> 2.6)
36
43
  dry-types (1.8.3)
37
44
  bigdecimal (~> 3.0)
38
45
  concurrent-ruby (~> 1.0)
@@ -46,8 +53,9 @@ GEM
46
53
  dry-initializer (~> 3.2)
47
54
  dry-schema (~> 1.14)
48
55
  zeitwerk (~> 2.6)
56
+ ice_nine (0.11.2)
49
57
  logger (1.7.0)
50
- multi_json (1.16.0)
58
+ multi_json (1.21.1)
51
59
  mustermann (3.0.3)
52
60
  ruby2_keywords (~> 0.0.1)
53
61
  nio4r (2.7.4)
@@ -92,4 +100,4 @@ DEPENDENCIES
92
100
  sinatra-contrib
93
101
 
94
102
  BUNDLED WITH
95
- 2.7.0
103
+ 4.0.8
@@ -1,8 +1,10 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.4.0)
4
+ jsonrpc-middleware (0.6.0)
5
+ dry-struct (~> 1.8)
5
6
  dry-validation (~> 1.11)
7
+ multi_json (~> 1.20)
6
8
  zeitwerk (~> 2.7)
7
9
 
8
10
  GEM
@@ -33,6 +35,11 @@ GEM
33
35
  dry-logic (~> 1.5)
34
36
  dry-types (~> 1.8)
35
37
  zeitwerk (~> 2.6)
38
+ dry-struct (1.8.0)
39
+ dry-core (~> 1.1)
40
+ dry-types (~> 1.8, >= 1.8.2)
41
+ ice_nine (~> 0.11)
42
+ zeitwerk (~> 2.6)
36
43
  dry-types (1.8.3)
37
44
  bigdecimal (~> 3.0)
38
45
  concurrent-ruby (~> 1.0)
@@ -46,8 +53,9 @@ GEM
46
53
  dry-initializer (~> 3.2)
47
54
  dry-schema (~> 1.14)
48
55
  zeitwerk (~> 2.6)
56
+ ice_nine (0.11.2)
49
57
  logger (1.7.0)
50
- multi_json (1.16.0)
58
+ multi_json (1.21.1)
51
59
  mustermann (3.0.3)
52
60
  ruby2_keywords (~> 0.0.1)
53
61
  nio4r (2.7.4)
@@ -92,4 +100,4 @@ DEPENDENCIES
92
100
  sinatra-contrib
93
101
 
94
102
  BUNDLED WITH
95
- 2.7.0
103
+ 4.0.8