Результат gRPC-вызова передаётся через код состояния, а не через привычный HTTP-статус. Клиент по нему решает, что делать дальше.

Коды

  • InvalidArgument - неверный запрос;
  • NotFound - объект отсутствует;
  • AlreadyExists - конфликт создания;
  • PermissionDenied - нет права;
  • Unauthenticated - не аутентифицирован;
  • FailedPrecondition - состояние не позволяет операцию;
  • ResourceExhausted - квота или лимит;
  • DeadlineExceeded - истёк дедлайн;
  • Unavailable - временная недоступность;
  • Internal - внутренняя ошибка.

Повторять по умолчанию стоит только временные ошибки, причём операция должна быть идемпотентной.

status.New

st := status.New(codes.InvalidArgument, "Заказ не проходит проверку")
st, err := st.WithDetails(&errdetails.BadRequest{
    FieldViolations: []*errdetails.BadRequest_FieldViolation{
        {Field: "items", Description: "пустой заказ"},
    },
})
if err != nil {
    return nil, status.Error(codes.Internal, "cannot build details")
}
return nil, st.Err()

BadRequest передаёт нарушения по полям. ErrorInfo добавляет машинную причину и домен, RetryInfo - рекомендуемую задержку перед повтором.

На клиенте status.Convert(err) даёт код, сообщение и детали. Внутренний стек и секреты в сообщение не кладут.

Как читать ошибку клиенту

Не всякое сообщение стоит показывать пользователю. Клиент сначала смотрит код и машинную причину, затем решает, что делать: исправить поле, повторить позже или показать общий сбой.

st := status.Convert(err)
for _, detail := range st.Details() {
    if info, ok := detail.(*errdetails.ErrorInfo); ok {
        log.Printf("reason=%s domain=%s", info.Reason, info.Domain)
    }
}

ErrorInfo.Reason должен быть устойчивой строкой вроде ORDER_NOT_FOUND, а не русским предложением, которое меняется от версии к версии. Для повтора используют RetryInfo, но сначала проверяют идемпотентность и общий бюджет времени. Если сервер не прислал причину, деталей не выдумывают: остаются код и общее сообщение.

Коротко

  • Код выбирается по смыслу ошибки, а не по удобству.
  • Unavailable временный, FailedPrecondition требует изменения состояния.
  • Детали делают ошибку машиночитаемой: поле, причина, повтор.
  • Секреты и внутренний стек клиенту не отдают.