jsonrpc-middleware 0.3.0 → 0.5.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/.claude/commands/test.md +561 -0
- data/.claude/settings.local.json +2 -1
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +71 -2
- data/README.md +49 -18
- data/Rakefile +57 -3
- data/examples/procedures.rb +1 -5
- data/examples/rack/Gemfile.lock +1 -1
- data/examples/rack/app.rb +1 -4
- data/examples/rack-echo/Gemfile.lock +1 -1
- data/examples/rack-echo/app.rb +1 -4
- data/examples/rack-single-file/config.ru +1 -4
- data/examples/rails/Gemfile.lock +1 -1
- data/examples/rails/app/controllers/jsonrpc_controller.rb +1 -4
- data/examples/rails/config/initializers/jsonrpc.rb +1 -5
- data/examples/rails-routing-dsl/README.md +199 -0
- data/examples/rails-routing-dsl/config.ru +146 -0
- data/examples/rails-single-file/config.ru +1 -4
- data/examples/rails-single-file-routing/README.md +1 -1
- data/examples/rails-single-file-routing/config.ru +1 -5
- data/examples/sinatra-classic/Gemfile.lock +1 -1
- data/examples/sinatra-classic/app.rb +1 -4
- data/examples/sinatra-modular/Gemfile.lock +1 -1
- data/examples/sinatra-modular/app.rb +1 -4
- data/lib/jsonrpc/batch_request.rb +25 -6
- data/lib/jsonrpc/configuration.rb +12 -3
- data/lib/jsonrpc/railtie/batch_constraint.rb +25 -0
- data/lib/jsonrpc/railtie/mapper_extension.rb +34 -0
- data/lib/jsonrpc/railtie/method_constraint.rb +1 -1
- data/lib/jsonrpc/railtie/routes_dsl.rb +147 -0
- data/lib/jsonrpc/railtie.rb +7 -0
- data/lib/jsonrpc/version.rb +1 -1
- metadata +11 -5
- /data/{.aiexclude → .aiignore} +0 -0
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
|
|
@@ -26,10 +37,29 @@ calls encoded in JSON.
|
|
26
37
|
- **Spec-compliant**: Fully implements the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification)
|
27
38
|
- **Rack middleware integration**: Seamlessly integrates with Rack applications (Rails, Sinatra, Hanami, etc)
|
28
39
|
- **Support for all request types**: Handles single requests, notifications, and batch requests
|
40
|
+
- **Rails routing DSL**: Elegant routing DSL for Rails applications with support for namespaces and batch handling
|
29
41
|
- **Error handling**: Comprehensive error handling with standard JSON-RPC error responses
|
30
42
|
- **Request validation**: Define request parameter specifications and validations
|
31
43
|
- **Helpers**: Convenient helper methods to simplify request and response processing
|
32
44
|
|
45
|
+
## 🏗️ Architecture
|
46
|
+
|
47
|
+
The gem integrates seamlessly into your Rack-based application:
|
48
|
+
|
49
|
+
```mermaid
|
50
|
+
block-beta
|
51
|
+
columns 4
|
52
|
+
|
53
|
+
App["Your app"]:4
|
54
|
+
Rails:1 Sinatra:1 RackApp["Other Rack-compatible framework"]:2
|
55
|
+
Middleware["JSON-RPC Middleware"]:4
|
56
|
+
Rack["Rack"]:4
|
57
|
+
HTTP["HTTP"]:4
|
58
|
+
|
59
|
+
classDef middlewareStyle fill:#ff6b6b,stroke:#d63031,stroke-width:2px,color:#fff
|
60
|
+
class Middleware middlewareStyle
|
61
|
+
```
|
62
|
+
|
33
63
|
## 📦 Installation
|
34
64
|
|
35
65
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -78,31 +108,26 @@ class App
|
|
78
108
|
@env = env
|
79
109
|
|
80
110
|
if jsonrpc_request?
|
81
|
-
|
82
|
-
jsonrpc_response(
|
111
|
+
sum = add(jsonrpc_request.params)
|
112
|
+
jsonrpc_response(sum)
|
83
113
|
elsif jsonrpc_notification?
|
84
|
-
|
114
|
+
add(jsonrpc_notification.params)
|
85
115
|
jsonrpc_notification_response
|
86
116
|
else
|
87
|
-
|
88
|
-
jsonrpc_batch_response(
|
117
|
+
results = add_in_batches(jsonrpc_batch)
|
118
|
+
jsonrpc_batch_response(results)
|
89
119
|
end
|
90
120
|
end
|
91
121
|
|
92
122
|
private
|
93
123
|
|
94
|
-
def
|
95
|
-
params = request_or_notification.params
|
96
|
-
|
124
|
+
def add(params)
|
97
125
|
addends = params.is_a?(Array) ? params : params['addends'] # Handle positional and named arguments
|
98
126
|
addends.sum
|
99
127
|
end
|
100
128
|
|
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
|
129
|
+
def add_in_batches(batch)
|
130
|
+
batch.process_each { |request_or_notification| add(request_or_notification.params) }
|
106
131
|
end
|
107
132
|
end
|
108
133
|
|
@@ -110,9 +135,14 @@ use JSONRPC::Middleware
|
|
110
135
|
run App.new
|
111
136
|
```
|
112
137
|
|
113
|
-
This will give you a fully-featured JSON-RPC server
|
138
|
+
This will give you a fully-featured JSON-RPC server, capable of:
|
139
|
+
- Handling JSON-RPC requests, notifications __and batches__
|
140
|
+
- Validating the allowed JSON-RPC methods (e.g. allow only `add`)
|
141
|
+
- Validating the JSON-RPC method parameters (e.g. allow only non-empty arrays of numbers)
|
142
|
+
- Accept positional and named parameters (`params: [5, 5]`, `params: { addends: [5, 5] }`)
|
143
|
+
- Respond successfully or erroneously, according to the specification
|
114
144
|
|
115
|
-
For more advanced setups, check the [examples](https://github.com/wilsonsilva/jsonrpc-middleware/blob/main/examples/README.md).
|
145
|
+
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
146
|
|
117
147
|
## 📚 Documentation
|
118
148
|
|
@@ -145,6 +175,7 @@ rake bundle:audit:update # Updates the bundler-audit vulnerability database
|
|
145
175
|
rake clean # Remove any temporary products
|
146
176
|
rake clobber # Remove any generated files
|
147
177
|
rake coverage # Run spec with coverage
|
178
|
+
rake examples:bundle_install # Run bundle install on all example folders (useful after updating the gem version)
|
148
179
|
rake install # Build and install jsonrpc-middleware.gem into system gems
|
149
180
|
rake install:local # Build and install jsonrpc-middleware.gem into system gems without network access
|
150
181
|
rake qa # Test, lint and perform security and documentation audits
|
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)
|
@@ -97,3 +95,59 @@ namespace :yard do
|
|
97
95
|
puts 'Done!'
|
98
96
|
end
|
99
97
|
end
|
98
|
+
|
99
|
+
namespace :examples do
|
100
|
+
desc 'Run bundle install on all example folders'
|
101
|
+
task :bundle_install do
|
102
|
+
examples_dir = File.join(Dir.pwd, 'examples')
|
103
|
+
|
104
|
+
unless Dir.exist?(examples_dir)
|
105
|
+
puts 'Examples directory not found'
|
106
|
+
exit 1
|
107
|
+
end
|
108
|
+
|
109
|
+
example_folders = Dir.glob(File.join(examples_dir, '*')).select { |path| Dir.exist?(path) }
|
110
|
+
|
111
|
+
if example_folders.empty?
|
112
|
+
puts 'No example folders found'
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
puts "Found #{example_folders.length} example folders:"
|
117
|
+
example_folders.each { |folder| puts " - #{File.basename(folder)}" }
|
118
|
+
puts
|
119
|
+
|
120
|
+
failed_folders = []
|
121
|
+
|
122
|
+
example_folders.each do |folder|
|
123
|
+
gemfile_path = File.join(folder, 'Gemfile')
|
124
|
+
|
125
|
+
unless File.exist?(gemfile_path)
|
126
|
+
puts "Skipping #{File.basename(folder)} - no Gemfile found"
|
127
|
+
next
|
128
|
+
end
|
129
|
+
|
130
|
+
puts "Running bundle install in #{File.basename(folder)}..."
|
131
|
+
|
132
|
+
Dir.chdir(folder) do
|
133
|
+
system('bundle install')
|
134
|
+
|
135
|
+
unless $?.success?
|
136
|
+
failed_folders << File.basename(folder)
|
137
|
+
puts " ✗ Failed to bundle install in #{File.basename(folder)}"
|
138
|
+
else
|
139
|
+
puts " ✓ Successfully installed gems in #{File.basename(folder)}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
puts
|
144
|
+
end
|
145
|
+
|
146
|
+
if failed_folders.empty?
|
147
|
+
puts 'All example folders processed successfully!'
|
148
|
+
else
|
149
|
+
puts "Failed to process #{failed_folders.length} folders: #{failed_folders.join(', ')}"
|
150
|
+
exit 1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/examples/procedures.rb
CHANGED
data/examples/rack/Gemfile.lock
CHANGED
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
|
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
@@ -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,199 @@
|
|
1
|
+
# Rails JSON-RPC Routing DSL
|
2
|
+
|
3
|
+
Demonstrates using the Rails routing DSL extension to route JSON-RPC methods to different controller actions for a
|
4
|
+
smart home control system.
|
5
|
+
|
6
|
+
## Highlights
|
7
|
+
|
8
|
+
Uses the `jsonrpc` routing DSL to map JSON-RPC methods to Rails controller actions with clean, readable syntax:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class App < Rails::Application
|
12
|
+
# ...
|
13
|
+
routes.append do
|
14
|
+
jsonrpc '/' do
|
15
|
+
# Handle batch requests with a dedicated controller
|
16
|
+
batch to: 'batch#handle'
|
17
|
+
|
18
|
+
method 'on', to: 'main#on'
|
19
|
+
method 'off', to: 'main#off'
|
20
|
+
|
21
|
+
namespace 'lights' do
|
22
|
+
method 'on', to: 'lights#on' # becomes lights.on
|
23
|
+
method 'off', to: 'lights#off' # becomes lights.off
|
24
|
+
end
|
25
|
+
|
26
|
+
namespace 'climate' do
|
27
|
+
method 'on', to: 'climate#on' # becomes climate.on
|
28
|
+
method 'off', to: 'climate#off' # becomes climate.off
|
29
|
+
|
30
|
+
namespace 'fan' do
|
31
|
+
method 'on', to: 'fan#on' # becomes climate.fan.on
|
32
|
+
method 'off', to: 'fan#off' # becomes climate.fan.off
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class MainController < ActionController::Base
|
40
|
+
def on
|
41
|
+
render jsonrpc: { device: 'main_system', status: 'on' }
|
42
|
+
end
|
43
|
+
|
44
|
+
def off
|
45
|
+
render jsonrpc: { device: 'main_system', status: 'off' }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class LightsController < ActionController::Base
|
50
|
+
def on
|
51
|
+
render jsonrpc: { device: 'lights', status: 'on' }
|
52
|
+
end
|
53
|
+
|
54
|
+
def off
|
55
|
+
render jsonrpc: { device: 'lights', status: 'off' }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ClimateController < ActionController::Base
|
60
|
+
def on
|
61
|
+
render jsonrpc: { device: 'climate_system', status: 'on' }
|
62
|
+
end
|
63
|
+
|
64
|
+
def off
|
65
|
+
render jsonrpc: { device: 'climate_system', status: 'off' }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class FanController < ActionController::Base
|
70
|
+
def on
|
71
|
+
render jsonrpc: { device: 'fan', status: 'on' }
|
72
|
+
end
|
73
|
+
|
74
|
+
def off
|
75
|
+
render jsonrpc: { device: 'fan', status: 'off' }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class BatchController < ActionController::Base
|
80
|
+
def handle
|
81
|
+
# Process each request in the batch and collect results
|
82
|
+
results = jsonrpc_batch.process_each do |request_or_notification|
|
83
|
+
case request_or_notification.method
|
84
|
+
when 'on'
|
85
|
+
{ device: 'main_system', status: 'on' }
|
86
|
+
when 'off'
|
87
|
+
{ device: 'main_system', status: 'off' }
|
88
|
+
when 'lights.on'
|
89
|
+
{ device: 'lights', status: 'on' }
|
90
|
+
when 'lights.off'
|
91
|
+
{ device: 'lights', status: 'off' }
|
92
|
+
# ... handle other methods
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
render jsonrpc: results
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
## Running
|
102
|
+
|
103
|
+
```sh
|
104
|
+
bundle exec rackup
|
105
|
+
```
|
106
|
+
|
107
|
+
## API
|
108
|
+
|
109
|
+
The server implements smart home controls with these procedures:
|
110
|
+
|
111
|
+
**Root Methods:**
|
112
|
+
- `on` - Turn home automation system on
|
113
|
+
- `off` - Turn home automation system off
|
114
|
+
|
115
|
+
**Lights Namespace:**
|
116
|
+
- `lights.on` - Turn lights on
|
117
|
+
- `lights.off` - Turn lights off
|
118
|
+
|
119
|
+
**Climate Namespace:**
|
120
|
+
- `climate.on` - Turn climate system on
|
121
|
+
- `climate.off` - Turn climate system off
|
122
|
+
|
123
|
+
**Climate Fan Namespace:**
|
124
|
+
- `climate.fan.on` - Turn fan on
|
125
|
+
- `climate.fan.off` - Turn fan off
|
126
|
+
|
127
|
+
**Batch Processing:**
|
128
|
+
- Batch requests are automatically routed to the `BatchController#handle` action
|
129
|
+
- The controller uses `jsonrpc_batch.process_each` to handle each request in the batch
|
130
|
+
- Responses are collected and returned as an array
|
131
|
+
|
132
|
+
## Example Requests
|
133
|
+
|
134
|
+
Turn on the home automation system:
|
135
|
+
```sh
|
136
|
+
curl -X POST http://localhost:9292 \
|
137
|
+
-H "Content-Type: application/json" \
|
138
|
+
-d '{"jsonrpc": "2.0", "method": "on", "params": {}, "id": 1}'
|
139
|
+
```
|
140
|
+
|
141
|
+
Turn off the home automation system:
|
142
|
+
```sh
|
143
|
+
curl -X POST http://localhost:9292 \
|
144
|
+
-H "Content-Type: application/json" \
|
145
|
+
-d '{"jsonrpc": "2.0", "method": "off", "params": {}, "id": 2}'
|
146
|
+
```
|
147
|
+
|
148
|
+
Turn on lights:
|
149
|
+
```sh
|
150
|
+
curl -X POST http://localhost:9292 \
|
151
|
+
-H "Content-Type: application/json" \
|
152
|
+
-d '{"jsonrpc": "2.0", "method": "lights.on", "params": {}, "id": 3}'
|
153
|
+
```
|
154
|
+
|
155
|
+
Turn off the lights:
|
156
|
+
```sh
|
157
|
+
curl -X POST http://localhost:9292 \
|
158
|
+
-H "Content-Type: application/json" \
|
159
|
+
-d '{"jsonrpc": "2.0", "method": "lights.off", "params": {}, "id": 4}'
|
160
|
+
```
|
161
|
+
|
162
|
+
Turn on the climate system:
|
163
|
+
```sh
|
164
|
+
curl -X POST http://localhost:9292 \
|
165
|
+
-H "Content-Type: application/json" \
|
166
|
+
-d '{"jsonrpc": "2.0", "method": "climate.on", "params": {}, "id": 5}'
|
167
|
+
```
|
168
|
+
|
169
|
+
Turn off the climate system:
|
170
|
+
```sh
|
171
|
+
curl -X POST http://localhost:9292 \
|
172
|
+
-H "Content-Type: application/json" \
|
173
|
+
-d '{"jsonrpc": "2.0", "method": "climate.off", "params": {}, "id": 6}'
|
174
|
+
```
|
175
|
+
|
176
|
+
Turn on fan:
|
177
|
+
```sh
|
178
|
+
curl -X POST http://localhost:9292 \
|
179
|
+
-H "Content-Type: application/json" \
|
180
|
+
-d '{"jsonrpc": "2.0", "method": "climate.fan.on", "params": {}, "id": 7}'
|
181
|
+
```
|
182
|
+
|
183
|
+
Turn off fan:
|
184
|
+
```sh
|
185
|
+
curl -X POST http://localhost:9292 \
|
186
|
+
-H "Content-Type: application/json" \
|
187
|
+
-d '{"jsonrpc": "2.0", "method": "climate.fan.off", "params": {}, "id": 8}'
|
188
|
+
```
|
189
|
+
|
190
|
+
Batch request for evening routine:
|
191
|
+
```sh
|
192
|
+
curl -X POST http://localhost:9292 \
|
193
|
+
-H "Content-Type: application/json" \
|
194
|
+
-d '[
|
195
|
+
{"jsonrpc": "2.0", "method": "off", "params": {}, "id": 9},
|
196
|
+
{"jsonrpc": "2.0", "method": "lights.off", "params": {}, "id": 10},
|
197
|
+
{"jsonrpc": "2.0", "method": "climate.off", "params": {}, "id": 11}
|
198
|
+
]'
|
199
|
+
```
|
@@ -0,0 +1,146 @@
|
|
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
|
+
# Define the allowed JSON-RPC methods. Calls to methods absent from this list will return a method not found error.
|
20
|
+
procedure 'on'
|
21
|
+
procedure 'off'
|
22
|
+
procedure 'lights.on'
|
23
|
+
procedure 'lights.off'
|
24
|
+
procedure 'climate.on'
|
25
|
+
procedure 'climate.off'
|
26
|
+
procedure 'climate.fan.on'
|
27
|
+
procedure 'climate.fan.off'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Define the application
|
31
|
+
class App < Rails::Application
|
32
|
+
config.root = __dir__
|
33
|
+
config.cache_classes = true
|
34
|
+
config.eager_load = true
|
35
|
+
config.active_support.deprecation = :stderr
|
36
|
+
config.consider_all_requests_local = true
|
37
|
+
config.active_support.to_time_preserves_timezone = :zone
|
38
|
+
config.logger = nil
|
39
|
+
config.hosts.clear
|
40
|
+
|
41
|
+
routes.append do
|
42
|
+
jsonrpc '/' do
|
43
|
+
# Handle batch requests with a dedicated controller
|
44
|
+
batch to: 'batch#handle'
|
45
|
+
|
46
|
+
method :on, to: 'main#on'
|
47
|
+
method :off, to: 'main#off'
|
48
|
+
|
49
|
+
namespace 'lights' do
|
50
|
+
method :on, to: 'lights#on' # becomes lights.on
|
51
|
+
method :off, to: 'lights#off' # becomes lights.off
|
52
|
+
end
|
53
|
+
|
54
|
+
namespace 'climate' do
|
55
|
+
method :on, to: 'climate#on' # becomes climate.on
|
56
|
+
method :off, to: 'climate#off' # becomes climate.off
|
57
|
+
|
58
|
+
namespace 'fan' do
|
59
|
+
method :on, to: 'fan#on' # becomes climate.fan.on
|
60
|
+
method :off, to: 'fan#off' # becomes climate.fan.off
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Controller for main system operations
|
68
|
+
class MainController < ActionController::Base
|
69
|
+
def on
|
70
|
+
render jsonrpc: { device: 'main_system', status: 'on' }
|
71
|
+
end
|
72
|
+
|
73
|
+
def off
|
74
|
+
render jsonrpc: { device: 'main_system', status: 'off' }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Controller for lights operations
|
79
|
+
class LightsController < ActionController::Base
|
80
|
+
def on
|
81
|
+
render jsonrpc: { device: 'lights', status: 'on' }
|
82
|
+
end
|
83
|
+
|
84
|
+
def off
|
85
|
+
render jsonrpc: { device: 'lights', status: 'off' }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Controller for climate operations
|
90
|
+
class ClimateController < ActionController::Base
|
91
|
+
def on
|
92
|
+
render jsonrpc: { device: 'climate_system', status: 'on' }
|
93
|
+
end
|
94
|
+
|
95
|
+
def off
|
96
|
+
render jsonrpc: { device: 'climate_system', status: 'off' }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Controller for climate fan operations
|
101
|
+
class FanController < ActionController::Base
|
102
|
+
def on
|
103
|
+
render jsonrpc: { device: 'fan', status: 'on' }
|
104
|
+
end
|
105
|
+
|
106
|
+
def off
|
107
|
+
render jsonrpc: { device: 'fan', status: 'off' }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Controller for batch operations
|
112
|
+
class BatchController < ActionController::Base
|
113
|
+
def handle
|
114
|
+
# Process each request in the batch and collect results
|
115
|
+
results = jsonrpc_batch.process_each do |request_or_notification|
|
116
|
+
result = case request_or_notification.method
|
117
|
+
when 'on'
|
118
|
+
{ device: 'main_system', status: 'on' }
|
119
|
+
when 'off'
|
120
|
+
{ device: 'main_system', status: 'off' }
|
121
|
+
when 'lights.on'
|
122
|
+
{ device: 'lights', status: 'on' }
|
123
|
+
when 'lights.off'
|
124
|
+
{ device: 'lights', status: 'off' }
|
125
|
+
when 'climate.on'
|
126
|
+
{ device: 'climate_system', status: 'on' }
|
127
|
+
when 'climate.off'
|
128
|
+
{ device: 'climate_system', status: 'off' }
|
129
|
+
when 'climate.fan.on'
|
130
|
+
{ device: 'fan', status: 'on' }
|
131
|
+
when 'climate.fan.off'
|
132
|
+
{ device: 'fan', status: 'off' }
|
133
|
+
else
|
134
|
+
{ error: 'Unknown method', method: request_or_notification.method }
|
135
|
+
end
|
136
|
+
|
137
|
+
result
|
138
|
+
end
|
139
|
+
|
140
|
+
render jsonrpc: results
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
App.initialize!
|
145
|
+
|
146
|
+
run App
|
@@ -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
|
|