Error Handling¶
RpcError¶
RpcError represents a protocol-level error with a type and message:
The Type field uses Python exception class names by convention (e.g. "ValueError", "RuntimeError", "TypeError"). This ensures compatibility with Python clients that map error types to exception classes.
ErrRpc Sentinel¶
Use errors.Is(err, vgirpc.ErrRpc) to check whether any error in a chain is an *RpcError:
Error Types¶
| Type | Typical use |
|---|---|
ValueError |
Invalid parameter value |
TypeError |
Wrong parameter type or method type mismatch |
RuntimeError |
General server-side error |
AttributeError |
Unknown method name |
VersionError |
Protocol version mismatch |
SerializationError |
Failed to serialize result |
Returning Errors from Handlers¶
Any handler can return an *RpcError to send a typed error to the client:
vgirpc.Unary(server, "divide", func(_ context.Context, _ *vgirpc.CallContext, p DivideParams) (float64, error) {
if p.B == 0 {
return 0, &vgirpc.RpcError{
Type: "ValueError",
Message: "division by zero",
}
}
return p.A / p.B, nil
})
Non-RpcError errors returned from handlers are wrapped as RuntimeError automatically.
The error model: code, kind, details¶
Every EXCEPTION batch carries three layers, adopted from gRPC's
google.rpc.Status (WIRE_PROTOCOL.md ยง8):
| Layer | Wire key | Go |
|---|---|---|
| Code | vgi_rpc.error_code |
vgirpc.Code -- one of the sixteen gRPC codes minus OK, sent by name ("UNAVAILABLE") |
| Reason | vgi_rpc.error_kind |
an open, stable token a client branches on |
| Details | vgi_rpc.error_details |
a JSON array from a fixed catalog: ErrorInfo, RetryInfo, BadRequest, PreconditionFailure, QuotaFailure, ResourceInfo, Help, LocalizedMessage |
The code is sent on every error -- UNKNOWN when the error is unclassified --
and all three are mirrored in log_extra. Return a *vgirpc.StatusError to
choose them:
return &vgirpc.StatusError{
Code: vgirpc.CodeUnavailable,
Kind: "report_rebuilding",
Message: "report is being rebuilt",
Details: []vgirpc.ErrorDetail{vgirpc.RetryInfo{RetryDelaySeconds: 30}},
}
Any error type may instead implement ErrorCode() vgirpc.Code,
ErrorKind() string and ErrorDetails() []vgirpc.ErrorDetail; they are found
with errors.As, so wrapping with %w keeps the classification. A
protocol-defined detail goes in a vgirpc.RawDetail whose @type lives under
the protocol's own name. Each type may appear once, and the serialized array is
capped at 4 KiB: an array that breaks a rule or exceeds the cap is dropped
whole (code and kind are still sent). vgirpc.ValidateErrorDetails checks
an array up front.
Details never carry credentials, tokens or user data.
On the client¶
A decoded *RpcError carries Code ("" when the server predates the model,
which is not the same as "UNKNOWN"), Kind and Details (every object as
received, unknown types included), plus typed accessors that skip what they do
not know:
var rpcErr *vgirpc.RpcError
if errors.As(err, &rpcErr) && rpcErr.IsRetryable() {
wait := time.Second
if ri, ok := rpcErr.RetryInfo(); ok {
wait = time.Duration(ri.RetryDelaySeconds * float64(time.Second))
}
_ = wait // the caller decides whether to retry
}
IsRetryable follows the code: UNAVAILABLE, and RESOURCE_EXHAUSTED when it
carries RetryInfo. ABORTED means retry the whole operation at a higher
level. The client never retries an RPC error itself, because a method may not
be idempotent.
Tracebacks¶
Server.SetIncludeTracebacks decides whether errors carry the Go stack trace.
They are included by default on every transport -- the DuckDB extension shows
the remote traceback to its user -- and SetIncludeTracebacks(false) turns
them off for the whole server, on every transport it serves. The type,
message, code, kind and details are sent either way. (SetDebugErrors is the
older name for the same setting.)