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
@@ -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
- to_h.to_json(*)
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
- to_h.to_json(*)
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
- # @!method allow_positional_arguments
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
- # @!method contract
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
- # @!method parameter_name
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 [optional] A block that defines the validation contract using Dry::Validation DSL
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
- to_h.to_json(*)
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)
@@ -5,6 +5,8 @@ module JSONRPC
5
5
  #
6
6
  # Raised when there was an internal JSON-RPC error.
7
7
  #
8
+ # @api public
9
+ #
8
10
  # @example Create an internal error
9
11
  # error = JSONRPC::Errors::InternalError.new(data: { details: 'Unexpected server error' })
10
12
  #
@@ -5,6 +5,8 @@ module JSONRPC
5
5
  #
6
6
  # Raised when invalid method parameter(s) were provided.
7
7
  #
8
+ # @api public
9
+ #
8
10
  # @example Create an invalid params error
9
11
  # error = JSONRPC::InvalidParamsError.new(data: { details: "Expected array of integers" })
10
12
  #
@@ -5,6 +5,8 @@ module JSONRPC
5
5
  #
6
6
  # Raised when the JSON sent is not a valid Request object.
7
7
  #
8
+ # @api public
9
+ #
8
10
  # @example Create an invalid request error
9
11
  # error = JSONRPC::InvalidRequestError.new(data: { details: "Method must be a string" })
10
12
  #
@@ -5,6 +5,8 @@ module JSONRPC
5
5
  #
6
6
  # Raised when the method does not exist / is not available.
7
7
  #
8
+ # @api public
9
+ #
8
10
  # @example Create a method not found error
9
11
  # error = JSONRPC::MethodNotFound.new(data: { requested_method: "unknown_method" })
10
12
  #
@@ -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
  #
@@ -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
  #
@@ -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.reject { |item| item.is_a?(Error) }
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 = JSON.parse(body.join)
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
- [status, { 'content-type' => 'application/json' }, [body.is_a?(String) ? body : JSON.generate(body)]]
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 to stdout with full backtrace
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
- puts "Internal error: #{error.message}"
393
- puts error.backtrace.join("\n")
394
+ @logger.error("Internal error: #{error.message}")
395
+ @logger.error(error.backtrace.join("\n"))
394
396
  end
395
397
  end
396
398
  end
@@ -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
- to_h.to_json(*)
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)
@@ -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
- begin
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)
@@ -13,6 +13,7 @@ module JSONRPC
13
13
  # Check if the request is a JSON-RPC batch request
14
14
  #
15
15
  # @param request [ActionDispatch::Request] The Rails request object
16
+ #
16
17
  # @return [Boolean] true if the request is a batch request, false otherwise
17
18
  #
18
19
  def matches?(request)
@@ -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