Skip to content

Commit 61208ea

Browse files
authored
Merge pull request #3027 from zarinn3pal/error_details
Added error status details example
2 parents 164d14f + cbe8f47 commit 61208ea

File tree

4 files changed

+220
-0
lines changed

4 files changed

+220
-0
lines changed

examples/error_details/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Error Details
2+
3+
This example demonstrates how to send and receive rich error details in gRPC using the standard `google.rpc` error model.
4+
5+
## Overview
6+
7+
The example uses the `@q42philips/node-grpc-error-details` package to:
8+
- **Server**: Serialize error details (like `BadRequest`) into the `grpc-status-details-bin` metadata
9+
- **Client**: Deserialize error details from the metadata
10+
11+
This follows the official gRPC error model specification.
12+
13+
## Start the server
14+
15+
Run the server, which sends a rich error if the name field is empty:
16+
17+
```bash
18+
node server.js
19+
```
20+
21+
## Run the client
22+
23+
In another terminal, run the client which makes two calls:
24+
1. A successful call with a valid name
25+
2. A failing call with an empty name that receives rich error details
26+
27+
```bash
28+
node client.js
29+
```
30+
31+
## Expected Output
32+
33+
The client makes two calls and displays both results:
34+
35+
**Successful call:**
36+
```
37+
Greeting: Hello World
38+
```
39+
40+
**Failed call with standard error and rich error details:**
41+
```
42+
--- Standard gRPC Error Received ---
43+
Code: 3
44+
Status: INVALID_ARGUMENT
45+
Message: Simple Error: The name field was empty.
46+
47+
--- Rich Error Details---
48+
Violation: [
49+
{
50+
"field": "name",
51+
"description": "Name field is required"
52+
}
53+
]
54+
```

examples/error_details/client.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
*
3+
* Copyright 2025 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
var PROTO_PATH = __dirname + '/../protos/helloworld.proto';
20+
21+
var grpc = require('@grpc/grpc-js');
22+
var protoLoader = require('@grpc/proto-loader');
23+
var packageDefinition = protoLoader.loadSync(
24+
PROTO_PATH,
25+
{
26+
keepCase: true,
27+
longs: String,
28+
enums: String,
29+
defaults: true,
30+
oneofs: true
31+
});
32+
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
33+
34+
var {deserializeGoogleGrpcStatusDetails, BadRequest} =
35+
require('@q42philips/node-grpc-error-details');
36+
37+
var client = new hello_proto.Greeter('localhost:50051', grpc.credentials.createInsecure());
38+
39+
function main() {
40+
// Successful call
41+
client.sayHello({name: 'World'}, function (err, response) {
42+
if (err) {
43+
console.error('Successful call failed:', err.message);
44+
return;
45+
}
46+
console.log('Greeting:', response.message);
47+
48+
// Failing call with empty name
49+
client.sayHello({name: ''}, function (err, response) {
50+
if (err) {
51+
console.log('\n--- Standard gRPC Error Received ---');
52+
console.log('Code:', err.code);
53+
console.log('Status:', grpc.status[err.code] || 'UNKNOWN');
54+
console.log('Message:', err.details);
55+
56+
// Deserialize rich error details
57+
var grpcErrorDetails = deserializeGoogleGrpcStatusDetails(err);
58+
if (grpcErrorDetails) {
59+
console.log('\n--- Rich Error Details---');
60+
grpcErrorDetails.details.forEach(function(detail) {
61+
if (detail instanceof BadRequest) {
62+
var violations = detail.getFieldViolationsList().map(function(violation) {
63+
return {
64+
field: violation.getField(),
65+
description: violation.getDescription()
66+
};
67+
});
68+
console.log('Violation:', JSON.stringify(violations, null, 2));
69+
}
70+
});
71+
}
72+
} else {
73+
console.log('Expected error but got success:', response.message);
74+
}
75+
});
76+
});
77+
}
78+
79+
main();

examples/error_details/server.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
*
3+
* Copyright 2025 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
var PROTO_PATH = __dirname + '/../protos/helloworld.proto';
20+
21+
var grpc = require('@grpc/grpc-js');
22+
var protoLoader = require('@grpc/proto-loader');
23+
var packageDefinition = protoLoader.loadSync(
24+
PROTO_PATH,
25+
{
26+
keepCase: true,
27+
longs: String,
28+
enums: String,
29+
defaults: true,
30+
oneofs: true
31+
});
32+
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
33+
34+
var {Status, BadRequest} = require('@q42philips/node-grpc-error-details');
35+
var {Any} = require('google-protobuf/google/protobuf/any_pb');
36+
37+
/**
38+
* Implements the SayHello RPC method.
39+
*/
40+
function sayHello(call, callback) {
41+
if (call.request.name === '') {
42+
// Create BadRequest detail
43+
var fieldViolation = new BadRequest.FieldViolation();
44+
fieldViolation.setField('name');
45+
fieldViolation.setDescription('Name field is required');
46+
47+
var badRequest = new BadRequest();
48+
badRequest.setFieldViolationsList([fieldViolation]);
49+
50+
// Pack into Any
51+
var anyMessage = new Any();
52+
anyMessage.pack(badRequest.serializeBinary(), 'google.rpc.BadRequest');
53+
54+
// Create Status
55+
var status = new Status();
56+
status.setCode(3); // INVALID_ARGUMENT
57+
status.setMessage('Request argument invalid');
58+
status.setDetailsList([anyMessage]);
59+
60+
// Attach as metadata
61+
var metadata = new grpc.Metadata();
62+
metadata.add('grpc-status-details-bin', Buffer.from(status.serializeBinary()));
63+
64+
callback({
65+
code: grpc.status.INVALID_ARGUMENT,
66+
details: 'Simple Error: The name field was empty.',
67+
metadata: metadata
68+
});
69+
return;
70+
}
71+
72+
callback(null, {message: 'Hello ' + call.request.name});
73+
}
74+
75+
/**
76+
* Starts an RPC server.
77+
*/
78+
function main() {
79+
var server = new grpc.Server();
80+
server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
81+
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
82+
console.log('Server running at http://0.0.0.0:50051');
83+
});
84+
}
85+
86+
main();

examples/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"@grpc/grpc-js": "^1.10.2",
99
"@grpc/grpc-js-xds": "^1.10.0",
1010
"@grpc/reflection": "^1.0.0",
11+
"@q42philips/node-grpc-error-details": "^2.1.0",
1112
"lodash": "^4.6.1",
1213
"minimist": "^1.2.0"
1314
}

0 commit comments

Comments
 (0)