jsonrpc-middleware 0.1.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 +7 -0
- data/.claude/settings.local.json +9 -0
- data/.editorconfig +11 -0
- data/.overcommit.yml +31 -0
- data/.rspec +3 -0
- data/.rubocop.yml +74 -0
- data/.tool-versions +1 -0
- data/.yardstick.yml +22 -0
- data/CHANGELOG.md +37 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Guardfile +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +248 -0
- data/Rakefile +41 -0
- data/Steepfile +7 -0
- data/docs/JSON-RPC-2.0-Specification.md +278 -0
- data/examples/procedures.rb +55 -0
- data/examples/rack/Gemfile +8 -0
- data/examples/rack/Gemfile.lock +68 -0
- data/examples/rack/README.md +7 -0
- data/examples/rack/app.rb +48 -0
- data/examples/rack/config.ru +19 -0
- data/examples/rack-echo/Gemfile +8 -0
- data/examples/rack-echo/Gemfile.lock +68 -0
- data/examples/rack-echo/README.md +7 -0
- data/examples/rack-echo/app.rb +43 -0
- data/examples/rack-echo/config.ru +18 -0
- data/lib/jsonrpc/batch_request.rb +102 -0
- data/lib/jsonrpc/batch_response.rb +85 -0
- data/lib/jsonrpc/configuration.rb +85 -0
- data/lib/jsonrpc/error.rb +96 -0
- data/lib/jsonrpc/errors/internal_error.rb +27 -0
- data/lib/jsonrpc/errors/invalid_params_error.rb +27 -0
- data/lib/jsonrpc/errors/invalid_request_error.rb +31 -0
- data/lib/jsonrpc/errors/method_not_found_error.rb +31 -0
- data/lib/jsonrpc/errors/parse_error.rb +29 -0
- data/lib/jsonrpc/helpers.rb +83 -0
- data/lib/jsonrpc/middleware.rb +190 -0
- data/lib/jsonrpc/notification.rb +94 -0
- data/lib/jsonrpc/parser.rb +176 -0
- data/lib/jsonrpc/request.rb +112 -0
- data/lib/jsonrpc/response.rb +127 -0
- data/lib/jsonrpc/validator.rb +140 -0
- data/lib/jsonrpc/version.rb +5 -0
- data/lib/jsonrpc.rb +25 -0
- data/sig/jsonrpc/middleware.rbs +6 -0
- data/sig/jsonrpc/parser.rbs +7 -0
- data/sig/jsonrpc.rbs +164 -0
- metadata +120 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# A JSON-RPC 2.0 batch request object
|
5
|
+
#
|
6
|
+
# A batch request is an Array filled with Request objects to send several requests at once.
|
7
|
+
# The Server should respond with an Array containing the corresponding Response objects.
|
8
|
+
#
|
9
|
+
# @example Create a batch request with multiple requests
|
10
|
+
# batch = JSONRPC::BatchRequest.new([
|
11
|
+
# JSONRPC::Request.new(method: "sum", params: [1, 2, 4], id: "1"),
|
12
|
+
# JSONRPC::Notification.new(method: "notify_hello", params: [7]),
|
13
|
+
# JSONRPC::Request.new(method: "subtract", params: [42, 23], id: "2")
|
14
|
+
# ])
|
15
|
+
#
|
16
|
+
class BatchRequest
|
17
|
+
include Enumerable
|
18
|
+
|
19
|
+
# The collection of request objects in this batch (may include errors)
|
20
|
+
# @return [Array<JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error>]
|
21
|
+
#
|
22
|
+
attr_reader :requests
|
23
|
+
|
24
|
+
# Creates a new JSON-RPC 2.0 Batch Request object
|
25
|
+
#
|
26
|
+
# @param requests [Array<JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error>] an array of request objects
|
27
|
+
# or errors
|
28
|
+
# @raise [ArgumentError] if requests is not an Array
|
29
|
+
# @raise [ArgumentError] if requests is empty
|
30
|
+
# @raise [ArgumentError] if any request is not a valid Request, Notification, or Error
|
31
|
+
#
|
32
|
+
def initialize(requests)
|
33
|
+
validate_requests(requests)
|
34
|
+
@requests = requests
|
35
|
+
end
|
36
|
+
|
37
|
+
# Converts the batch request to a JSON-compatible Array
|
38
|
+
#
|
39
|
+
# @return [Array<Hash>] the batch request as a JSON-compatible Array
|
40
|
+
#
|
41
|
+
def to_h
|
42
|
+
requests.map { |item| item.respond_to?(:to_h) ? item.to_h : item }
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_json(*)
|
46
|
+
to_h.to_json(*)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Implements the Enumerable contract by yielding each request in the batch
|
50
|
+
#
|
51
|
+
# @yield [request] Yields each request in the batch to the block
|
52
|
+
# @yieldparam request [JSONRPC::Request, JSONRPC::Notification, JSONRPC::Error] a request in the batch
|
53
|
+
# @return [Enumerator] if no block is given
|
54
|
+
# @return [BatchRequest] self if a block is given
|
55
|
+
#
|
56
|
+
def each(&)
|
57
|
+
return to_enum(:each) unless block_given?
|
58
|
+
|
59
|
+
requests.each(&)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the number of requests in the batch
|
64
|
+
#
|
65
|
+
# @return [Integer] the number of requests in the batch
|
66
|
+
#
|
67
|
+
def size
|
68
|
+
requests.size
|
69
|
+
end
|
70
|
+
|
71
|
+
# Alias for size for Array-like interface
|
72
|
+
alias length size
|
73
|
+
|
74
|
+
# Returns true if the batch contains no requests
|
75
|
+
#
|
76
|
+
# @return [Boolean] true if the batch is empty, false otherwise
|
77
|
+
#
|
78
|
+
def empty?
|
79
|
+
requests.empty?
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Validates that the requests is a valid array of Request/Notification/Error objects
|
85
|
+
#
|
86
|
+
# @param requests [Array] the array of requests
|
87
|
+
# @raise [ArgumentError] if requests is not an Array
|
88
|
+
# @raise [ArgumentError] if requests is empty
|
89
|
+
# @raise [ArgumentError] if any request is not a valid Request, Notification, or Error
|
90
|
+
#
|
91
|
+
def validate_requests(requests)
|
92
|
+
raise ArgumentError, 'Requests must be an Array' unless requests.is_a?(Array)
|
93
|
+
raise ArgumentError, 'Batch request cannot be empty' if requests.empty?
|
94
|
+
|
95
|
+
requests.each_with_index do |request, index|
|
96
|
+
unless request.is_a?(Request) || request.is_a?(Notification) || request.is_a?(Error)
|
97
|
+
raise ArgumentError, "Request at index #{index} is not a valid Request, Notification, or Error"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# A JSON-RPC 2.0 Batch Response object
|
5
|
+
#
|
6
|
+
# A Batch Response is an Array containing Response objects, corresponding to
|
7
|
+
# a Batch Request. The Server should respond with one Response for each Request
|
8
|
+
# (except for Notifications which don't receive responses).
|
9
|
+
#
|
10
|
+
# @example Create a batch response
|
11
|
+
# batch = JSONRPC::BatchResponse.new([
|
12
|
+
# JSONRPC::Response.new(result: 7, id: "1"),
|
13
|
+
# JSONRPC::Response.new(result: 19, id: "2"),
|
14
|
+
# JSONRPC::Response.new(error: JSONRPC::Error.new(code: -32600, message: "Invalid Request"), id: nil)
|
15
|
+
# ])
|
16
|
+
#
|
17
|
+
class BatchResponse
|
18
|
+
include Enumerable
|
19
|
+
|
20
|
+
# The collection of response objects in this batch
|
21
|
+
# @return [Array<JSONRPC::Response>]
|
22
|
+
#
|
23
|
+
attr_reader :responses
|
24
|
+
|
25
|
+
# Creates a new JSON-RPC 2.0 Batch Response object
|
26
|
+
#
|
27
|
+
# @param responses [Array<JSONRPC::Response>] an array of response objects
|
28
|
+
# @raise [ArgumentError] if responses is not an Array
|
29
|
+
# @raise [ArgumentError] if responses is empty
|
30
|
+
# @raise [ArgumentError] if any response is not a valid Response
|
31
|
+
#
|
32
|
+
def initialize(responses)
|
33
|
+
validate_responses(responses)
|
34
|
+
@responses = responses
|
35
|
+
end
|
36
|
+
|
37
|
+
# Converts the batch response to a JSON-compatible Array
|
38
|
+
#
|
39
|
+
# @return [Array<Hash>] the batch response as a JSON-compatible Array
|
40
|
+
#
|
41
|
+
def to_h
|
42
|
+
responses.map(&:to_h)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_json(*)
|
46
|
+
to_h.to_json(*)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Implements the Enumerable contract by yielding each response in the batch
|
50
|
+
#
|
51
|
+
# @yield [response] Yields each response in the batch to the block
|
52
|
+
# @yieldparam response [JSONRPC::Response] a response in the batch
|
53
|
+
# @return [Enumerator] if no block is given
|
54
|
+
# @return [BatchResponse] self if a block is given
|
55
|
+
#
|
56
|
+
def each(&)
|
57
|
+
return to_enum(:each) unless block_given?
|
58
|
+
|
59
|
+
responses.each(&)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_response
|
64
|
+
responses.map(&:to_response)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Validates that the responses is a valid array of Response objects
|
70
|
+
#
|
71
|
+
# @param responses [Array] the array of responses
|
72
|
+
# @raise [ArgumentError] if responses is not an Array
|
73
|
+
# @raise [ArgumentError] if responses is empty
|
74
|
+
# @raise [ArgumentError] if any response is not a valid Response
|
75
|
+
#
|
76
|
+
def validate_responses(responses)
|
77
|
+
raise ArgumentError, 'Responses must be an Array' unless responses.is_a?(Array)
|
78
|
+
raise ArgumentError, 'Batch response cannot be empty' if responses.empty?
|
79
|
+
|
80
|
+
responses.each_with_index do |response, index|
|
81
|
+
raise ArgumentError, "Response at index #{index} is not a valid Response" unless response.is_a?(Response)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# Configuration class for JSON-RPC procedure management and validation.
|
5
|
+
# This class provides functionality to register, retrieve, and validate JSON-RPC procedures.
|
6
|
+
#
|
7
|
+
# @example Registering a procedure
|
8
|
+
# JSONRPC::Configuration.instance.procedure('sum') do
|
9
|
+
# params do
|
10
|
+
# required(:numbers).value(:array, min_size?: 1)
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
class Configuration
|
15
|
+
# Represents a registered JSON-RPC procedure with its validation contract and configuration.
|
16
|
+
#
|
17
|
+
# @!attribute [r] allow_positional_arguments
|
18
|
+
# @return [Boolean] whether the procedure accepts positional arguments
|
19
|
+
# @!attribute [r] contract
|
20
|
+
# @return [Dry::Validation::Contract] the validation contract for procedure parameters
|
21
|
+
# @!attribute [r] parameter_name
|
22
|
+
# @return [Symbol, nil] the name of the first parameter in the contract schema
|
23
|
+
Procedure = Data.define(:allow_positional_arguments, :contract, :parameter_name)
|
24
|
+
|
25
|
+
# @!attribute [r] validate_procedure_signatures
|
26
|
+
# @return [Boolean] whether procedure signatures are validated
|
27
|
+
attr_reader :validate_procedure_signatures
|
28
|
+
|
29
|
+
# Initializes a new Configuration instance.
|
30
|
+
#
|
31
|
+
# @return [Configuration] a new configuration instance
|
32
|
+
def initialize
|
33
|
+
@procedures = {}
|
34
|
+
@validate_procedure_signatures = true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the singleton instance of the Configuration class.
|
38
|
+
#
|
39
|
+
# @return [Configuration] the singleton instance
|
40
|
+
def self.instance
|
41
|
+
@instance ||= new
|
42
|
+
end
|
43
|
+
|
44
|
+
# Registers a new procedure with the given method name and validation contract.
|
45
|
+
#
|
46
|
+
# @param method_name [String, Symbol] the name of the procedure
|
47
|
+
# @param allow_positional_arguments [Boolean] whether the procedure accepts positional arguments
|
48
|
+
# @yield A block that defines the validation contract using Dry::Validation DSL
|
49
|
+
# @return [Procedure] the registered procedure
|
50
|
+
def procedure(method_name, allow_positional_arguments: false, &)
|
51
|
+
contract_class = Class.new(Dry::Validation::Contract, &)
|
52
|
+
contract_class.class_eval { import_predicates_as_macros }
|
53
|
+
contract = contract_class.new
|
54
|
+
|
55
|
+
@procedures[method_name.to_s] = Procedure.new(
|
56
|
+
allow_positional_arguments:,
|
57
|
+
contract:,
|
58
|
+
parameter_name: contract.schema.key_map.keys.first&.name
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Retrieves a procedure by its method name.
|
63
|
+
#
|
64
|
+
# @param method_name [String, Symbol] the name of the procedure to retrieve
|
65
|
+
# @return [Procedure, nil] the procedure if found, nil otherwise
|
66
|
+
def get_procedure(method_name)
|
67
|
+
@procedures[method_name.to_s]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Checks if a procedure with the given method name exists.
|
71
|
+
#
|
72
|
+
# @param method_name [String, Symbol] the name of the procedure to check
|
73
|
+
# @return [Boolean] true if the procedure exists, false otherwise
|
74
|
+
def procedure?(method_name)
|
75
|
+
@procedures.key?(method_name.to_s)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Clears all registered procedures.
|
79
|
+
#
|
80
|
+
# @return [void]
|
81
|
+
def reset!
|
82
|
+
@procedures.clear
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# A JSON-RPC 2.0 Error object
|
5
|
+
#
|
6
|
+
# 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.
|
8
|
+
#
|
9
|
+
# @example Create an error
|
10
|
+
# error = JSONRPC::Error.new(
|
11
|
+
# code: -32600,
|
12
|
+
# message: "Invalid Request",
|
13
|
+
# data: { detail: "Additional information about the error" }
|
14
|
+
# )
|
15
|
+
#
|
16
|
+
class Error < StandardError
|
17
|
+
# The request identifier (optional for notifications)
|
18
|
+
# @return [String, Integer, nil]
|
19
|
+
#
|
20
|
+
attr_accessor :request_id
|
21
|
+
|
22
|
+
# Error code indicating the error type
|
23
|
+
# @return [Integer]
|
24
|
+
#
|
25
|
+
attr_accessor :code
|
26
|
+
|
27
|
+
# Short description of the error
|
28
|
+
# @return [String]
|
29
|
+
#
|
30
|
+
attr_accessor :message
|
31
|
+
|
32
|
+
# Additional information about the error (optional)
|
33
|
+
# @return [Hash, Array, String, Number, Boolean, nil]
|
34
|
+
#
|
35
|
+
attr_accessor :data
|
36
|
+
|
37
|
+
# Creates a new JSON-RPC 2.0 Error object
|
38
|
+
#
|
39
|
+
# @param message [String] short description of the error
|
40
|
+
# @param code [Integer] a number indicating the error type
|
41
|
+
# @param data [Hash, Array, String, Number, Boolean, nil] additional error information
|
42
|
+
# @param request_id [String, Integer, nil] the request identifier
|
43
|
+
# @raise [ArgumentError] if code is not an Integer
|
44
|
+
# @raise [ArgumentError] if message is not a String
|
45
|
+
#
|
46
|
+
def initialize(message, code:, data: nil, request_id: nil)
|
47
|
+
super(message)
|
48
|
+
|
49
|
+
validate_code(code)
|
50
|
+
validate_message(message)
|
51
|
+
|
52
|
+
@code = code
|
53
|
+
@message = message
|
54
|
+
@data = data
|
55
|
+
@request_id = request_id
|
56
|
+
end
|
57
|
+
|
58
|
+
# Converts the error to a JSON-compatible Hash
|
59
|
+
#
|
60
|
+
# @return [Hash] the error as a JSON-compatible Hash
|
61
|
+
#
|
62
|
+
def to_h
|
63
|
+
hash = { code:, message: }
|
64
|
+
hash[:data] = data unless data.nil?
|
65
|
+
hash
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_json(*)
|
69
|
+
to_h.to_json(*)
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_response
|
73
|
+
Response.new(id: request_id, error: self).to_h
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Validates that the code is a valid Integer
|
79
|
+
#
|
80
|
+
# @param code [Integer] the error code
|
81
|
+
# @raise [ArgumentError] if code is not an Integer
|
82
|
+
#
|
83
|
+
def validate_code(code)
|
84
|
+
raise ArgumentError, 'Error code must be an Integer' unless code.is_a?(Integer)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Validates that the message is a String
|
88
|
+
#
|
89
|
+
# @param message [String] the error message
|
90
|
+
# @raise [ArgumentError] if message is not a String
|
91
|
+
#
|
92
|
+
def validate_message(message)
|
93
|
+
raise ArgumentError, 'Error message must be a String' unless message.is_a?(String)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# JSON-RPC 2.0 Internal Error (-32603)
|
5
|
+
#
|
6
|
+
# Raised when there was an internal JSON-RPC error.
|
7
|
+
#
|
8
|
+
# @example Create an internal error
|
9
|
+
# error = JSONRPC::Errors::InternalError.new(data: { details: "Unexpected server error" })
|
10
|
+
#
|
11
|
+
class InternalError < Error
|
12
|
+
# Creates a new Internal Error with code -32603
|
13
|
+
#
|
14
|
+
# @param message [String] short description of the error
|
15
|
+
# @param data [Hash, Array, String, Number, Boolean, nil] additional error information
|
16
|
+
# @param request_id [String, Integer, nil] the request identifier
|
17
|
+
#
|
18
|
+
def initialize(message = 'Internal JSON-RPC error.', data: nil, request_id: nil)
|
19
|
+
super(
|
20
|
+
message,
|
21
|
+
code: -32_603,
|
22
|
+
data:,
|
23
|
+
request_id:
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# JSON-RPC 2.0 Invalid Params Error (-32602)
|
5
|
+
#
|
6
|
+
# Raised when invalid method parameter(s) were provided.
|
7
|
+
#
|
8
|
+
# @example Create an invalid params error
|
9
|
+
# error = JSONRPC::InvalidParamsError.new(data: { details: "Expected array of integers" })
|
10
|
+
#
|
11
|
+
class InvalidParamsError < Error
|
12
|
+
# Creates a new Invalid Params Error with code -32602
|
13
|
+
#
|
14
|
+
# @param message [String] short description of the error
|
15
|
+
# @param data [Hash, Array, String, Number, Boolean, nil] additional error information
|
16
|
+
# @param request_id [String, Integer, nil] the request identifier
|
17
|
+
#
|
18
|
+
def initialize(message = 'Invalid method parameter(s).', data: nil, request_id: nil)
|
19
|
+
super(
|
20
|
+
message,
|
21
|
+
code: -32_602,
|
22
|
+
data:,
|
23
|
+
request_id:
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# JSON-RPC 2.0 Invalid Request Error (-32600)
|
5
|
+
#
|
6
|
+
# Raised when the JSON sent is not a valid Request object.
|
7
|
+
#
|
8
|
+
# @example Create an invalid request error
|
9
|
+
# error = JSONRPC::InvalidRequestError.new(data: { details: "Method must be a string" })
|
10
|
+
#
|
11
|
+
class InvalidRequestError < Error
|
12
|
+
# Creates a new Invalid Request Error with code -32600
|
13
|
+
#
|
14
|
+
# @param message [String] short description of the error
|
15
|
+
# @param data [Hash, Array, String, Number, Boolean, nil] additional error information
|
16
|
+
# @param request_id [String, Integer, nil] the request identifier
|
17
|
+
#
|
18
|
+
def initialize(
|
19
|
+
message = 'The JSON payload was valid JSON, but not a valid JSON-RPC Request object.',
|
20
|
+
data: nil,
|
21
|
+
request_id: nil
|
22
|
+
)
|
23
|
+
super(
|
24
|
+
message,
|
25
|
+
code: -32_600,
|
26
|
+
data:,
|
27
|
+
request_id:
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# JSON-RPC 2.0 Method Not Found Error (-32601)
|
5
|
+
#
|
6
|
+
# Raised when the method does not exist / is not available.
|
7
|
+
#
|
8
|
+
# @example Create a method not found error
|
9
|
+
# error = JSONRPC::MethodNotFound.new(data: { requested_method: "unknown_method" })
|
10
|
+
#
|
11
|
+
class MethodNotFoundError < Error
|
12
|
+
# Creates a new Method Not Found Error with code -32601
|
13
|
+
#
|
14
|
+
# @param message [String] short description of the error
|
15
|
+
# @param data [Hash, Array, String, Number, Boolean, nil] additional error information
|
16
|
+
# @param request_id [String, Integer, nil] the request identifier
|
17
|
+
#
|
18
|
+
def initialize(
|
19
|
+
message = 'The requested RPC method does not exist or is not supported.',
|
20
|
+
data: nil,
|
21
|
+
request_id: nil
|
22
|
+
)
|
23
|
+
super(
|
24
|
+
message,
|
25
|
+
code: -32_601,
|
26
|
+
data:,
|
27
|
+
request_id:
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# JSON-RPC 2.0 Parse Error (-32700)
|
5
|
+
#
|
6
|
+
# Raised when invalid JSON was received by the server.
|
7
|
+
# An error occurred on the server while parsing the JSON text.
|
8
|
+
#
|
9
|
+
# @example Create a parse error
|
10
|
+
# error = JSONRPC::ParseError.new(data: { details: "Unexpected end of input" })
|
11
|
+
#
|
12
|
+
class ParseError < Error
|
13
|
+
# Creates a new Parse Error with code -32700
|
14
|
+
#
|
15
|
+
# @param message [String] short description of the error
|
16
|
+
# @param data [Hash, Array, String, Number, Boolean, nil] additional error information
|
17
|
+
#
|
18
|
+
def initialize(
|
19
|
+
message = 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.',
|
20
|
+
data: nil
|
21
|
+
)
|
22
|
+
super(
|
23
|
+
message,
|
24
|
+
code: -32_700,
|
25
|
+
data: data
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
# Framework-agnostic helpers for JSON-RPC
|
5
|
+
module Helpers
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Class methods for registering JSON-RPC procedure handlers
|
11
|
+
module ClassMethods
|
12
|
+
def jsonrpc_method(method_name, &)
|
13
|
+
Configuration.instance.procedure(method_name, &)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def jsonrpc_batch? = @env.key?('jsonrpc.batch')
|
18
|
+
def jsonrpc_notification? = @env.key?('jsonrpc.notification')
|
19
|
+
def jsonrpc_request? = @env.key?('jsonrpc.request')
|
20
|
+
|
21
|
+
# Get the current JSON-RPC request object
|
22
|
+
def jsonrpc_batch = @env['jsonrpc.batch']
|
23
|
+
def jsonrpc_request = @env['jsonrpc.request']
|
24
|
+
def jsonrpc_notification = @env['jsonrpc.notification']
|
25
|
+
|
26
|
+
# Create a JSON-RPC response
|
27
|
+
def jsonrpc_response(result)
|
28
|
+
[200, { 'content-type' => 'application/json' }, [Response.new(id: jsonrpc_request.id, result: result).to_json]]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a JSON-RPC response
|
32
|
+
def jsonrpc_batch_response(responses)
|
33
|
+
# If batch contained only notifications, responses will be empty or contain only nils
|
34
|
+
return [204, {}, []] if responses.compact.empty?
|
35
|
+
|
36
|
+
[200, { 'content-type' => 'application/json' }, [responses.to_json]]
|
37
|
+
end
|
38
|
+
|
39
|
+
def jsonrpc_notification_response
|
40
|
+
[204, {}, []]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create a JSON-RPC error response
|
44
|
+
def jsonrpc_error(error)
|
45
|
+
Response.new(id: jsonrpc_request.id, error: error).to_json
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get the current JSON-RPC request params object
|
49
|
+
def jsonrpc_params
|
50
|
+
jsonrpc_request.params
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create a Parse error (-32700)
|
54
|
+
# Used when invalid JSON was received by the server
|
55
|
+
def jsonrpc_parse_error(data: nil)
|
56
|
+
jsonrpc_error(ParseError.new(data: data))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create an Invalid Request error (-32600)
|
60
|
+
# Used when the JSON sent is not a valid Request object
|
61
|
+
def jsonrpc_invalid_request_error(data: nil)
|
62
|
+
jsonrpc_error(InvalidRequestError.new(data: data))
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create a Method not found error (-32601)
|
66
|
+
# Used when the method does not exist / is not available
|
67
|
+
def jsonrpc_method_not_found_error(data: nil)
|
68
|
+
jsonrpc_error(MethodNotFoundError.new(data: data))
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create an Invalid params error (-32602)
|
72
|
+
# Used when invalid method parameter(s) were received
|
73
|
+
def jsonrpc_invalid_params_error(data: nil)
|
74
|
+
jsonrpc_error(InvalidParamsError.new(data: data))
|
75
|
+
end
|
76
|
+
|
77
|
+
# Create an Internal error (-32603)
|
78
|
+
# Used for implementation-defined server errors
|
79
|
+
def jsonrpc_internal_error(data: nil)
|
80
|
+
jsonrpc_error(InternalError.new(data: data))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|