by Edgar Sipki
message Result {
oneof response {
error.v1.Error error = 1;
info.v1.Info info = 2;
}
}
oneof
to represent either an error or a result is convenient. However, this approach introduces unnecessary complexity into the message exchange protocol and worsens code readability. gRPC provides built-in tools for error handling that allow elegant and efficient transmission of error information.oneof
for errors a bad idea? Firstly, it complicates the use of the standard gRPC error mechanism and status codes designed for this purpose. Secondly, it can lead to confusion on the client side when needing to distinguish between successful responses and errors.oneof
for error handling, it's better to use gRPC's built-in capabilities for transmitting detailed error information.import "google.golang.org/grpc/status"
import "google.golang.org/grpc/codes"
// ...
err := status.Error(codes.NotFound, "cat not found")
// ...
import (
"google.golang.org/grpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/genproto/googleapis/rpc/errdetails"
)
// ...
st := status.New(codes.BadRequest, "invalid parameter")
// General error form
errInfo := &errdetails.ErrorInfo{
Reason: "Insufficient funds in the account",
Domain: "finance",
Metadata: map[string]string{
"my_meta_info": "my_meta_details",
},
}
st, err := st.WithDetails(errInfo)
if err != nil {
return fmt.Sprintf("st.WithDetails: %w", err)
}
return st.Err()
st := status.New(codes.InvalidArgument, "invalid parameter")
details := &errdetails.BadRequest_FieldViolation{
Field: "user_id",
Description: "value must be a valid UUID",
}
br := &errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
details,
// ... can add other error details
},
}
st, err := st.WithDetails(br)
if err != nil {
return fmt.Sprintf("Unexpected error attaching metadata: %w", err)
}
return st.Err()
proto
file. We need to create a message
for the CustomErrorDetail error. It will contain information about errors related to user data:syntax = "proto3";
package myerrors;
message CustomErrorDetail {
string reason = 1;
string field = 2;
string help = 3;
}
import (
"google.golang.org/grpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/anypb"
"myerrors"
)
// ...
customErrorDetail := &myerrors.CustomErrorDetail{
Reason: "Value out of range",
Field: "age",
Help: "The age must be between 0 and 120",
}
st := status.New(codes.InvalidArgument, "invalid parameter")
st, err = st.WithDetails(customErrorDetail)
if err != nil {
return fmt.Sprintf("Unexpected error attaching custom error detail: %w", err)
}
return st.Err()
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"log"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := NewYourServiceClient(conn)
response, err := client.YourMethod(context.Background(), &YourRequest{})
if err != nil {
st, ok := status.FromError(err)
if ok {
switch st.Code() {
case codes.InvalidArgument:
log.Println("Invalid argument error:", st.Message())
case codes.NotFound:
log.Println("Not found error:", st.Message())
// Handle other error codes as needed
default:
log.Println("Unexpected error:", st.Message())
}
} else {
log.Fatalf("failed to call YourMethod: %v", err)
}
} else {
log.Println("Response:", response)
}
}
import (
"google.golang.org/grpc/status"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"myerrors"
"log"
)
// ...
func handleError(err error) {
st, ok := status.FromError(err)
if !ok {
log.Fatalf("An unexpected error occurred: %v", err)
}
for _, detail := range st.Details() {
switch t := detail.(type) {
case *errdetails.BadRequest:
// Processing bad request details
for _, violation := range t.GetFieldViolations() {
log.Printf("The field %s was wrong: %s\n", violation.GetField(), violation.GetDescription())
}
case *myerrors.CustomErrorDetail:
// Processing custom error details
log.Printf("Custom error detail: Reason: %s, Field: %s, Help: %s\n", t.Reason, t.Field, t.Help)
// Add processing for other error types as needed
default:
log.Printf("Received an unknown error detail type: %v\n", t)
}
}
}