jsonrpc-middleware 0.1.0 → 0.2.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.aiexclude +4 -0
  3. data/.claude/commands/document.md +105 -0
  4. data/.claude/docs/yard.md +602 -0
  5. data/.claude/settings.local.json +2 -1
  6. data/.env.example +5 -0
  7. data/CHANGELOG.md +22 -2
  8. data/CLAUDE.md +114 -0
  9. data/README.md +42 -102
  10. data/Rakefile +59 -1
  11. data/examples/README.md +37 -0
  12. data/examples/procedures.rb +6 -1
  13. data/examples/rack/README.md +26 -1
  14. data/examples/rack/app.rb +1 -1
  15. data/examples/rack-echo/README.md +23 -1
  16. data/examples/rack-single-file/README.md +37 -0
  17. data/examples/rack-single-file/config.ru +54 -0
  18. data/examples/rails/.gitignore +21 -0
  19. data/examples/rails/.ruby-version +1 -0
  20. data/examples/rails/Gemfile +15 -0
  21. data/examples/rails/Gemfile.lock +261 -0
  22. data/examples/rails/README.md +32 -0
  23. data/examples/rails/Rakefile +8 -0
  24. data/examples/rails/app/controllers/application_controller.rb +4 -0
  25. data/examples/rails/app/controllers/jsonrpc_controller.rb +44 -0
  26. data/examples/rails/bin/dev +4 -0
  27. data/examples/rails/bin/rails +6 -0
  28. data/examples/rails/bin/rake +6 -0
  29. data/examples/rails/bin/setup +28 -0
  30. data/examples/rails/config/application.rb +47 -0
  31. data/examples/rails/config/boot.rb +5 -0
  32. data/examples/rails/config/credentials.yml.enc +1 -0
  33. data/examples/rails/config/environment.rb +7 -0
  34. data/examples/rails/config/environments/development.rb +42 -0
  35. data/examples/rails/config/environments/production.rb +60 -0
  36. data/examples/rails/config/environments/test.rb +44 -0
  37. data/examples/rails/config/initializers/cors.rb +18 -0
  38. data/examples/rails/config/initializers/filter_parameter_logging.rb +10 -0
  39. data/examples/rails/config/initializers/inflections.rb +18 -0
  40. data/examples/rails/config/initializers/jsonrpc.rb +62 -0
  41. data/examples/rails/config/locales/en.yml +31 -0
  42. data/examples/rails/config/puma.rb +40 -0
  43. data/examples/rails/config/routes.rb +14 -0
  44. data/examples/rails/config.ru +8 -0
  45. data/examples/rails/public/robots.txt +1 -0
  46. data/examples/rails-single-file/config.ru +71 -0
  47. data/examples/sinatra-classic/Gemfile +9 -0
  48. data/examples/sinatra-classic/Gemfile.lock +95 -0
  49. data/examples/sinatra-classic/README.md +32 -0
  50. data/examples/sinatra-classic/app.rb +54 -0
  51. data/examples/sinatra-classic/config.ru +6 -0
  52. data/examples/sinatra-modular/Gemfile +9 -0
  53. data/examples/sinatra-modular/Gemfile.lock +95 -0
  54. data/examples/sinatra-modular/README.md +32 -0
  55. data/examples/sinatra-modular/app.rb +57 -0
  56. data/examples/sinatra-modular/config.ru +6 -0
  57. data/lib/jsonrpc/batch_request.rb +67 -2
  58. data/lib/jsonrpc/batch_response.rb +56 -0
  59. data/lib/jsonrpc/configuration.rb +156 -14
  60. data/lib/jsonrpc/error.rb +83 -2
  61. data/lib/jsonrpc/errors/internal_error.rb +14 -2
  62. data/lib/jsonrpc/errors/invalid_params_error.rb +13 -1
  63. data/lib/jsonrpc/errors/invalid_request_error.rb +8 -0
  64. data/lib/jsonrpc/errors/method_not_found_error.rb +8 -0
  65. data/lib/jsonrpc/errors/parse_error.rb +8 -0
  66. data/lib/jsonrpc/helpers.rb +212 -21
  67. data/lib/jsonrpc/middleware.rb +211 -5
  68. data/lib/jsonrpc/notification.rb +58 -0
  69. data/lib/jsonrpc/parser.rb +30 -0
  70. data/lib/jsonrpc/railtie.rb +57 -0
  71. data/lib/jsonrpc/request.rb +68 -1
  72. data/lib/jsonrpc/response.rb +76 -0
  73. data/lib/jsonrpc/validator.rb +67 -6
  74. data/lib/jsonrpc/version.rb +11 -1
  75. data/lib/jsonrpc.rb +53 -1
  76. metadata +49 -1
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/base'
4
+ require 'sinatra/json'
5
+ require 'jsonrpc'
6
+
7
+ # App is a Sinatra::Base subclass that provides JSON-RPC endpoint handling for requests and batches.
8
+ class App < Sinatra::Base
9
+ set :host_authorization, permitted_hosts: []
10
+ set :raise_errors, true
11
+ set :show_exceptions, false
12
+
13
+ use JSONRPC::Middleware
14
+ helpers JSONRPC::Helpers
15
+
16
+ post '/' do
17
+ @env = env # Set the @env instance variable to use the JSONRPC helpers below
18
+
19
+ if jsonrpc_request?
20
+ result = handle_single(jsonrpc_request)
21
+ jsonrpc_response(result)
22
+ elsif jsonrpc_notification?
23
+ handle_single(jsonrpc_notification)
24
+ jsonrpc_notification_response
25
+ else
26
+ responses = handle_batch(jsonrpc_batch)
27
+ jsonrpc_batch_response(responses)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def handle_single(request_or_notification)
34
+ params = request_or_notification.params
35
+
36
+ case request_or_notification.method
37
+ when 'add'
38
+ addends = params.is_a?(Array) ? params : params['addends'] # Handle positional and named arguments
39
+ addends.sum
40
+ when 'subtract'
41
+ params['minuend'] - params['subtrahend']
42
+ when 'multiply'
43
+ params['multiplicand'] * params['multiplier']
44
+ when 'divide'
45
+ params['dividend'] / params['divisor']
46
+ when 'explode'
47
+ raise 'An internal error has occurred.'
48
+ end
49
+ end
50
+
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
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './app'
4
+ require '../procedures'
5
+
6
+ run App
@@ -17,16 +17,34 @@ module JSONRPC
17
17
  include Enumerable
