Error Mapping
This page documents default runtime exception-to-gRPC status conversion and practical debugging guidance.
Default Mapping Table
| Exception type | gRPC status | Typical cause |
|---|---|---|
pydantic.ValidationError |
INVALID_ARGUMENT |
request payload doesn't match schema |
RequestDecodeError with validation cause |
INVALID_ARGUMENT |
pb2 request decoded to invalid Pydantic payload |
PermissionError |
PERMISSION_DENIED |
permission check failed |
django.core.exceptions.ObjectDoesNotExist |
NOT_FOUND |
Detail/Get object is missing |
OrderingError / SearchingError |
INVALID_ARGUMENT |
invalid ordering/search field/value |
RequestDecodeError / ResponseEncodeError |
INTERNAL |
runtime conversion failure |
| any other exception | UNKNOWN |
unhandled application/runtime error |
Where Mapping Happens
Runtime catches exceptions inside method wrappers and aborts gRPC context with mapped status + message.
This behavior applies to unary and streaming wrappers.
Common Error Patterns
INVALID_ARGUMENT
Most common triggers:
- missing required request fields
- wrong field types (
strinstead ofint, invalid enum value) - malformed search/order values
What to check:
- request payload against generated proto schema
- request Pydantic schema defaults/constraints
- decorator-provided request augmentation (
search,ordering,limit,offset)
PERMISSION_DENIED
Most common triggers:
has_permreturnedFalsehas_obj_permreturnedFalse
What to check:
- whether method-level permissions intentionally override service-level permissions
- identity attached to
contextin auth backend (context.user) - object-level checks for
Detail/Get
UNAUTHENTICATED
Most common triggers:
- auth backend returned
NoneorFalse - token extraction failed (header/scheme/value format)
What to check:
- metadata key/value in client call
- backend
scheme/headerconfiguration - whitelist logic for public RPC methods (e.g. health)
NOT_FOUND
Most common trigger:
- object lookup in detail endpoint fails
What to check:
- lookup field (
id, external key, etc.) - queryset filters applied by service/data helper
INTERNAL
Most common triggers:
- response payload doesn't match
response_schema - unsupported payload shape for pb2 constructor
What to check:
- actual method return type
- nested schema field types
- decimal/date/time conversions and proto field types
UNKNOWN
Means exception wasn't matched by default mapper.
What to check:
- server logs with stack trace
- add custom mapper to classify domain exceptions
Custom Exception Mapper
You can override mapping globally:
from grpc_extra.exceptions import MappedError
import grpc
def custom_exception_mapper(exc: Exception) -> MappedError:
if isinstance(exc, DomainConflictError):
return MappedError(grpc.StatusCode.ALREADY_EXISTS, str(exc))
return MappedError(grpc.StatusCode.UNKNOWN, str(exc))
GRPC_EXTRA = {
"EXCEPTION_MAPPER": custom_exception_mapper,
# or import path string
# "EXCEPTION_MAPPER": "path.to.custom_exception_mapper",
}
Mapper must be callable; class types are not accepted.
Debugging Checklist
When status is unexpected:
- reproduce with minimal payload
- check generated proto is up to date
- restart server
- recreate client reflection/descriptor cache
- inspect server logs with full traceback
- verify auth + permissions path before business logic