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.
- checksums.yaml +4 -4
- data/.aiignore +6 -1
- data/.claude/agents/entire-search.md +25 -0
- data/.claude/agents/rbs-specialist.md +89 -0
- data/.claude/settings.json +84 -0
- data/.devcontainer/devcontainer.json +17 -0
- data/.dockerignore +16 -0
- data/.entire/.gitignore +5 -0
- data/.entire/settings.json +4 -0
- data/.rubocop.yml +26 -1
- data/.tool-versions +1 -1
- data/.yard-lint.yml +283 -0
- data/AGENTS.md +142 -0
- data/CHANGELOG.md +43 -0
- data/CLAUDE.md +2 -113
- data/Dockerfile +144 -0
- data/README.md +10 -17
- data/Rakefile +78 -26
- data/examples/README.md +9 -0
- data/examples/procedures.rb +3 -1
- data/examples/rack/Gemfile.lock +11 -2
- data/examples/rack-echo/Gemfile.lock +11 -2
- data/examples/rails/Gemfile.lock +18 -23
- data/examples/rails/config/initializers/jsonrpc.rb +1 -1
- data/examples/rails-routing-dsl/config.ru +5 -5
- data/examples/rails-single-file/config.ru +1 -1
- data/examples/rails-single-file-routing/README.md +38 -3
- data/examples/rails-single-file-routing/config.ru +18 -1
- data/examples/sinatra-classic/Gemfile.lock +11 -3
- data/examples/sinatra-modular/Gemfile.lock +11 -3
- data/lib/jsonrpc/batch_request.rb +9 -12
- data/lib/jsonrpc/batch_response.rb +7 -9
- data/lib/jsonrpc/configuration.rb +43 -4
- data/lib/jsonrpc/error.rb +8 -9
- data/lib/jsonrpc/errors/internal_error.rb +2 -0
- data/lib/jsonrpc/errors/invalid_params_error.rb +2 -0
- data/lib/jsonrpc/errors/invalid_request_error.rb +2 -0
- data/lib/jsonrpc/errors/method_not_found_error.rb +2 -0
- data/lib/jsonrpc/errors/parse_error.rb +2 -0
- data/lib/jsonrpc/helpers.rb +6 -0
- data/lib/jsonrpc/middleware.rb +15 -13
- data/lib/jsonrpc/notification.rb +8 -9
- data/lib/jsonrpc/parser.rb +22 -19
- data/lib/jsonrpc/railtie/batch_constraint.rb +1 -0
- data/lib/jsonrpc/railtie/mapper_extension.rb +2 -2
- data/lib/jsonrpc/railtie/method_constraint.rb +9 -0
- data/lib/jsonrpc/railtie/routes_dsl.rb +10 -15
- data/lib/jsonrpc/railtie.rb +4 -2
- data/lib/jsonrpc/request.rb +12 -84
- data/lib/jsonrpc/response.rb +11 -60
- data/lib/jsonrpc/types.rb +13 -0
- data/lib/jsonrpc/validator.rb +14 -4
- data/lib/jsonrpc/version.rb +1 -1
- data/lib/jsonrpc.rb +5 -0
- data/rbs_collection.lock.yaml +476 -0
- data/rbs_collection.yaml +21 -0
- data/sig/jsonrpc/batch_request.rbs +17 -0
- data/sig/jsonrpc/batch_response.rbs +17 -0
- data/sig/jsonrpc/configuration.rbs +18 -0
- data/sig/jsonrpc/error.rbs +17 -0
- data/sig/jsonrpc/errors/internal_error.rbs +5 -0
- data/sig/jsonrpc/errors/invalid_params_error.rbs +5 -0
- data/sig/jsonrpc/errors/invalid_request_error.rbs +5 -0
- data/sig/jsonrpc/errors/method_not_found_error.rbs +5 -0
- data/sig/jsonrpc/errors/parse_error.rbs +5 -0
- data/sig/jsonrpc/middleware.rbs +20 -3
- data/sig/jsonrpc/notification.rbs +15 -0
- data/sig/jsonrpc/parser.rbs +7 -1
- data/sig/jsonrpc/request.rbs +18 -0
- data/sig/jsonrpc/response.rbs +19 -0
- data/sig/jsonrpc/validator.rbs +8 -0
- data/sig/jsonrpc.rbs +3 -156
- data/sig/multi_json.rbs +17 -0
- data/sig/type_definitions.rbs +11 -0
- data/sig/zeitwerk.rbs +10 -0
- metadata +61 -9
- data/.claude/commands/document.md +0 -105
- data/.claude/commands/test.md +0 -561
- data/.claude/docs/yard.md +0 -602
- data/.claude/settings.local.json +0 -11
- data/.yardstick.yml +0 -22
|
@@ -6,6 +6,8 @@ module JSONRPC
|
|
|
6
6
|
# A batch request is an Array filled with Request objects to send several requests at once.
|
|
7
7
|
# The Server should respond with an Array containing the corresponding Response objects.
|
|
8
8
|
#
|
|
9
|
+
# @api public
|
|
10
|
+
#
|
|
9
11
|
# @example Create a batch request with multiple requests
|
|
10
12
|
# batch = JSONRPC::BatchRequest.new([
|
|
11
13
|
# JSONRPC::Request.new(method: "sum", params: [1, 2, 4], id: "1"),
|
|
@@ -39,14 +41,13 @@ module JSONRPC
|
|
|
39
41
|
# ]
|
|
40
42
|
# batch = JSONRPC::BatchRequest.new(requests)
|
|
41
43
|
#
|
|
42
|
-
# @param requests [Array<JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error>] an array of request objects
|
|
43
|
-
# or errors
|
|
44
44
|
# @raise [ArgumentError] if requests is not an Array
|
|
45
|
-
#
|
|
46
45
|
# @raise [ArgumentError] if requests is empty
|
|
47
|
-
#
|
|
48
46
|
# @raise [ArgumentError] if any request is not a valid Request, Notification, or Error
|
|
49
47
|
#
|
|
48
|
+
# @param requests [Array<JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error>] an array of request objects
|
|
49
|
+
# or errors
|
|
50
|
+
#
|
|
50
51
|
def initialize(requests)
|
|
51
52
|
validate_requests(requests)
|
|
52
53
|
@requests = requests
|
|
@@ -75,7 +76,7 @@ module JSONRPC
|
|
|
75
76
|
# @return [String] the JSON-formatted batch
|
|
76
77
|
#
|
|
77
78
|
def to_json(*)
|
|
78
|
-
|
|
79
|
+
MultiJson.dump(to_h, *)
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
# Implements the Enumerable contract by yielding each request in the batch
|
|
@@ -109,9 +110,7 @@ module JSONRPC
|
|
|
109
110
|
#
|
|
110
111
|
# @return [Integer] the number of requests in the batch
|
|
111
112
|
#
|
|
112
|
-
def size
|
|
113
|
-
requests.size
|
|
114
|
-
end
|
|
113
|
+
def size = requests.size
|
|
115
114
|
|
|
116
115
|
# Alias for size method providing Array-like interface
|
|
117
116
|
#
|
|
@@ -162,14 +161,12 @@ module JSONRPC
|
|
|
162
161
|
#
|
|
163
162
|
# @api private
|
|
164
163
|
#
|
|
165
|
-
# @param requests [Array] the array of requests
|
|
166
|
-
#
|
|
167
164
|
# @raise [ArgumentError] if requests is not an Array
|
|
168
|
-
#
|
|
169
165
|
# @raise [ArgumentError] if requests is empty
|
|
170
|
-
#
|
|
171
166
|
# @raise [ArgumentError] if any request is not a valid Request, Notification, or Error
|
|
172
167
|
#
|
|
168
|
+
# @param requests [Array] the array of requests
|
|
169
|
+
#
|
|
173
170
|
# @return [void]
|
|
174
171
|
#
|
|
175
172
|
def validate_requests(requests)
|
|
@@ -7,6 +7,8 @@ module JSONRPC
|
|
|
7
7
|
# a Batch Request. The Server should respond with one Response for each Request
|
|
8
8
|
# (except for Notifications which don't receive responses).
|
|
9
9
|
#
|
|
10
|
+
# @api public
|
|
11
|
+
#
|
|
10
12
|
# @example Create a batch response
|
|
11
13
|
# batch = JSONRPC::BatchResponse.new([
|
|
12
14
|
# JSONRPC::Response.new(result: 7, id: "1"),
|
|
@@ -39,14 +41,12 @@ module JSONRPC
|
|
|
39
41
|
# ]
|
|
40
42
|
# batch = JSONRPC::BatchResponse.new(responses)
|
|
41
43
|
#
|
|
42
|
-
# @param responses [Array<JSONRPC::Response>] an array of response objects
|
|
43
|
-
#
|
|
44
44
|
# @raise [ArgumentError] if responses is not an Array
|
|
45
|
-
#
|
|
46
45
|
# @raise [ArgumentError] if responses is empty
|
|
47
|
-
#
|
|
48
46
|
# @raise [ArgumentError] if any response is not a valid Response
|
|
49
47
|
#
|
|
48
|
+
# @param responses [Array<JSONRPC::Response>] an array of response objects
|
|
49
|
+
#
|
|
50
50
|
def initialize(responses)
|
|
51
51
|
validate_responses(responses)
|
|
52
52
|
@responses = responses
|
|
@@ -75,7 +75,7 @@ module JSONRPC
|
|
|
75
75
|
# @return [String] the JSON-formatted batch response
|
|
76
76
|
#
|
|
77
77
|
def to_json(*)
|
|
78
|
-
|
|
78
|
+
MultiJson.dump(to_h, *)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Implements the Enumerable contract by yielding each response in the batch
|
|
@@ -119,14 +119,12 @@ module JSONRPC
|
|
|
119
119
|
#
|
|
120
120
|
# @api private
|
|
121
121
|
#
|
|
122
|
-
# @param responses [Array] the array of responses
|
|
123
|
-
#
|
|
124
122
|
# @raise [ArgumentError] if responses is not an Array
|
|
125
|
-
#
|
|
126
123
|
# @raise [ArgumentError] if responses is empty
|
|
127
|
-
#
|
|
128
124
|
# @raise [ArgumentError] if any response is not a valid Response
|
|
129
125
|
#
|
|
126
|
+
# @param responses [Array] the array of responses
|
|
127
|
+
#
|
|
130
128
|
# @return [void]
|
|
131
129
|
#
|
|
132
130
|
def validate_responses(responses)
|
|
@@ -20,32 +20,52 @@ module JSONRPC
|
|
|
20
20
|
#
|
|
21
21
|
# @api public
|
|
22
22
|
#
|
|
23
|
-
# @!
|
|
23
|
+
# @!attribute [r] allow_positional_arguments
|
|
24
24
|
# Indicates if the procedure accepts positional arguments
|
|
25
|
+
#
|
|
25
26
|
# @api public
|
|
26
27
|
#
|
|
27
28
|
# @example
|
|
28
29
|
# procedure.allow_positional_arguments # => true
|
|
30
|
+
#
|
|
29
31
|
# @return [Boolean] whether the procedure accepts positional arguments
|
|
30
32
|
#
|
|
31
|
-
# @!
|
|
33
|
+
# @!attribute [r] contract
|
|
32
34
|
# The validation contract for procedure parameters
|
|
35
|
+
#
|
|
33
36
|
# @api public
|
|
34
37
|
#
|
|
35
38
|
# @example
|
|
36
39
|
# procedure.contract # => #<Dry::Validation::Contract...>
|
|
40
|
+
#
|
|
37
41
|
# @return [Dry::Validation::Contract] the validation contract for procedure parameters
|
|
38
42
|
#
|
|
39
|
-
# @!
|
|
43
|
+
# @!attribute [r] parameter_name
|
|
40
44
|
# The name of the first parameter in the contract schema
|
|
45
|
+
#
|
|
41
46
|
# @api public
|
|
42
47
|
#
|
|
43
48
|
# @example
|
|
44
49
|
# procedure.parameter_name # => :numbers
|
|
50
|
+
#
|
|
45
51
|
# @return [Symbol, nil] the name of the first parameter in the contract schema
|
|
46
52
|
#
|
|
47
53
|
Procedure = Data.define(:allow_positional_arguments, :contract, :parameter_name)
|
|
48
54
|
|
|
55
|
+
# The logger instance used for error and diagnostic output
|
|
56
|
+
#
|
|
57
|
+
# @api public
|
|
58
|
+
#
|
|
59
|
+
# @example Using the default logger
|
|
60
|
+
# config.logger # => #<Logger:...>
|
|
61
|
+
#
|
|
62
|
+
# @example Setting a custom logger
|
|
63
|
+
# config.logger = Logger.new('log/jsonrpc.log')
|
|
64
|
+
#
|
|
65
|
+
# @return [Logger] the logger instance
|
|
66
|
+
#
|
|
67
|
+
attr_accessor :logger
|
|
68
|
+
|
|
49
69
|
# Whether to log detailed internal error information in the terminal
|
|
50
70
|
#
|
|
51
71
|
# @api public
|
|
@@ -101,6 +121,21 @@ module JSONRPC
|
|
|
101
121
|
#
|
|
102
122
|
attr_reader :validate_procedure_signatures
|
|
103
123
|
|
|
124
|
+
# JSON adapter to use (optional)
|
|
125
|
+
#
|
|
126
|
+
# @api public
|
|
127
|
+
#
|
|
128
|
+
# @example
|
|
129
|
+
# config.json_adapter = :oj
|
|
130
|
+
#
|
|
131
|
+
# @param adapter [Symbol, nil] the JSON adapter to use
|
|
132
|
+
#
|
|
133
|
+
# @return [Symbol, nil] the JSON adapter to use
|
|
134
|
+
#
|
|
135
|
+
def json_adapter=(adapter)
|
|
136
|
+
MultiJson.use(adapter)
|
|
137
|
+
end
|
|
138
|
+
|
|
104
139
|
# Initializes a new Configuration instance
|
|
105
140
|
#
|
|
106
141
|
# @api public
|
|
@@ -114,6 +149,7 @@ module JSONRPC
|
|
|
114
149
|
# render_internal_errors: true
|
|
115
150
|
# )
|
|
116
151
|
#
|
|
152
|
+
# @param logger [Logger] the logger instance for error and diagnostic output
|
|
117
153
|
# @param log_internal_errors [Boolean] whether to log detailed internal error information in the terminal
|
|
118
154
|
# @param log_request_validation_errors [Boolean] whether to log validation errors during JSON-RPC request processing
|
|
119
155
|
# @param rescue_internal_errors [Boolean] whether internal errors should be rescued and converted to JSON-RPC errors
|
|
@@ -123,6 +159,7 @@ module JSONRPC
|
|
|
123
159
|
# @return [Configuration] a new configuration instance
|
|
124
160
|
#
|
|
125
161
|
def initialize(
|
|
162
|
+
logger: Logger.new($stdout, progname: 'JSONRPC'),
|
|
126
163
|
log_internal_errors: true,
|
|
127
164
|
log_request_validation_errors: false,
|
|
128
165
|
rescue_internal_errors: true,
|
|
@@ -130,6 +167,7 @@ module JSONRPC
|
|
|
130
167
|
validate_procedure_signatures: true
|
|
131
168
|
)
|
|
132
169
|
@procedures = {}
|
|
170
|
+
@logger = logger
|
|
133
171
|
@log_internal_errors = log_internal_errors
|
|
134
172
|
@log_request_validation_errors = log_request_validation_errors
|
|
135
173
|
@rescue_internal_errors = rescue_internal_errors
|
|
@@ -167,8 +205,9 @@ module JSONRPC
|
|
|
167
205
|
#
|
|
168
206
|
# @param method_name [String, Symbol] the name of the procedure
|
|
169
207
|
# @param allow_positional_arguments [Boolean] whether the procedure accepts positional arguments
|
|
208
|
+
# @param block [Proc, nil] an optional block that defines the validation contract using Dry::Validation DSL
|
|
170
209
|
#
|
|
171
|
-
# @yield
|
|
210
|
+
# @yield A block that defines the validation contract using Dry::Validation DSL
|
|
172
211
|
#
|
|
173
212
|
# @return [Procedure] the registered procedure
|
|
174
213
|
#
|
data/lib/jsonrpc/error.rb
CHANGED
|
@@ -82,15 +82,14 @@ module JSONRPC
|
|
|
82
82
|
# @example Create an error with additional data
|
|
83
83
|
# error = JSONRPC::Error.new("Invalid params", code: -32602, data: { "field" => "missing" })
|
|
84
84
|
#
|
|
85
|
+
# @raise [ArgumentError] if code is not an Integer
|
|
86
|
+
# @raise [ArgumentError] if message is not a String
|
|
87
|
+
#
|
|
85
88
|
# @param message [String] short description of the error
|
|
86
89
|
# @param code [Integer] a number indicating the error type
|
|
87
90
|
# @param data [Hash, Array, String, Number, Boolean, nil] additional error information
|
|
88
91
|
# @param request_id [String, Integer, nil] the request identifier
|
|
89
92
|
#
|
|
90
|
-
# @raise [ArgumentError] if code is not an Integer
|
|
91
|
-
#
|
|
92
|
-
# @raise [ArgumentError] if message is not a String
|
|
93
|
-
#
|
|
94
93
|
def initialize(message, code:, data: nil, request_id: nil)
|
|
95
94
|
super(message)
|
|
96
95
|
|
|
@@ -128,7 +127,7 @@ module JSONRPC
|
|
|
128
127
|
# @return [String] the error as a JSON string
|
|
129
128
|
#
|
|
130
129
|
def to_json(*)
|
|
131
|
-
|
|
130
|
+
MultiJson.dump(to_h, *)
|
|
132
131
|
end
|
|
133
132
|
|
|
134
133
|
# Converts the error to a complete JSON-RPC response
|
|
@@ -150,10 +149,10 @@ module JSONRPC
|
|
|
150
149
|
#
|
|
151
150
|
# @api private
|
|
152
151
|
#
|
|
153
|
-
# @param code [Integer] the error code
|
|
154
|
-
#
|
|
155
152
|
# @raise [ArgumentError] if code is not an Integer
|
|
156
153
|
#
|
|
154
|
+
# @param code [Integer] the error code
|
|
155
|
+
#
|
|
157
156
|
# @return [void]
|
|
158
157
|
#
|
|
159
158
|
def validate_code(code)
|
|
@@ -164,10 +163,10 @@ module JSONRPC
|
|
|
164
163
|
#
|
|
165
164
|
# @api private
|
|
166
165
|
#
|
|
167
|
-
# @param message [String] the error message
|
|
168
|
-
#
|
|
169
166
|
# @raise [ArgumentError] if message is not a String
|
|
170
167
|
#
|
|
168
|
+
# @param message [String] the error message
|
|
169
|
+
#
|
|
171
170
|
# @return [void]
|
|
172
171
|
#
|
|
173
172
|
def validate_message(message)
|
|
@@ -6,6 +6,8 @@ module JSONRPC
|
|
|
6
6
|
# Raised when invalid JSON was received by the server.
|
|
7
7
|
# An error occurred on the server while parsing the JSON text.
|
|
8
8
|
#
|
|
9
|
+
# @api public
|
|
10
|
+
#
|
|
9
11
|
# @example Create a parse error
|
|
10
12
|
# error = JSONRPC::ParseError.new(data: { details: "Unexpected end of input" })
|
|
11
13
|
#
|
data/lib/jsonrpc/helpers.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module JSONRPC
|
|
4
4
|
# Framework-agnostic helpers for JSON-RPC
|
|
5
|
+
#
|
|
6
|
+
# @api public
|
|
7
|
+
#
|
|
5
8
|
module Helpers
|
|
6
9
|
# Extends the including class with ClassMethods when module is included
|
|
7
10
|
#
|
|
@@ -21,6 +24,9 @@ module JSONRPC
|
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
# Class methods for registering JSON-RPC procedure handlers
|
|
27
|
+
#
|
|
28
|
+
# @api public
|
|
29
|
+
#
|
|
24
30
|
module ClassMethods
|
|
25
31
|
# Registers a JSON-RPC procedure with the given method name
|
|
26
32
|
#
|
data/lib/jsonrpc/middleware.rb
CHANGED
|
@@ -41,13 +41,15 @@ module JSONRPC
|
|
|
41
41
|
# @option options [String] :path ('/') The path to handle JSON-RPC requests on
|
|
42
42
|
# @option options [Boolean] :rescue_internal_errors (nil) Override config rescue_internal_errors
|
|
43
43
|
# @option options [Boolean] :log_internal_errors (true) Override config log_internal_errors
|
|
44
|
+
# @option options [Logger] :logger (nil) Override config logger
|
|
44
45
|
#
|
|
45
46
|
def initialize(app, options = {})
|
|
46
47
|
@app = app
|
|
47
|
-
@parser = Parser.new
|
|
48
|
-
@validator = Validator.new
|
|
49
48
|
@path = options.fetch(:path, DEFAULT_PATH)
|
|
50
49
|
@config = JSONRPC.configuration
|
|
50
|
+
@logger = options.fetch(:logger, @config.logger)
|
|
51
|
+
@parser = Parser.new
|
|
52
|
+
@validator = Validator.new(logger: @logger)
|
|
51
53
|
@log_internal_errors = options.fetch(:log_internal_errors, @config.log_internal_errors)
|
|
52
54
|
@rescue_internal_errors = options.fetch(:rescue_internal_errors, @config.rescue_internal_errors)
|
|
53
55
|
end
|
|
@@ -89,10 +91,10 @@ module JSONRPC
|
|
|
89
91
|
#
|
|
90
92
|
# @api private
|
|
91
93
|
#
|
|
92
|
-
# @return [Array] Rack response tuple
|
|
93
|
-
#
|
|
94
94
|
# @raise [StandardError] Catches all errors and converts to Internal Error response
|
|
95
95
|
#
|
|
96
|
+
# @return [Array] Rack response tuple
|
|
97
|
+
#
|
|
96
98
|
def handle_jsonrpc_request
|
|
97
99
|
parsed_request = parse_request
|
|
98
100
|
return parsed_request if parsed_request.is_a?(Array) # Early return for parse errors
|
|
@@ -122,12 +124,11 @@ module JSONRPC
|
|
|
122
124
|
#
|
|
123
125
|
# @api private
|
|
124
126
|
#
|
|
125
|
-
# @return [Request, Notification, BatchRequest, Array] Parsed request or error response
|
|
126
|
-
#
|
|
127
127
|
# @raise [ParseError] When JSON parsing fails
|
|
128
|
-
#
|
|
129
128
|
# @raise [InvalidRequestError] When request structure is invalid
|
|
130
129
|
#
|
|
130
|
+
# @return [Request, Notification, BatchRequest, Array] Parsed request or error response
|
|
131
|
+
#
|
|
131
132
|
def parse_request
|
|
132
133
|
body = read_request_body
|
|
133
134
|
parsed = @parser.parse(body)
|
|
@@ -259,7 +260,7 @@ module JSONRPC
|
|
|
259
260
|
# @return [Array<Request, Notification>] Array of valid requests
|
|
260
261
|
#
|
|
261
262
|
def collect_valid_requests(batch_request)
|
|
262
|
-
batch_request.requests.
|
|
263
|
+
batch_request.requests.grep_v(Error)
|
|
263
264
|
end
|
|
264
265
|
|
|
265
266
|
# Builds ordered array of responses maintaining request order for batch processing
|
|
@@ -324,7 +325,7 @@ module JSONRPC
|
|
|
324
325
|
|
|
325
326
|
return [] unless status == 200 && !body.empty?
|
|
326
327
|
|
|
327
|
-
app_responses =
|
|
328
|
+
app_responses = MultiJson.load(body.join)
|
|
328
329
|
app_responses.map do |resp|
|
|
329
330
|
Response.new(id: resp['id'], result: resp['result'], error: resp['error'])
|
|
330
331
|
end
|
|
@@ -359,7 +360,8 @@ module JSONRPC
|
|
|
359
360
|
# @return [Array] Rack response tuple [status, headers, body]
|
|
360
361
|
#
|
|
361
362
|
def json_response(status, body)
|
|
362
|
-
|
|
363
|
+
json_body = body.is_a?(String) ? body : MultiJson.dump(body)
|
|
364
|
+
[status, { 'content-type' => 'application/json' }, [json_body]]
|
|
363
365
|
end
|
|
364
366
|
|
|
365
367
|
# Reads and returns the request body from the Rack environment
|
|
@@ -377,7 +379,7 @@ module JSONRPC
|
|
|
377
379
|
body_content
|
|
378
380
|
end
|
|
379
381
|
|
|
380
|
-
# Logs internal errors
|
|
382
|
+
# Logs internal errors with full backtrace using the configured logger
|
|
381
383
|
#
|
|
382
384
|
# @api private
|
|
383
385
|
#
|
|
@@ -389,8 +391,8 @@ module JSONRPC
|
|
|
389
391
|
# @return [void]
|
|
390
392
|
#
|
|
391
393
|
def log_internal_error(error)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
+
@logger.error("Internal error: #{error.message}")
|
|
395
|
+
@logger.error(error.backtrace.join("\n"))
|
|
394
396
|
end
|
|
395
397
|
end
|
|
396
398
|
end
|
data/lib/jsonrpc/notification.rb
CHANGED
|
@@ -62,13 +62,12 @@ module JSONRPC
|
|
|
62
62
|
# @example Create a notification with named parameters
|
|
63
63
|
# JSONRPC::Notification.new(method: "log", params: { level: "info", message: "Hello" })
|
|
64
64
|
#
|
|
65
|
-
# @param method [String] the name of the method to be invoked
|
|
66
|
-
# @param params [Hash, Array, nil] the parameters to be used during method invocation
|
|
67
|
-
#
|
|
68
65
|
# @raise [ArgumentError] if method is not a String or is reserved
|
|
69
|
-
#
|
|
70
66
|
# @raise [ArgumentError] if params is not a Hash, Array, or nil
|
|
71
67
|
#
|
|
68
|
+
# @param method [String] the name of the method to be invoked
|
|
69
|
+
# @param params [Hash, Array, nil] the parameters to be used during method invocation
|
|
70
|
+
#
|
|
72
71
|
def initialize(method:, params: nil)
|
|
73
72
|
@jsonrpc = '2.0'
|
|
74
73
|
|
|
@@ -108,7 +107,7 @@ module JSONRPC
|
|
|
108
107
|
# @return [String] the notification as a JSON string
|
|
109
108
|
#
|
|
110
109
|
def to_json(*)
|
|
111
|
-
|
|
110
|
+
MultiJson.dump(to_h, *)
|
|
112
111
|
end
|
|
113
112
|
|
|
114
113
|
private
|
|
@@ -117,10 +116,10 @@ module JSONRPC
|
|
|
117
116
|
#
|
|
118
117
|
# @api private
|
|
119
118
|
#
|
|
120
|
-
# @param method [String] the method name
|
|
121
|
-
#
|
|
122
119
|
# @raise [ArgumentError] if method is not a String or is reserved
|
|
123
120
|
#
|
|
121
|
+
# @param method [String] the method name
|
|
122
|
+
#
|
|
124
123
|
# @return [void]
|
|
125
124
|
#
|
|
126
125
|
def validate_method(method)
|
|
@@ -135,10 +134,10 @@ module JSONRPC
|
|
|
135
134
|
#
|
|
136
135
|
# @api private
|
|
137
136
|
#
|
|
138
|
-
# @param params [Hash, Array, nil] the parameters
|
|
139
|
-
#
|
|
140
137
|
# @raise [ArgumentError] if params is not a Hash, Array, or nil
|
|
141
138
|
#
|
|
139
|
+
# @param params [Hash, Array, nil] the parameters
|
|
140
|
+
#
|
|
142
141
|
# @return [void]
|
|
143
142
|
#
|
|
144
143
|
def validate_params(params)
|
data/lib/jsonrpc/parser.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
3
|
module JSONRPC
|
|
6
4
|
# JSON-RPC 2.0 Parser for converting raw JSON into JSONRPC objects
|
|
7
5
|
#
|
|
8
6
|
# The Parser handles converting raw JSON strings into appropriate JSONRPC objects
|
|
9
7
|
# based on the JSON-RPC 2.0 protocol specification.
|
|
10
8
|
#
|
|
9
|
+
# @api public
|
|
10
|
+
#
|
|
11
11
|
# @example Parse a request
|
|
12
12
|
# parser = JSONRPC::Parser.new
|
|
13
13
|
# request = parser.parse('{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1}')
|
|
@@ -28,26 +28,29 @@ module JSONRPC
|
|
|
28
28
|
# @example Parse a batch request
|
|
29
29
|
# parser.parse('[{"jsonrpc":"2.0","method":"sum","params":[1,2],"id":"1"}]')
|
|
30
30
|
#
|
|
31
|
+
# @raise [ParseError] if the JSON is invalid
|
|
32
|
+
# @raise [InvalidRequestError] if the request structure is invalid
|
|
33
|
+
#
|
|
31
34
|
# @param json [String] the JSON-RPC 2.0 message
|
|
32
35
|
#
|
|
33
36
|
# @return [Request, Notification, BatchRequest] the parsed object
|
|
34
37
|
#
|
|
35
|
-
# @raise [ParseError] if the JSON is invalid
|
|
36
|
-
#
|
|
37
|
-
# @raise [InvalidRequestError] if the request structure is invalid
|
|
38
|
-
#
|
|
39
38
|
def parse(json)
|
|
40
|
-
|
|
41
|
-
data = JSON.parse(json)
|
|
42
|
-
rescue JSON::ParserError => e
|
|
43
|
-
raise ParseError.new(data: { details: e.message })
|
|
44
|
-
end
|
|
39
|
+
data = MultiJson.load(json)
|
|
45
40
|
|
|
46
41
|
if data.is_a?(Array)
|
|
47
42
|
parse_batch(data)
|
|
48
43
|
else
|
|
49
44
|
parse_single(data)
|
|
50
45
|
end
|
|
46
|
+
rescue MultiJson::ParseError => e
|
|
47
|
+
raise ParseError.new(
|
|
48
|
+
data: {
|
|
49
|
+
details: e.message,
|
|
50
|
+
adapter: MultiJson.adapter.name,
|
|
51
|
+
input_preview: json[0..100]
|
|
52
|
+
}
|
|
53
|
+
)
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
private
|
|
@@ -81,12 +84,12 @@ module JSONRPC
|
|
|
81
84
|
#
|
|
82
85
|
# @api private
|
|
83
86
|
#
|
|
87
|
+
# @raise [InvalidRequestError] if the batch is empty
|
|
88
|
+
#
|
|
84
89
|
# @param data [Array] the array of request data
|
|
85
90
|
#
|
|
86
91
|
# @return [BatchRequest] the batch request
|
|
87
92
|
#
|
|
88
|
-
# @raise [InvalidRequestError] if the batch is empty
|
|
89
|
-
#
|
|
90
93
|
def parse_batch(data)
|
|
91
94
|
raise InvalidRequestError.new(data: { details: 'Batch request cannot be empty' }) if data.empty?
|
|
92
95
|
|
|
@@ -112,12 +115,12 @@ module JSONRPC
|
|
|
112
115
|
#
|
|
113
116
|
# @api private
|
|
114
117
|
#
|
|
118
|
+
# @raise [InvalidRequestError] if the request structure is invalid
|
|
119
|
+
#
|
|
115
120
|
# @param data [Hash] the parsed JSON data for a single item
|
|
116
121
|
#
|
|
117
122
|
# @return [Request, Notification] the parsed request or notification
|
|
118
123
|
#
|
|
119
|
-
# @raise [InvalidRequestError] if the request structure is invalid
|
|
120
|
-
#
|
|
121
124
|
def parse_single_for_batch(data)
|
|
122
125
|
validate_jsonrpc_version(data)
|
|
123
126
|
validate_request_structure(data)
|
|
@@ -139,10 +142,10 @@ module JSONRPC
|
|
|
139
142
|
#
|
|
140
143
|
# @api private
|
|
141
144
|
#
|
|
142
|
-
# @param data [Hash] the request data
|
|
143
|
-
#
|
|
144
145
|
# @raise [InvalidRequestError] if the version is missing or invalid
|
|
145
146
|
#
|
|
147
|
+
# @param data [Hash] the request data
|
|
148
|
+
#
|
|
146
149
|
# @return [void]
|
|
147
150
|
#
|
|
148
151
|
def validate_jsonrpc_version(data)
|
|
@@ -168,10 +171,10 @@ module JSONRPC
|
|
|
168
171
|
#
|
|
169
172
|
# @api private
|
|
170
173
|
#
|
|
171
|
-
# @param data [Hash] the request data
|
|
172
|
-
#
|
|
173
174
|
# @raise [InvalidRequestError] if the request structure is invalid
|
|
174
175
|
#
|
|
176
|
+
# @param data [Hash] the request data
|
|
177
|
+
#
|
|
175
178
|
# @return [void]
|
|
176
179
|
#
|
|
177
180
|
def validate_request_structure(data)
|
|
@@ -8,8 +8,6 @@ module JSONRPC
|
|
|
8
8
|
module MapperExtension
|
|
9
9
|
# Define JSON-RPC routes with a DSL
|
|
10
10
|
#
|
|
11
|
-
# @param path [String] the path to handle JSON-RPC requests on
|
|
12
|
-
#
|
|
13
11
|
# @example Define JSON-RPC routes
|
|
14
12
|
# jsonrpc '/api/v1' do
|
|
15
13
|
# # Handle batch requests
|
|
@@ -24,6 +22,8 @@ module JSONRPC
|
|
|
24
22
|
# end
|
|
25
23
|
# end
|
|
26
24
|
#
|
|
25
|
+
# @param path [String] the path to handle JSON-RPC requests on
|
|
26
|
+
#
|
|
27
27
|
# @return [void]
|
|
28
28
|
#
|
|
29
29
|
def jsonrpc(path = '/', &)
|
|
@@ -22,6 +22,7 @@ module JSONRPC
|
|
|
22
22
|
# Check if the request matches the configured method name
|
|
23
23
|
#
|
|
24
24
|
# @param request [ActionDispatch::Request] The Rails request object
|
|
25
|
+
#
|
|
25
26
|
# @return [Boolean] true if the JSON-RPC method matches, false otherwise
|
|
26
27
|
#
|
|
27
28
|
def matches?(request)
|
|
@@ -31,5 +32,13 @@ module JSONRPC
|
|
|
31
32
|
|
|
32
33
|
jsonrpc_request.method == @jsonrpc_method_name
|
|
33
34
|
end
|
|
35
|
+
|
|
36
|
+
# Returns a string representation of the constraint
|
|
37
|
+
#
|
|
38
|
+
# @return [String] A string showing the configured JSON-RPC method name
|
|
39
|
+
#
|
|
40
|
+
def to_s
|
|
41
|
+
"#<JSONRPC::MethodConstraint method=\"#{@jsonrpc_method_name}\">"
|
|
42
|
+
end
|
|
34
43
|
end
|
|
35
44
|
end
|