18
18
 
19
19
  # The collection of request objects in this batch (may include errors)
20
+ #
21
+ # @api public
22
+ #
23
+ # @example Accessing requests in a batch
24
+ # batch = JSONRPC::BatchRequest.new([request1, request2])
25
+ # batch.requests # => [#<JSONRPC::Request...>, #<JSONRPC::Request...>]
26
+ #
20
27
  # @return [Array<JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error>]
21
28
  #
22
29
  attr_reader :requests
23
30
 
24
31
  # Creates a new JSON-RPC 2.0 Batch Request object
25
32
  #
33
+ # @api public
34
+ #
35
+ # @example Create a batch request
36
+ # requests = [
37
+ # JSONRPC::Request.new(method: 'add', params: [1, 2], id: 1),
38
+ # JSONRPC::Notification.new(method: 'notify', params: ['hello'])
39
+ # ]
40
+ # batch = JSONRPC::BatchRequest.new(requests)
41
+ #
26
42
  # @param requests [Array<JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error>] an array of request objects
27
43
  # or errors
28
44
  # @raise [ArgumentError] if requests is not an Array
45
+ #
29
46
  # @raise [ArgumentError] if requests is empty
47
+ #
30
48
  # @raise [ArgumentError] if any request is not a valid Request, Notification, or Error
31
49
  #
32
50
  def initialize(requests)
@@ -36,21 +54,43 @@ module JSONRPC
36
54
 
37
55
  # Converts the batch request to a JSON-compatible Array
38
56
  #
57
+ # @api public
58
+ #
59
+ # @example Convert batch to hash
60
+ # batch.to_h # => [{"jsonrpc":"2.0","method":"add","params":[1,2],"id":1}]
61
+ #
39
62
  # @return [Array<Hash>] the batch request as a JSON-compatible Array
40
63
  #
41
64
  def to_h
42
65
  requests.map { |item| item.respond_to?(:to_h) ? item.to_h : item }
