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.
data/README.md CHANGED
@@ -1,12 +1,23 @@
1
- # JSONRPC::Middleware
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
  [![Gem Version](https://badge.fury.io/rb/jsonrpc-middleware.svg)](https://badge.fury.io/rb/jsonrpc-middleware)
4
14
  ![Build](https://github.com/wilsonsilva/jsonrpc-middleware/actions/workflows/main.yml/badge.svg)
5
15
  [![Maintainability](https://qlty.sh/badges/a275de81-94e3-45af-9469-523aa5345871/maintainability.svg)](https://qlty.sh/gh/wilsonsilva/projects/jsonrpc-middleware)
6
16
  [![Code Coverage](https://qlty.sh/badges/a275de81-94e3-45af-9469-523aa5345871/test_coverage.svg)](https://qlty.sh/gh/wilsonsilva/projects/jsonrpc-middleware)
7
17
 
8
- A Ruby implementation of the JSON-RPC protocol, enabling standardized communication between systems via remote procedure
9
- calls encoded in JSON.
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
- result = handle_single(jsonrpc_request)
82
- jsonrpc_response(result)
110
+ sum = add(jsonrpc_request.params)
111
+ jsonrpc_response(sum)
83
112
  elsif jsonrpc_notification?
84
- handle_single(jsonrpc_notification)
113
+ add(jsonrpc_notification.params)
85
114
  jsonrpc_notification_response
86
115
  else
87
- responses = handle_batch(jsonrpc_batch)
88
- jsonrpc_batch_response(responses)
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 handle_single(request_or_notification)
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 handle_batch(batch)
102
- batch.flat_map do |request_or_notification|
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 do |task|
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-single-file/) - Calculator server using 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
 
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.1.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.1.9)
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.2)
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.14)
52
+ rack (3.1.16)
53
53
  rackup (2.2.1)
54
54
  rack (>= 3)
55
- zeitwerk (2.7.2)
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.6.8
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.flat_map do |request_or_notification|
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.1.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.1.9)
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.2)
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.14)
52
+ rack (3.1.16)
53
53
  rackup (2.2.1)
54
54
  rack (>= 3)
55
- zeitwerk (2.7.2)
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.6.8
68
+ 2.7.0
@@ -27,10 +27,7 @@ class App
27
27
  end
28
28
 
29
29
  def handle_batch(batch)
30
- batch.flat_map do |request_or_notification|
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.flat_map do |request_or_notification|
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
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.1.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.1)
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.0)
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.6.9
261
+ 2.7.0
@@ -36,9 +36,6 @@ class JsonrpcController < ApplicationController
36
36
  end
37
37
 
38
38
  def handle_batch(batch)
39
- batch.flat_map do |request_or_notification|
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.flat_map do |request_or_notification|
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.1.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.2.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.15.0)
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.13)
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.0)
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.0)
81
- zeitwerk (2.7.2)
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.6.8
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.flat_map do |request_or_notification|
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.1.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.2.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.15.0)
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.13)
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.0)
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.0)
81
- zeitwerk (2.7.2)
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.6.8
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.flat_map do |request_or_notification|
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
@@ -3,6 +3,7 @@
3
3
  module JSONRPC
4
4
  # @api private
5
5
  class Railtie < ::Rails::Railtie
6
+ # Rails routing constraint for matching JSON-RPC method names
6
7
  initializer 'jsonrpc.middleware' do |app|
7
8
  app.middleware.use JSONRPC::Middleware
8
9
  end