Skip to content

Commit 10f5e5a

Browse files
authored
examples: Error details example (#9997)
* examples: Detail Error example (google.rpc.Status)
1 parent 9ea7506 commit 10f5e5a

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed

examples/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ task sharingClient(type: CreateStartScripts) {
252252
classpath = startScripts.classpath
253253
}
254254

255+
task errorDetails(type: CreateStartScripts) {
256+
mainClass = 'io.grpc.examples.errordetails.ErrorDetailsExample'
257+
applicationName = 'error-details'
258+
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
259+
classpath = startScripts.classpath
260+
}
261+
255262
applicationDistribution.into('bin') {
256263
from(routeGuideServer)
257264
from(routeGuideClient)
@@ -280,5 +287,6 @@ applicationDistribution.into('bin') {
280287
from(cancellationServer)
281288
from(multiplexingServer)
282289
from(sharingClient)
290+
from(errorDetails)
283291
fileMode = 0755
284292
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright 2023 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.examples.errordetails;
18+
19+
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
20+
21+
import com.google.common.base.Verify;
22+
import com.google.common.util.concurrent.FutureCallback;
23+
import com.google.common.util.concurrent.Futures;
24+
import com.google.common.util.concurrent.ListenableFuture;
25+
import com.google.common.util.concurrent.Uninterruptibles;
26+
import com.google.protobuf.Any;
27+
import com.google.protobuf.InvalidProtocolBufferException;
28+
import com.google.rpc.Code;
29+
import com.google.rpc.DebugInfo;
30+
import com.google.rpc.Status;
31+
import io.grpc.Channel;
32+
import io.grpc.Grpc;
33+
import io.grpc.InsecureChannelCredentials;
34+
import io.grpc.InsecureServerCredentials;
35+
import io.grpc.ManagedChannel;
36+
import io.grpc.Server;
37+
import io.grpc.examples.helloworld.GreeterGrpc;
38+
import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
39+
import io.grpc.examples.helloworld.GreeterGrpc.GreeterFutureStub;
40+
import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub;
41+
import io.grpc.examples.helloworld.HelloReply;
42+
import io.grpc.examples.helloworld.HelloRequest;
43+
import io.grpc.protobuf.StatusProto;
44+
import io.grpc.stub.StreamObserver;
45+
import java.util.concurrent.CountDownLatch;
46+
import java.util.concurrent.ExecutionException;
47+
import java.util.concurrent.TimeUnit;
48+
import javax.annotation.Nullable;
49+
50+
/**
51+
* Shows how to set and read com.google.rpc.Status objects as google.rpc.Status error details.
52+
*/
53+
public class ErrorDetailsExample {
54+
private static final DebugInfo DEBUG_INFO =
55+
DebugInfo.newBuilder()
56+
.addStackEntries("stack_entry_1")
57+
.addStackEntries("stack_entry_2")
58+
.addStackEntries("stack_entry_3")
59+
.setDetail("detailed error info.").build();
60+
61+
public static void main(String[] args) throws Exception {
62+
Server server = null;
63+
ManagedChannel channel = null;
64+
65+
try {
66+
server = launchServer();
67+
channel = Grpc.newChannelBuilderForAddress(
68+
"localhost", server.getPort(), InsecureChannelCredentials.create()).build();
69+
70+
runClientTests(channel);
71+
} finally {
72+
cleanup(channel, server);
73+
}
74+
}
75+
76+
77+
/**
78+
* Create server and start it
79+
*/
80+
static Server launchServer() throws Exception {
81+
return Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create())
82+
.addService(new GreeterGrpc.GreeterImplBase() {
83+
@Override
84+
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
85+
// This is com.google.rpc.Status, not io.grpc.Status
86+
Status status = Status.newBuilder()
87+
.setCode(Code.INVALID_ARGUMENT.getNumber())
88+
.setMessage("Email or password malformed")
89+
.addDetails(Any.pack(DEBUG_INFO))
90+
.build();
91+
responseObserver.onError(StatusProto.toStatusRuntimeException(status));
92+
}
93+
})
94+
.build()
95+
.start();
96+
}
97+
98+
private static void runClientTests(Channel channel) {
99+
blockingCall(channel);
100+
futureCallDirect(channel);
101+
futureCallCallback(channel);
102+
asyncCall(channel);
103+
}
104+
105+
private static void cleanup(ManagedChannel channel, Server server) throws InterruptedException {
106+
107+
// Shutdown client and server for resources to be cleanly released
108+
if (channel != null) {
109+
channel.shutdown();
110+
}
111+
if (server != null) {
112+
server.shutdown();
113+
}
114+
115+
// Wait for cleanup to complete
116+
if (channel != null) {
117+
channel.awaitTermination(1, TimeUnit.SECONDS);
118+
}
119+
if (server != null) {
120+
server.awaitTermination(1, TimeUnit.SECONDS);
121+
}
122+
}
123+
124+
static void verifyErrorReply(Throwable t) {
125+
Status status = StatusProto.fromThrowable(t);
126+
Verify.verify(status.getCode() == Code.INVALID_ARGUMENT.getNumber());
127+
Verify.verify(status.getMessage().equals("Email or password malformed"));
128+
try {
129+
DebugInfo unpackedDetail = status.getDetails(0).unpack(DebugInfo.class);
130+
Verify.verify(unpackedDetail.equals(DEBUG_INFO));
131+
} catch (InvalidProtocolBufferException e) {
132+
Verify.verify(false, "Message was a different type than expected");
133+
}
134+
}
135+
136+
static void blockingCall(Channel channel) {
137+
GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
138+
try {
139+
stub.sayHello(HelloRequest.newBuilder().build());
140+
} catch (Exception e) {
141+
verifyErrorReply(e);
142+
System.out.println("Blocking call received expected error details");
143+
}
144+
}
145+
146+
static void futureCallDirect(Channel channel) {
147+
GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
148+
ListenableFuture<HelloReply> response =
149+
stub.sayHello(HelloRequest.newBuilder().build());
150+
151+
try {
152+
response.get();
153+
} catch (InterruptedException e) {
154+
Thread.currentThread().interrupt();
155+
throw new RuntimeException(e);
156+
} catch (ExecutionException e) {
157+
verifyErrorReply(e.getCause());
158+
System.out.println("Future call direct received expected error details");
159+
}
160+
}
161+
162+
static void futureCallCallback(Channel channel) {
163+
GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
164+
ListenableFuture<HelloReply> response =
165+
stub.sayHello(HelloRequest.newBuilder().build());
166+
167+
final CountDownLatch latch = new CountDownLatch(1);
168+
169+
Futures.addCallback(
170+
response,
171+
new FutureCallback<HelloReply>() {
172+
@Override
173+
public void onSuccess(@Nullable HelloReply result) {
174+
// Won't be called, since the server in this example always fails.
175+
}
176+
177+
@Override
178+
public void onFailure(Throwable t) {
179+
verifyErrorReply(t);
180+
System.out.println("Future callback received expected error details");
181+
latch.countDown();
182+
}
183+
},
184+
directExecutor());
185+
186+
if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
187+
throw new RuntimeException("timeout!");
188+
}
189+
}
190+
191+
static void asyncCall(Channel channel) {
192+
GreeterStub stub = GreeterGrpc.newStub(channel);
193+
HelloRequest request = HelloRequest.newBuilder().build();
194+
final CountDownLatch latch = new CountDownLatch(1);
195+
StreamObserver<HelloReply> responseObserver = new StreamObserver<HelloReply>() {
196+
197+
@Override
198+
public void onNext(HelloReply value) {
199+
// Won't be called.
200+
}
201+
202+
@Override
203+
public void onError(Throwable t) {
204+
verifyErrorReply(t);
205+
System.out.println("Async call received expected error details");
206+
latch.countDown();
207+
}
208+
209+
@Override
210+
public void onCompleted() {
211+
// Won't be called, since the server in this example always fails.
212+
}
213+
};
214+
stub.sayHello(request, responseObserver);
215+
216+
if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
217+
throw new RuntimeException("timeout!");
218+
}
219+
}
220+
}
221+

0 commit comments

Comments
 (0)