43
66
  end
44
67
 
68
+ # Converts the batch to JSON format
69
+ #
70
+ # @api public
71
+ #
72
+ # @example Convert batch to JSON
73
+ # batch.to_json # => '[{"id":"1","method":"sum","params":[1,2,4]}]'
74
+ #
75
+ # @return [String] the JSON-formatted batch
76
+ #
45
77
  def to_json(*)
46
78
  to_h.to_json(*)
47
79
  end
48
80
 
49
81
  # Implements the Enumerable contract by yielding each request in the batch
50
82
  #
83
+ # @api public
84
+ #
85
+ # @example Iterate over requests
86
+ # batch.each { |request| puts request.method }
87
+ #
51
88
  # @yield [request] Yields each request in the batch to the block
89
+ #
52
90
  # @yieldparam request [JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error] a request in the batch
91
+ #
53
92
  # @return [Enumerator] if no block is given
93
+ #
54
94
  # @return [BatchRequest] self if a block is given
55
95
  #
56
96
  def each(&)
@@ -62,17 +102,35 @@ module JSONRPC
62
102
 
63
103
  # Returns the number of requests in the batch
64
104
  #
105
+ # @api public
106
+ #
107
+ # @example Get batch size
108
+ # batch.size # => 3
109
+ #
65
110
  # @return [Integer] the number of requests in the batch
66
111
  #
67
112
  def size
68
113
  requests.size
69
114
  end
70
115
 
71
- # Alias for size for Array-like interface
116
+ # Alias for size method providing Array-like interface
117
+ #
118
+ # @api public
119
+ #
120
+ # @example Get batch length
121
+ # batch.length # => 3
122
+ #
123
+ # @return [Integer] the number of requests in the batch
124
+ #
72
125
  alias length size
73
126
 
74
127
  # Returns true if the batch contains no requests
75
128
  #
129
+ # @api public
130
+ #
131
+ # @example Check if batch is empty
132
+ # batch.empty? # => false
133
+ #
76
134
  # @return [Boolean] true if the batch is empty, false otherwise
77
135
  #
78
136
  def empty?
@@ -81,13 +139,20 @@ module JSONRPC
81
139
 
82
140
  private
83
141
 
84
- # Validates that the requests is a valid array of Request/Notification/Error objects
142
+ # Validates the requests array
143
+ #
144
+ # @api private
85
145
  #
86
146
  # @param requests [Array] the array of requests
147
+ #
87
148
  # @raise [ArgumentError] if requests is not an Array
149
+ #
88
150
  # @raise [ArgumentError] if requests is empty
151
+ #
89
152
  # @raise [ArgumentError] if any request is not a valid Request, Notification, or Error
90
153
  #
154
+ # @return [void]
155
+ #
91
156
  def validate_requests(requests)
92
157
  raise ArgumentError, 'Requests must be an Array' unless requests.is_a?(Array)
93
158
  raise ArgumentError, 'Batch request cannot be empty' if requests.empty?
@@ -18,15 +18,33 @@ module JSONRPC
18
18
  include Enumerable
19
19
 
20
20
  # The collection of response objects in this batch
21
+ #
22
+ # @api public
23
+ #
24
+ # @example Accessing responses in a batch
25
+ # batch.responses # => [#<JSONRPC::Response...>, #<JSONRPC::Response...>]
26
+ #
21
27
  # @return [Array<JSONRPC::Response>]
22
28
  #
23
29
  attr_reader :responses
24
30
 
25
31
  # Creates a new JSON-RPC 2.0 Batch Response object
26
32
  #
33
+ # @api public
34
+ #
35
+ # @example Create a batch response
36
+ # responses = [
37
+ # JSONRPC::Response.new(result: 42, id: 1),
38
+ # JSONRPC::Response.new(result: "hello", id: 2)
39
+ # ]
40
+ # batch = JSONRPC::BatchResponse.new(responses)
41
+ #
27
42
  # @param responses [Array<JSONRPC::Response>] an array of response objects
43
+ #
28
44
  # @raise [ArgumentError] if responses is not an Array
45
+ #
29
46
  # @raise [ArgumentError] if responses is empty
47
+ #
30
48
  # @raise [ArgumentError] if any response is not a valid Response
