Skip to content

Commit 5415d18

Browse files
enocomdfawley
authored andcommitted
Add documentation and example of adding details to errors (grpc#1915)
1 parent 57640c0 commit 5415d18

File tree

3 files changed

+209
-0
lines changed

3 files changed

+209
-0
lines changed

Documentation/rpc-errors.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# RPC Errors
2+
3+
All service method handlers should return `nil` or errors from the
4+
`status.Status` type. Clients have direct access to the errors.
5+
6+
Upon encountering an error, a gRPC server method handler should create a
7+
`status.Status`. In typical usage, one would use [status.New][new-status]
8+
passing in an appropriate [codes.Code][code] as well as a description of the
9+
error to produce a `status.Status`. Calling [status.Err][status-err] converts
10+
the `status.Status` type into an `error`. As a convenience method, there is also
11+
[status.Error][status-error] which obviates the conversion step. Compare:
12+
13+
```
14+
st := status.New(codes.NotFound, "some description")
15+
err := st.Err()
16+
17+
// vs.
18+
19+
err := status.Error(codes.NotFound, "some description")
20+
```
21+
22+
## Adding additional details to errors
23+
24+
In some cases, it may be necessary to add details for a particular error on the
25+
server side. The [status.WithDetails][with-details] method exists for this
26+
purpose. Clients may then read those details by first converting the plain
27+
`error` type back to a [status.Status][status] and then using
28+
[status.Details][details].
29+
30+
## Example
31+
32+
The [example][example] demonstrates the API discussed above and shows how to add
33+
information about rate limits to the error message using `status.Status`.
34+
35+
To run the example, first start the server:
36+
37+
```
38+
$ go run examples/rpc_errors/server/main.go
39+
```
40+
41+
In a separate sesssion, run the client:
42+
43+
```
44+
$ go run examples/rpc_errors/client/main.go
45+
```
46+
47+
On the first run of the client, all is well:
48+
49+
```
50+
2018/03/12 19:39:33 Greeting: Hello world
51+
```
52+
53+
Upon running the client a second time, the client exceeds the rate limit and
54+
receives an error with details:
55+
56+
```
57+
2018/03/19 16:42:01 Quota failure: violations:<subject:"name:world" description:"Limit one greeting per person" >
58+
exit status 1
59+
```
60+
61+
[status]: https://godoc.org/google.golang.org/grpc/status#Status
62+
[new-status]: https://godoc.org/google.golang.org/grpc/status#New
63+
[code]: https://godoc.org/google.golang.org/grpc/codes#Code
64+
[with-details]: https://godoc.org/google.golang.org/grpc/status#Status.WithDetails
65+
[details]: https://godoc.org/google.golang.org/grpc/status#Status.Details
66+
[status-err]: https://godoc.org/google.golang.org/grpc/status#Status.Err
67+
[example]: https://github.com/grpc/grpc-go/blob/master/examples/rpc_errors

examples/rpc_errors/client/main.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
*
3+
* Copyright 2018 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+
package main
20+
21+
import (
22+
"log"
23+
"os"
24+
"time"
25+
26+
"golang.org/x/net/context"
27+
epb "google.golang.org/genproto/googleapis/rpc/errdetails"
28+
"google.golang.org/grpc"
29+
pb "google.golang.org/grpc/examples/helloworld/helloworld"
30+
"google.golang.org/grpc/status"
31+
)
32+
33+
func main() {
34+
// Set up a connection to the server.
35+
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
36+
if err != nil {
37+
log.Fatalf("did not connect: %v", err)
38+
}
39+
defer func() {
40+
if e := conn.Close(); e != nil {
41+
log.Printf("failed to close connection: %s", e)
42+
}
43+
}()
44+
c := pb.NewGreeterClient(conn)
45+
46+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
47+
defer cancel()
48+
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
49+
if err != nil {
50+
s := status.Convert(err)
51+
for _, d := range s.Details() {
52+
switch info := d.(type) {
53+
case *epb.QuotaFailure:
54+
log.Printf("Quota failure: %s", info)
55+
default:
56+
log.Printf("Unexpected type: %s", info)
57+
}
58+
}
59+
os.Exit(1)
60+
}
61+
log.Printf("Greeting: %s", r.Message)
62+
}

examples/rpc_errors/server/main.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
*
3+
* Copyright 2018 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+
package main
20+
21+
import (
22+
"fmt"
23+
"log"
24+
"net"
25+
"sync"
26+
27+
"golang.org/x/net/context"
28+
epb "google.golang.org/genproto/googleapis/rpc/errdetails"
29+
"google.golang.org/grpc"
30+
"google.golang.org/grpc/codes"
31+
pb "google.golang.org/grpc/examples/helloworld/helloworld"
32+
"google.golang.org/grpc/status"
33+
)
34+
35+
const (
36+
port = ":50051"
37+
)
38+
39+
// server is used to implement helloworld.GreeterServer.
40+
type server struct {
41+
mu sync.Mutex
42+
count map[string]int
43+
}
44+
45+
// SayHello implements helloworld.GreeterServer
46+
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
47+
s.mu.Lock()
48+
defer s.mu.Unlock()
49+
// Track the number of times the user has been greeted.
50+
s.count[in.Name]++
51+
if s.count[in.Name] > 1 {
52+
st := status.New(codes.ResourceExhausted, "Request limit exceeded.")
53+
ds, err := st.WithDetails(
54+
&epb.QuotaFailure{
55+
Violations: []*epb.QuotaFailure_Violation{{
56+
Subject: fmt.Sprintf("name:%s", in.Name),
57+
Description: "Limit one greeting per person",
58+
}},
59+
},
60+
)
61+
if err != nil {
62+
return nil, st.Err()
63+
}
64+
return nil, ds.Err()
65+
}
66+
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
67+
}
68+
69+
func main() {
70+
log.Printf("server starting on port %s...", port)
71+
lis, err := net.Listen("tcp", port)
72+
if err != nil {
73+
log.Fatalf("failed to listen: %v", err)
74+
}
75+
s := grpc.NewServer()
76+
pb.RegisterGreeterServer(s, &server{count: make(map[string]int)})
77+
if err := s.Serve(lis); err != nil {
78+
log.Fatalf("failed to serve: %v", err)
79+
}
80+
}

0 commit comments

Comments
 (0)