perspective-dev/perspective · error · std::runtime_error
Unhandled request type 2
Error message
Unhandled request type 2
What it means
Thrown by the static helper `entity_type_is_table()` in the Perspective C++ server when the incoming `proto::Request` has no oneof member set at all (`CLIENT_REQ_NOT_SET`). The dispatcher must know which entity (table vs view) the request targets, and an empty request carries no such information. It means the client sent a request message that was missing its required payload field — every valid request must set exactly one of the `Request` oneof members (e.g. `make_table_req`, `view_schema_req`).
Solutions
- On the client, verify the Request is fully populated before sending: check `request.WhichOneof('client_req')` (Python) / the oneof case accessor is not `CLIENT_REQ_NOT_SET` and refuse to send otherwise.
- Inspect the serialized payload on the wire; if it is truncated, fix the framing/length-prefix logic in the transport layer.
- Ensure you are sending the outer `Request` envelope, not an inner message like `MakeTableRequest`, directly.
- If you control the server, reject empty requests earlier with a clearer error instead of throwing from the constexpr dispatcher.
Example fix
// before (client, Python): sends an incomplete request
req = perspective_pb2.Request()
req.table_size_req.table_name = "t"
# ...but if table_name is empty some code paths skip filling the oneof
send(req)
// after: guard before sending
req = perspective_pb2.Request()
req.table_size_req.table_name = "t"
if req.WhichOneof("client_req") is None:
raise ValueError("Request has no payload set; refusing to send")
send(req) Defensive patterns
Strategy: validation
Validate before calling
# Python: refuse to send a Request whose oneof is unset
payload = req.WhichOneof("client_req")
if payload is None:
raise ValueError("proto::Request has no client_req oneof set; server will reject with 'Unhandled request type 2'")
if not req.HasField(payload): # defensible double-check
raise ValueError(f"oneof field {payload} present but empty") Type guard
// TypeScript: narrow an untyped request envelope before sending
function hasRequestPayload(req: Record<string, unknown>): boolean {
const oneofKeys = [
"makeTableReq", "tableSizeReq", "tableSchemaReq", "viewSchemaReq",
"viewDeleteReq", "tableRemoveReq", /* ... full known-case list */
];
return oneofKeys.some((k) => req[k] != null);
} Try / catch
try:
resp = stub.handle_request(req, timeout=30)
except grpc.RpcError as e:
if "Unhandled request type" in e.details():
# request reached the server with no oneof set — fix construction, not retry
raise RequestConstructionError(req) from e
raise Prevention
- Always construct requests through a helper that requires the oneof payload as an argument, never send default-constructed Request objects.
- Call WhichOneof / the case accessor as a precondition before every send.
- Beware clearing oneof fields (ClearField on the oneof name) after construction; re-validate afterwards.
- Use length-prefixed framing and checksums on custom transports to avoid truncated protobuf parses yielding empty messages.
When it happens
Trigger: Constructing a `proto::Request` and sending it without setting any of the oneof fields (e.g. calling `Send` on a default-constructed Request), clearing the oneof before sending, or transmitting truncated/corrupted bytes so the server-side parse yields a Request with `client_req_case() == CLIENT_REQ_NOT_SET`.
Common situations: A client bug where the request payload was conditionally skipped (e.g. an uninitialized or empty message), a serialization truncation over an unstable network/socket, or hand-rolled protobuf encoding that omits the oneof tag. Also seen when forwarding an inner message instead of the outer `Request` envelope.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
AI-assisted analysis of perspective-dev/perspective@11c8238c0c (2026-09-09).
Data as JSON: /api/errors/cdfb6f9a03d9f1dc.
Report an issue: GitHub.
Appendix: source
Thrown at rust/perspective-server/cpp/perspective/src/cpp/server.cpp:1324
case ReqCase::kViewToCsvReq:
case ReqCase::kViewToNdjsonStringReq:
case ReqCase::kViewToRowsStringReq:
case ReqCase::kViewToArrowReq:
case ReqCase::kViewSchemaReq:
case ReqCase::kViewGetMinMaxReq:
case ReqCase::kViewOnUpdateReq:
case ReqCase::kViewCollapseReq:
case ReqCase::kViewExpandReq:
case ReqCase::kViewSetDepthReq:
case ReqCase::kViewGetConfigReq:
case ReqCase::kViewColumnPathsReq:
case ReqCase::kViewDeleteReq:
case ReqCase::kViewExpressionSchemaReq:
case ReqCase::kViewRemoveOnUpdateReq:
case ReqCase::kRemoveHostedTablesUpdateReq:
return false;
case proto::Request::CLIENT_REQ_NOT_SET:
throw std::runtime_error("Unhandled request type 2");
}
throw std::runtime_error("Unhandled request type");
}
void
ProtoServer::handle_process_table(
const Request& req,
std::vector<ProtoServerResp<ProtoServer::Response>>& proto_resp
) {
if (!m_realtime_mode && needs_poll(req.client_req_case())) {
if (entity_type_is_table(req.client_req_case())) {
if (m_resources.is_table_dirty(req.entity_id())) {
auto table = m_resources.get_table(req.entity_id());
const auto& table_id = req.entity_id();
_process_table(table, table_id, proto_resp);
}
} else {
auto table_id = m_resources.get_table_id_for_view(req.entity_id());View on GitHub (pinned to 11c8238c0c)