31
49
  #
32
50
  def initialize(responses)
@@ -36,21 +54,43 @@ module JSONRPC
36
54
 
37
55
  # Converts the batch response to a JSON-compatible Array
38
56
  #
57
+ # @api public
58
+ #
59
+ # @example Convert batch to hash
60
+ # batch.to_h # => [{"jsonrpc":"2.0","result":42,"id":1}]
61
+ #
39
62
  # @return [Array<Hash>] the batch response as a JSON-compatible Array
40
63
  #
41
64
  def to_h
42
65
  responses.map(&:to_h)
43
66
  end
44
67
 
68
+ # Converts the batch response to a JSON string
69
+ #
70
+ # @api public
71
+ #
72
+ # @example Convert batch to JSON
73
+ # batch.to_json # => '[{"jsonrpc":"2.0","result":42,"id":1}]'
74
+ #
75
+ # @return [String] the JSON-formatted batch response
76
+ #
45
77
  def to_json(*)
46
78
  to_h.to_json(*)
47
79
  end
48
80
 
49
81
  # Implements the Enumerable contract by yielding each response in the batch
50
82
  #
83
+ # @api public
84
+ #
85
+ # @example Iterate over responses
86
+ # batch.each { |response| puts response.result }
87
+ #
51
88
  # @yield [response] Yields each response in the batch to the block
89
+ #
52
90
  # @yieldparam response [JSONRPC::Response] a response in the batch
91
+ #
53
92
  # @return [Enumerator] if no block is given
93
+ #
54
94
  # @return [BatchResponse] self if a block is given
55
95
  #
56
96
  def each(&)
@@ -60,6 +100,15 @@ module JSONRPC
60
100
  self
61
101
  end
62
102
 
103
+ # Converts the batch response to a response format
104
+ #
105
+ # @api public
106
+ #
107
+ # @example Convert to response format
108
+ # batch.to_response # => Array of response hashes
109
+ #
110
+ # @return [Array] array of response objects
111
+ #
63
112
  def to_response
64
113
  responses.map(&:to_response)
65
114
  end
@@ -68,11 +117,18 @@ module JSONRPC
68
117
 
69
118
  # Validates that the responses is a valid array of Response objects
70
119
  #
120
+ # @api private
121
+ #
71
122
  # @param responses [Array] the array of responses
123
+ #
72
124
  # @raise [ArgumentError] if responses is not an Array
125
+ #
73
126
  # @raise [ArgumentError] if responses is empty
127
+ #
74
128
  # @raise [ArgumentError] if any response is not a valid Response
75
129
  #
130
+ # @return [void]
131
+ #
76
132
  def validate_responses(responses)
77
133
  raise ArgumentError, 'Responses must be an Array' unless responses.is_a?(Array)
78
134
  raise ArgumentError, 'Batch response cannot be empty' if responses.empty?
@@ -2,7 +2,11 @@
2
2
 
3
3
  module JSONRPC
4
4
  # Configuration class for JSON-RPC procedure management and validation.
5
+ #
6
+ # @api public
7
+ #
5
8
  # This class provides functionality to register, retrieve, and validate JSON-RPC procedures.
9
+ # It acts as a central registry for method definitions and their parameter constraints.
6
10
  #
7
11
  # @example Registering a procedure
8
12
  # JSONRPC::Configuration.instance.procedure('sum') do
@@ -12,41 +16,159 @@ module JSONRPC
12
16
  # end
13
17
  #
14
18
  class Configuration
15
- # Represents a registered JSON-RPC procedure with its validation contract and configuration.
19
+ # Represents a registered JSON-RPC procedure with its validation contract and configuration
20
+ #
21
+ # @api public
22
+ #
23
+ # @!method allow_positional_arguments
24
+ # Indicates if the procedure accepts positional arguments
25
+ # @api public
16
26
  #
17
- # @!attribute [r] allow_positional_arguments
27
+ # @example
28
+ # procedure.allow_positional_arguments # => true
18
29
  # @return [Boolean] whether the procedure accepts positional arguments
