diff --git a/lib/json_rpc_handler.rb b/lib/json_rpc_handler.rb index 977afa39..2c6c836a 100644 --- a/lib/json_rpc_handler.rb +++ b/lib/json_rpc_handler.rb @@ -73,13 +73,18 @@ def process_request(request, id_validation_pattern:, &method_finder) error = if !valid_version?(request[:jsonrpc]) "JSON-RPC version must be 2.0" - elsif !valid_id?(request[:id], id_validation_pattern) + elsif !valid_id?(id, id_validation_pattern) "Request ID must match validation pattern, or be an integer or null" elsif !valid_method_name?(request[:method]) 'Method name must be a string and not start with "rpc."' end - return error_response(id: :unknown_id, id_validation_pattern: id_validation_pattern, error: { + # Per JSON-RPC 2.0 (Response object, `id`), the error response must carry + # the same id as the request when the id could be detected; null is only + # for requests whose id could not be determined. `error_response` nils out + # ids that fail validation, and the `:unknown_id` sentinel keeps a response + # (with a null id) being emitted when the request carried no id at all. + return error_response(id: id.nil? ? :unknown_id : id, id_validation_pattern: id_validation_pattern, error: { code: ErrorCode::INVALID_REQUEST, message: "Invalid Request", data: error, diff --git a/test/json_rpc_handler_test.rb b/test/json_rpc_handler_test.rb index c1f08f11..83a18df5 100644 --- a/test/json_rpc_handler_test.rb +++ b/test/json_rpc_handler_test.rb @@ -43,6 +43,18 @@ message: "Invalid Request", data: "JSON-RPC version must be 2.0", } + assert_equal 1, @response[:id] + end + + it "returns an error preserving the request id when jsonrpc is missing" do + handle id: 4, method: "add", params: { a: 1, b: 2 } + + assert_rpc_error expected_error: { + code: -32600, + message: "Invalid Request", + data: "JSON-RPC version must be 2.0", + } + assert_equal 4, @response[:id] end # method @@ -58,6 +70,7 @@ message: "Invalid Request", data: 'Method name must be a string and not start with "rpc."', } + assert_equal 1, @response[:id] end it "returns an error when method begins with 'rpc.'" do @@ -68,6 +81,7 @@ message: "Invalid Request", data: 'Method name must be a string and not start with "rpc."', } + assert_equal 1, @response[:id] end # params @@ -315,6 +329,50 @@ assert_nil @response[:id] end + it "returns the same request id on an Invalid Request error when the id is detectable" do + handle jsonrpc: "1.0", id: 3, method: "ping", params: {} + + assert_rpc_error expected_error: { + code: -32600, + message: "Invalid Request", + data: "JSON-RPC version must be 2.0", + } + assert_equal 3, @response[:id] + end + + it "returns nil for id on an Invalid Request error when the request has no id" do + handle jsonrpc: "1.0", method: "ping", params: {} + + assert_rpc_error expected_error: { + code: -32600, + message: "Invalid Request", + data: "JSON-RPC version must be 2.0", + } + assert_nil @response[:id] + end + + it "returns nil for id on an Invalid Request error when the request id is explicitly null" do + handle jsonrpc: "1.0", id: nil, method: "ping", params: {} + + assert_rpc_error expected_error: { + code: -32600, + message: "Invalid Request", + data: "JSON-RPC version must be 2.0", + } + assert_nil @response[:id] + end + + it "returns nil for id on an Invalid Request error when the id fails validation" do + handle jsonrpc: "1.0", id: "", method: "ping", params: {} + + assert_rpc_error expected_error: { + code: -32600, + message: "Invalid Request", + data: "JSON-RPC version must be 2.0", + } + assert_nil @response[:id] + end + # 5.1 Error object # # When a rpc call encounters an error, the Response Object MUST contain the error member with a value that is a @@ -610,6 +668,20 @@ assert_nil @response end + + it "preserves the request id of an invalid entry within a batch" do + register("add") { |params| params[:a] + params[:b] } + + handle [ + { jsonrpc: "2.0", id: 100, method: "add", params: { a: 1, b: 2 } }, + { jsonrpc: "1.0", id: 200, method: "add", params: { a: 3, b: 4 } }, + ] + + assert @response.is_a?(Array) + assert_equal [100, 200], @response.map { |result| result[:id] } + assert_equal 3, @response.first[:result] + assert_equal(-32600, @response.last.dig(:error, :code)) + end end # 7 Examples @@ -756,10 +828,11 @@ assert_nil @response end - it "returns an error with the id set to nil when the request is invalid" do + it "returns an error preserving the request id when the request is invalid" do handle_json({ jsonrpc: "0.0", id: 1, method: "add", params: { a: 1, b: 2 } }.to_json) - assert_nil @response[:id] + assert_equal 1, @response[:id] + assert_equal(-32600, @response.dig(:error, :code)) end end