From dfcc2f2bd3facc8653fde92d11e0b714e1c9f5ec Mon Sep 17 00:00:00 2001 From: Niraj Nepal Date: Fri, 14 Nov 2025 16:22:21 +0000 Subject: [PATCH 1/2] Added error status details example --- examples/error_details/README.md | 29 +++++++++++++++ examples/error_details/client.js | 57 ++++++++++++++++++++++++++++++ examples/error_details/server.js | 60 ++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 examples/error_details/README.md create mode 100644 examples/error_details/client.js create mode 100644 examples/error_details/server.js diff --git a/examples/error_details/README.md b/examples/error_details/README.md new file mode 100644 index 000000000..4faaf1d6f --- /dev/null +++ b/examples/error_details/README.md @@ -0,0 +1,29 @@ +# Error Details + +This example demonstrates the use of status details in grpc errors. + +## Start the server + +Run the server, which sends a rich error if the name field is empty. + +``` +node server.js +``` + +## Run the client + +Run the client in another terminal. It will make two calls: first, a successful call with a valid name, and second, a failing call with an empty name. + +``` +node client.js +``` + +## EWxpected Output +``` +Greeting: Hello World + +--- Standard gRPC Error Received --- +Code: 3 +Status: INVALID_ARGUMENT +Message: 3 INVALID_ARGUMENT: Simple Error: The name field was empty. +``` diff --git a/examples/error_details/client.js b/examples/error_details/client.js new file mode 100644 index 000000000..2e6db6826 --- /dev/null +++ b/examples/error_details/client.js @@ -0,0 +1,57 @@ +/* + * + * Copyright 2025 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +var PROTO_PATH = __dirname + '/../protos/helloworld.proto'; + +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +var client = new hello_proto.Greeter('localhost:50051', grpc.credentials.createInsecure()); + +function main() { + client.sayHello({ name: 'World' }, function (err, response) { + if (err) { + console.error('Success call failed:', err); + return; + } + console.log('Greeting:', response.message); + + client.sayHello({ name: '' }, function (err, response) { + if (err) { + console.log('--- Standard gRPC Error Received ---'); + console.log(`Code: ${err.code}`); + console.log(`Status: ${grpc.status[err.code]}`); + console.log(`Message: ${err.message}`); + } else { + console.log('Failing call unexpectedly succeeded:', response.message); + } + }); + }); +} + +main(); \ No newline at end of file diff --git a/examples/error_details/server.js b/examples/error_details/server.js new file mode 100644 index 000000000..f22d13682 --- /dev/null +++ b/examples/error_details/server.js @@ -0,0 +1,60 @@ +/* + * + * Copyright 2025 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +var PROTO_PATH = __dirname + '/../protos/helloworld.proto'; + +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +/** + * Implements the SayHello RPC method. + */ +function sayHello(call, callback) { + if (call.request.name === '') { + callback({ + code: grpc.status.INVALID_ARGUMENT, + details: 'Simple Error: The name field was empty.' + }); + return; + } + + callback(null, { message: 'Hello ' + call.request.name }); +} + +/** + * Starts an RPC server. + */ +function main() { + var server = new grpc.Server(); + server.addService(hello_proto.Greeter.service, { sayHello: sayHello }); + server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { + console.log('Server running at http://0.0.0.0:50051'); + }); +} + +main(); \ No newline at end of file From e946cbfcf6205a397d05040bce80722fedee1a10 Mon Sep 17 00:00:00 2001 From: Niraj Nepal Date: Wed, 26 Nov 2025 20:41:33 +0000 Subject: [PATCH 2/2] Add status serialization to metadata and send with grpc status --- examples/error_details/README.md | 10 +++++++++- examples/error_details/client.js | 24 +++++++++++++++++++++++- examples/error_details/server.js | 31 ++++++++++++++++++++++++++++++- examples/protos/helloworld.proto | 28 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/examples/error_details/README.md b/examples/error_details/README.md index 4faaf1d6f..cfa314e95 100644 --- a/examples/error_details/README.md +++ b/examples/error_details/README.md @@ -18,7 +18,7 @@ Run the client in another terminal. It will make two calls: first, a successful node client.js ``` -## EWxpected Output +## Expected Output ``` Greeting: Hello World @@ -26,4 +26,12 @@ Greeting: Hello World Code: 3 Status: INVALID_ARGUMENT Message: 3 INVALID_ARGUMENT: Simple Error: The name field was empty. + +--- Rich Error Details--- +Violation: [ + { + "field": "name", + "description": "Name field is required" + } +] ``` diff --git a/examples/error_details/client.js b/examples/error_details/client.js index 2e6db6826..49d8fce20 100644 --- a/examples/error_details/client.js +++ b/examples/error_details/client.js @@ -31,6 +31,11 @@ var packageDefinition = protoLoader.loadSync( }); var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; +// Extract Deserializers from the service definition +var serviceDef = hello_proto.Greeter.service; +var decodeStatus = serviceDef['_DecodeStatus'].responseDeserialize; +var decodeBadRequest = serviceDef['_DecodeBadRequest'].responseDeserialize; + var client = new hello_proto.Greeter('localhost:50051', grpc.credentials.createInsecure()); function main() { @@ -43,10 +48,27 @@ function main() { client.sayHello({ name: '' }, function (err, response) { if (err) { - console.log('--- Standard gRPC Error Received ---'); + console.log('\n--- Standard gRPC Error Received ---'); console.log(`Code: ${err.code}`); console.log(`Status: ${grpc.status[err.code]}`); console.log(`Message: ${err.message}`); + + // Rich Error Decoding + const [statusBuffer] = err.metadata?.get('grpc-status-details-bin') || []; + if (statusBuffer) { + console.log('\n--- Rich Error Details---'); + var statusObj = decodeStatus(statusBuffer); + + if (statusObj.details) { + statusObj.details.forEach(detail => { + if (detail.type_url === 'type.googleapis.com/google.rpc.BadRequest') { + var badRequestObj = decodeBadRequest(detail.value); + console.log('Violation:', JSON.stringify(badRequestObj.field_violations, null, 2)); + } + }); + } + } + } else { console.log('Failing call unexpectedly succeeded:', response.message); } diff --git a/examples/error_details/server.js b/examples/error_details/server.js index f22d13682..d489c1e0c 100644 --- a/examples/error_details/server.js +++ b/examples/error_details/server.js @@ -31,14 +31,43 @@ var packageDefinition = protoLoader.loadSync( }); var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; +// Extract Serializers +var serviceDef = hello_proto.Greeter.service; +var encodeStatus = serviceDef['_EncodeStatus'].requestSerialize; +var encodeBadRequest = serviceDef['_EncodeBadRequest'].requestSerialize; + /** * Implements the SayHello RPC method. */ function sayHello(call, callback) { if (call.request.name === '') { + //Serialize the BadRequest detail + var badRequestBuffer = encodeBadRequest({ + field_violations: [ + { field: 'name', description: 'Name field is required' } + ] + }); + + //Create and Serialize the Status Message + var statusBuffer = encodeStatus({ + code: 3, + message: 'Request argument invalid', + details: [ + { + type_url: 'type.googleapis.com/google.rpc.BadRequest', + value: badRequestBuffer + } + ] + }); + + // Attach Metadata + var metadata = new grpc.Metadata(); + metadata.add('grpc-status-details-bin', statusBuffer); + callback({ code: grpc.status.INVALID_ARGUMENT, - details: 'Simple Error: The name field was empty.' + details: 'Simple Error: The name field was empty.', + metadata: metadata }); return; } diff --git a/examples/protos/helloworld.proto b/examples/protos/helloworld.proto index 7e50d0fc7..0062e111f 100644 --- a/examples/protos/helloworld.proto +++ b/examples/protos/helloworld.proto @@ -27,6 +27,12 @@ service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {} + + // Internal helpers to generate serializers for the server and deserializers for the client + rpc _EncodeStatus (Status) returns (Empty) {} + rpc _EncodeBadRequest (BadRequest) returns (Empty) {} + rpc _DecodeStatus (Empty) returns (Status) {} + rpc _DecodeBadRequest (Empty) returns (BadRequest) {} } // The request message containing the user's name. @@ -38,3 +44,25 @@ message HelloRequest { message HelloReply { string message = 1; } + +// Standard definitions for rich errors +message Status { + int32 code = 1; + string message = 2; + repeated Any details = 3; +} + +message Any { + string type_url = 1; + bytes value = 2; +} + +message BadRequest { + message FieldViolation { + string field = 1; + string description = 2; + } + repeated FieldViolation field_violations = 1; +} + +message Empty {} \ No newline at end of file