19
- # @!attribute [r] contract
30
+ #
31
+ # @!method contract
32
+ # The validation contract for procedure parameters
33
+ # @api public
34
+ #
35
+ # @example
36
+ # procedure.contract # => #<Dry::Validation::Contract...>
20
37
  # @return [Dry::Validation::Contract] the validation contract for procedure parameters
21
- # @!attribute [r] parameter_name
38
+ #
39
+ # @!method parameter_name
40
+ # The name of the first parameter in the contract schema
41
+ # @api public
42
+ #
43
+ # @example
44
+ # procedure.parameter_name # => :numbers
22
45
  # @return [Symbol, nil] the name of the first parameter in the contract schema
46
+ #
23
47
  Procedure = Data.define(:allow_positional_arguments, :contract, :parameter_name)
24
48
 
25
- # @!attribute [r] validate_procedure_signatures
26
- # @return [Boolean] whether procedure signatures are validated
49
+ # Whether to log detailed internal error information in the terminal
50
+ #
51
+ # @api public
52
+ #
53
+ # @example
54
+ # config.log_internal_errors # => true
55
+ #
56
+ # @return [Boolean] whether to log internal error details
57
+ #
58
+ attr_accessor :log_internal_errors
59
+
60
+ # Whether to log validation errors during JSON-RPC request processing
61
+ #
62
+ # @api public
63
+ #
64
+ # @example
65
+ # config.log_request_validation_errors # => false
66
+ #
67
+ # @return [Boolean] whether to log JSON-RPC request validation errors
68
+ #
69
+ attr_accessor :log_request_validation_errors
70
+
71
+ # Whether to render detailed internal error information in responses
72
+ #
73
+ # @api public
74
+ #
75
+ # @example
76
+ # config.render_internal_errors # => true
77
+ #
78
+ # @return [Boolean] whether to log internal error details
79
+ #
80
+ attr_accessor :render_internal_errors
81
+
82
+ # Whether internal errors should be rescued and converted to JSON-RPC errors
83
+ #
84
+ # @api public
85
+ #
86
+ # @example
87
+ # config.rescue_internal_errors # => true
88
+ #
89
+ # @return [Boolean] whether internal errors are rescued
90
+ #
91
+ attr_accessor :rescue_internal_errors
92
+
93
+ # Whether procedure signatures are validated
94
+ #
95
+ # @api public
96
+ #
97
+ # @example
98
+ # config.validate_procedure_signatures # => true
99
+ #
100
+ # @return [Boolean] whether procedure signatures are validated
101
+ #
27
102
  attr_reader :validate_procedure_signatures
28
103
 
29
- # Initializes a new Configuration instance.
104
+ # Initializes a new Configuration instance
105
+ #
106
+ # @api public
107
+ #
108
+ # @example
109
+ # config = JSONRPC::Configuration.new
110
+ #
111
+ # @example With custom options
112
+ # config = JSONRPC::Configuration.new(
113
+ # log_request_validation_errors: true,
114
+ # render_internal_errors: true
115
+ # )
116
+ #
117
+ # @param log_internal_errors [Boolean] whether to log detailed internal error information in the terminal
118
+ # @param log_request_validation_errors [Boolean] whether to log validation errors during JSON-RPC request processing
119
+ # @param rescue_internal_errors [Boolean] whether internal errors should be rescued and converted to JSON-RPC errors
120
+ # @param render_internal_errors [Boolean] whether to render detailed internal error information in responses
121
+ # @param validate_procedure_signatures [Boolean] whether procedure signatures are validated
30
122
  #
31
123
  # @return [Configuration] a new configuration instance
32
- def initialize
124
+ #
125
+ def initialize(
126
+ log_internal_errors: true,
127
+ log_request_validation_errors: false,
128
+ rescue_internal_errors: true,
129
+ render_internal_errors: false,
130
+ validate_procedure_signatures: true
131
+ )
33
132
  @procedures = {}
34
- @validate_procedure_signatures = true
133
+ @log_internal_errors = log_internal_errors
134
+ @log_request_validation_errors = log_request_validation_errors
135
+ @rescue_internal_errors = rescue_internal_errors
136
+ @render_internal_errors = render_internal_errors
137
+ @validate_procedure_signatures = validate_procedure_signatures
35
138
  end
36
139
 
