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.
- checksums.yaml +4 -4
- data/.aiexclude +4 -0
- data/.claude/commands/document.md +105 -0
- data/.claude/docs/yard.md +602 -0
- data/.claude/settings.local.json +2 -1
- data/.env.example +5 -0
- data/CHANGELOG.md +22 -2
- data/CLAUDE.md +114 -0
- data/README.md +42 -102
- data/Rakefile +59 -1
- data/examples/README.md +37 -0
- data/examples/procedures.rb +6 -1
- data/examples/rack/README.md +26 -1
- data/examples/rack/app.rb +1 -1
- data/examples/rack-echo/README.md +23 -1
- data/examples/rack-single-file/README.md +37 -0
- data/examples/rack-single-file/config.ru +54 -0
- data/examples/rails/.gitignore +21 -0
- data/examples/rails/.ruby-version +1 -0
- data/examples/rails/Gemfile +15 -0
- data/examples/rails/Gemfile.lock +261 -0
- data/examples/rails/README.md +32 -0
- data/examples/rails/Rakefile +8 -0
- data/examples/rails/app/controllers/application_controller.rb +4 -0
- data/examples/rails/app/controllers/jsonrpc_controller.rb +44 -0
- data/examples/rails/bin/dev +4 -0
- data/examples/rails/bin/rails +6 -0
- data/examples/rails/bin/rake +6 -0
- data/examples/rails/bin/setup +28 -0
- data/examples/rails/config/application.rb +47 -0
- data/examples/rails/config/boot.rb +5 -0
- data/examples/rails/config/credentials.yml.enc +1 -0
- data/examples/rails/config/environment.rb +7 -0
- data/examples/rails/config/environments/development.rb +42 -0
- data/examples/rails/config/environments/production.rb +60 -0
- data/examples/rails/config/environments/test.rb +44 -0
- data/examples/rails/config/initializers/cors.rb +18 -0
- data/examples/rails/config/initializers/filter_parameter_logging.rb +10 -0
- data/examples/rails/config/initializers/inflections.rb +18 -0
- data/examples/rails/config/initializers/jsonrpc.rb +62 -0
- data/examples/rails/config/locales/en.yml +31 -0
- data/examples/rails/config/puma.rb +40 -0
- data/examples/rails/config/routes.rb +14 -0
- data/examples/rails/config.ru +8 -0
- data/examples/rails/public/robots.txt +1 -0
- data/examples/rails-single-file/config.ru +71 -0
- data/examples/sinatra-classic/Gemfile +9 -0
- data/examples/sinatra-classic/Gemfile.lock +95 -0
- data/examples/sinatra-classic/README.md +32 -0
- data/examples/sinatra-classic/app.rb +54 -0
- data/examples/sinatra-classic/config.ru +6 -0
- data/examples/sinatra-modular/Gemfile +9 -0
- data/examples/sinatra-modular/Gemfile.lock +95 -0
- data/examples/sinatra-modular/README.md +32 -0
- data/examples/sinatra-modular/app.rb +57 -0
- data/examples/sinatra-modular/config.ru +6 -0
- data/lib/jsonrpc/batch_request.rb +67 -2
- data/lib/jsonrpc/batch_response.rb +56 -0
- data/lib/jsonrpc/configuration.rb +156 -14
- data/lib/jsonrpc/error.rb +83 -2
- data/lib/jsonrpc/errors/internal_error.rb +14 -2
- data/lib/jsonrpc/errors/invalid_params_error.rb +13 -1
- data/lib/jsonrpc/errors/invalid_request_error.rb +8 -0
- data/lib/jsonrpc/errors/method_not_found_error.rb +8 -0
- data/lib/jsonrpc/errors/parse_error.rb +8 -0
- data/lib/jsonrpc/helpers.rb +212 -21
- data/lib/jsonrpc/middleware.rb +211 -5
- data/lib/jsonrpc/notification.rb +58 -0
- data/lib/jsonrpc/parser.rb +30 -0
- data/lib/jsonrpc/railtie.rb +57 -0
- data/lib/jsonrpc/request.rb +68 -1
- data/lib/jsonrpc/response.rb +76 -0
- data/lib/jsonrpc/validator.rb +67 -6
- data/lib/jsonrpc/version.rb +11 -1
- data/lib/jsonrpc.rb +53 -1
- 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
|
@@ -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
|
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
|
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
|
-
#
|
27
|
+
# @example
|
28
|
+
# procedure.allow_positional_arguments # => true
|
18
29
|
# @return [Boolean] whether the procedure accepts positional arguments
|
19
|
-
#
|
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
|
-
#
|
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
|
-
#
|
26
|
-
#
|
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
|
-
|
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
|
-
@
|
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
|