Handling Errors and Status Codes
1. Creating Status Errors
| API | Use |
|---|---|
status.Error(code, msg) | Simple error |
status.Errorf(code, fmt, ...) | Formatted |
status.New(code, msg).WithDetails(...) | With error details |
2. Using Standard Status Codes
| Code | Meaning | HTTP-like Analog |
|---|---|---|
| OK (0) | Success | 200 |
| Canceled (1) | Client cancelled | 499 |
| Unknown (2) | Unmapped error | 500 |
| InvalidArgument (3) | Bad input | 400 |
| DeadlineExceeded (4) | Timeout | 504 |
| NotFound (5) | Missing resource | 404 |
| AlreadyExists (6) | Duplicate | 409 |
| PermissionDenied (7) | AuthZ failed | 403 |
| ResourceExhausted (8) | Quota/rate limit | 429 |
| FailedPrecondition (9) | State invalid | 400 |
| Aborted (10) | Concurrency conflict | 409 |
| OutOfRange (11) | Range error | 400 |
| Unimplemented (12) | Method not impl | 501 |
| Internal (13) | Server bug | 500 |
| Unavailable (14) | Service down — retryable | 503 |
| DataLoss (15) | Unrecoverable corruption | 500 |
| Unauthenticated (16) | Missing creds | 401 |
3. Adding Error Details
| Detail Type | Purpose |
|---|---|
BadRequest | Field violations |
ErrorInfo | Domain + reason code |
RetryInfo | Suggested retry delay |
QuotaFailure | Quota exceeded specifics |
PreconditionFailure | State issues |
ResourceInfo | Resource type + name |
Help | Doc links |
LocalizedMessage | i18n message |
4. Extracting Status from Error
Example: Status extraction
st, ok := status.FromError(err)
if ok {
fmt.Println(st.Code(), st.Message())
for _, d := range st.Details() {
switch info := d.(type) {
case *errdetails.BadRequest:
// handle field violations
case *errdetails.RetryInfo:
time.Sleep(info.RetryDelay.AsDuration())
}
}
}
5. Creating Custom Error Details
Example: Custom detail message
// in proto:
message AppError {
string code = 1;
string trace_id = 2;
}
st, _ := status.New(codes.Internal, "db down").
WithDetails(&AppError{Code: "DB_001", TraceId: traceID})
return st.Err()
6. Checking Specific Status Codes
Example: Code-based handling
switch status.Code(err) {
case codes.NotFound:
return defaultUser(), nil
case codes.Unavailable, codes.DeadlineExceeded:
return retry()
default:
return nil, err
}
7. Handling Canceled Errors
| Source | Action |
|---|---|
| Client canceled | Treat as normal — don't log as error |
| Upstream canceled | Propagate as codes.Canceled |
8. Handling Timeout Errors
| Strategy | Detail |
|---|---|
| Retry with new deadline | If idempotent |
| Surface to user | For non-idempotent ops |
| Circuit breaker | Detect repeated timeouts |
9. Propagating Errors
| Rule | Detail |
|---|---|
| Preserve status | Don't wrap with fmt.Errorf if returning to gRPC client |
| Map domain → status | At service boundary only |
| Sanitize | Don't leak stack traces or DB errors |
10. Converting Errors
Example: Domain → gRPC mapping
func toStatus(err error) error {
switch {
case errors.Is(err, repo.ErrNotFound):
return status.Error(codes.NotFound, "not found")
case errors.Is(err, repo.ErrConflict):
return status.Error(codes.AlreadyExists, "exists")
case errors.Is(err, context.DeadlineExceeded):
return status.Error(codes.DeadlineExceeded, "deadline")
default:
return status.Error(codes.Internal, "internal")
}
}