37
- # Returns the singleton instance of the Configuration class.
140
+ # Returns the singleton instance of the Configuration class
141
+ #
142
+ # @api public
143
+ #
144
+ # @example
145
+ # config = JSONRPC::Configuration.instance
38
146
  #
39
147
  # @return [Configuration] the singleton instance
148
+ #
40
149
  def self.instance
41
150
  @instance ||= new
42
151
  end
43
152
 
44
- # Registers a new procedure with the given method name and validation contract.
153
+ # Registers a new procedure with the given method name and validation contract
154
+ #
155
+ # @api public
156
+ #
157
+ # @example Register a simple procedure
158
+ # config.procedure('add') do
159
+ # params do
160
+ # required(:a).value(:integer)
161
+ # required(:b).value(:integer)
162
+ # end
163
+ # end
45
164
  #
46
165
  # @param method_name [String, Symbol] the name of the procedure
47
166
  # @param allow_positional_arguments [Boolean] whether the procedure accepts positional arguments
167
+ #
48
168
  # @yield A block that defines the validation contract using Dry::Validation DSL
169
+ #
49
170
  # @return [Procedure] the registered procedure
171
+ #
50
172
  def procedure(method_name, allow_positional_arguments: false, &)
51
173
  contract_class = Class.new(Dry::Validation::Contract, &)
52
174
  contract_class.class_eval { import_predicates_as_macros }
@@ -59,25 +181,45 @@ module JSONRPC
59
181
  )
60
182
  end
61
183
 
62
- # Retrieves a procedure by its method name.
184
+ # Retrieves a procedure by its method name
185
+ #
186
+ # @api public
187
+ #
188
+ # @example
189
+ # procedure = config.get_procedure('add')
63
190
  #
64
191
  # @param method_name [String, Symbol] the name of the procedure to retrieve
192
+ #
65
193
  # @return [Procedure, nil] the procedure if found, nil otherwise
194
+ #
66
195
  def get_procedure(method_name)
67
196
  @procedures[method_name.to_s]
68
197
  end
69
198
 
70
- # Checks if a procedure with the given method name exists.
199
+ # Checks if a procedure with the given method name exists
200
+ #
201
+ # @api public
202
+ #
203
+ # @example
204
+ # config.procedure?('add') # => true
71
205
  #
72
206
  # @param method_name [String, Symbol] the name of the procedure to check
207
+ #
73
208
  # @return [Boolean] true if the procedure exists, false otherwise
209
+ #
74
210
  def procedure?(method_name)
75
211
  @procedures.key?(method_name.to_s)
76
212
  end
77
213
 
78
- # Clears all registered procedures.
214
+ # Clears all registered procedures
215
+ #
216
+ # @api public
217
+ #
218
+ # @example
219
+ # config.reset!
79
220
  #
80
221
  # @return [void]
222
+ #
81
223
  def reset!
82
224
  @procedures.clear
83
225
  end
data/lib/jsonrpc/error.rb CHANGED
@@ -3,44 +3,92 @@
3
3
  module JSONRPC
4
4
  # A JSON-RPC 2.0 Error object
5
5
  #
6
+ # @api public
7
+ #
6
8
  # When a rpc call encounters an error, the Response Object must contain an Error object
7
- # with specific properties according to the JSON-RPC 2.0.
9
+ # with specific properties according to the JSON-RPC 2.0 specification.
8
10
  #
9
11
  # @example Create an error
10
12
  # error = JSONRPC::Error.new(
13
+ # "Invalid Request",
11
14
  # code: -32600,
12
- # message: "Invalid Request",
13
15
  # data: { detail: "Additional information about the error" }
14
16
  # )
15
17
  #
16
18
  class Error < StandardError
17
19
  # The request identifier (optional for notifications)
20
+ #
21
+ # @api public
22
+ #
23
+ # @example Get request ID
24
+ # error.request_id # => "1"
25
+ #
26
+ # @example Set request ID
27
+ # error.request_id = "2"
28
+ #
18
29
  # @return [String, Integer, nil]
19
30
  #
20
31
  attr_accessor :request_id
21
32
 
22
33
  # Error code indicating the error type
34
+ #
35
+ # @api public
36
+ #
37
+ # @example Get error code
38
+ # error.code # => -32600
39
+ #
40
+ # @example Set error code
41
+ # error.code = -32601
42
+ #
23
43
  # @return [Integer]
