Ответ
В gRPC, в отличие от стандартных ошибок Go, которые являются просто строками, ошибки представляют собой структурированные данные. Это позволяет передавать клиенту не только текстовое сообщение, но и машиночитаемые детали.
Для этого используется пакет google.golang.org/grpc/status
.
На сервере:
- Создается статус с помощью
status.New()
, который принимает стандартный код ошибки (изgoogle.golang.org/grpc/codes
) и сообщение. - К статусу можно добавить детали (
details
) — это любые Protobuf-сообщения. Часто используют предопределенные типы изgoogle.golang.org/genproto/googleapis/rpc/errdetails
. - Из статуса получается ошибка с помощью метода
st.Err()
.
import (
"context"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *Server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
if req.Email == "" { // Пример валидации
// 1. Создаем базовый статус
st := status.New(codes.InvalidArgument, "invalid request: email is required")
// 2. Создаем и добавляем детализированную информацию об ошибке
br := &errdetails.BadRequest_FieldViolation{
Field: "email",
Description: "email field cannot be empty",
}
stWithDetails, err := st.WithDetails(br)
if err != nil {
// Если детали не удалось сериализовать, возвращаем исходный статус
return nil, st.Err()
}
// 3. Возвращаем ошибку с деталями
return nil, stWithDetails.Err()
}
// ...
}
На клиенте:
- Полученная ошибка
error
преобразуется обратно вstatus.Status
с помощьюstatus.FromError()
. - Из статуса извлекаются детали с помощью метода
st.Details()
.
// ... после вызова RPC
if err != nil {
// 1. Преобразуем ошибку в статус
st, ok := status.FromError(err)
if !ok {
// Ошибка не является gRPC статусом, обрабатываем как обычную
log.Printf("Unknown error: %v", err)
return
}
log.Printf("gRPC Error: code = %s, message = %s", st.Code(), st.Message())
// 2. Проверяем детали
for _, detail := range st.Details() {
switch t := detail.(type) {
case *errdetails.BadRequest_FieldViolation:
log.Printf("Bad Request Field: '%s' - %s", t.Field, t.Description)
default:
log.Printf("Unexpected detail type: %s", t)
}
}
}