24
44
  #
25
45
  attr_accessor :code
26
46
 
27
47
  # Short description of the error
48
+ #
49
+ # @api public
50
+ #
51
+ # @example Get error message
52
+ # error.message # => "Invalid Request"
53
+ #
54
+ # @example Set error message
55
+ # error.message = "Method not found"
56
+ #
28
57
  # @return [String]
29
58
  #
30
59
  attr_accessor :message
31
60
 
32
61
  # Additional information about the error (optional)
62
+ #
63
+ # @api public
64
+ #
65
+ # @example Get error data
66
+ # error.data # => { "detail" => "Additional info" }
67
+ #
68
+ # @example Set error data
69
+ # error.data = { "field" => "invalid" }
70
+ #
33
71
  # @return [Hash, Array, String, Number, Boolean, nil]
34
72
  #
35
73
  attr_accessor :data
36
74
 
37
75
  # Creates a new JSON-RPC 2.0 Error object
38
76
  #
77
+ # @api public
78
+ #
79
+ # @example Create an error with code and message
80
+ # error = JSONRPC::Error.new("Invalid Request", code: -32600)
81
+ #
82
+ # @example Create an error with additional data
83
+ # error = JSONRPC::Error.new("Invalid params", code: -32602, data: { "field" => "missing" })
84
+ #
39
85
  # @param message [String] short description of the error
40
86
  # @param code [Integer] a number indicating the error type
41
87
  # @param data [Hash, Array, String, Number, Boolean, nil] additional error information
42
88
  # @param request_id [String, Integer, nil] the request identifier
89
+ #
43
90
  # @raise [ArgumentError] if code is not an Integer
91
+ #
44
92
  # @raise [ArgumentError] if message is not a String
45
93
  #
46
94
  def initialize(message, code:, data: nil, request_id: nil)
@@ -57,6 +105,11 @@ module JSONRPC
57
105
 
58
106
  # Converts the error to a JSON-compatible Hash
59
107
  #
108
+ # @api public
109
+ #
110
+ # @example Convert error to hash
111
+ # error.to_h # => { code: -32600, message: "Invalid Request" }
112
+ #
60
113
  # @return [Hash] the error as a JSON-compatible Hash
61
114
  #
62
115
  def to_h
@@ -65,10 +118,28 @@ module JSONRPC
65
118
  hash
66
119
  end
67
120
 
121
+ # Converts the error to JSON
122
+ #
123
+ # @api public
124
+ #
125
+ # @example Convert error to JSON
126
+ # error.to_json # => '{"code":-32600,"message":"Invalid Request"}'
127
+ #
128
+ # @return [String] the error as a JSON string
129
+ #
68
130
  def to_json(*)
69
131
  to_h.to_json(*)
70
132
  end
71
133
 
134
+ # Converts the error to a complete JSON-RPC response
135
+ #
136
+ # @api public
137
+ #
138
+ # @example Convert error to response
139
+ # error.to_response # => { jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request" }, id: nil }
140
+ #
141
+ # @return [Hash] a complete JSON-RPC response with this error
142
+ #
72
143
  def to_response
73
144
  Response.new(id: request_id, error: self).to_h
74
145
  end
@@ -77,18 +148,28 @@ module JSONRPC
77
148
 
78
149
  # Validates that the code is a valid Integer
79
150
  #
151
+ # @api private
152
+ #
80
153
  # @param code [Integer] the error code
154
+ #
81
155
  # @raise [ArgumentError] if code is not an Integer
82
156
  #
157
+ # @return [void]
158
+ #
83
159
  def validate_code(code)
84
160
  raise ArgumentError, 'Error code must be an Integer' unless code.is_a?(Integer)
85
161
  end
86
162
 
87
163
  # Validates that the message is a String
88
164
  #
165
+ # @api private
166
+ #
89
167
  # @param message [String] the error message
168
+ #
90
169
  # @raise [ArgumentError] if message is not a String
91
170
  #
171
+ # @return [void]
172
+ #
92
173
  def validate_message(message)
93
174
  raise ArgumentError, 'Error message must be a String' unless message.is_a?(String)
94
175
  end