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

  1. 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.
  2. Inspect the serialized payload on the wire; if it is truncated, fix the framing/length-prefix logic in the transport layer.
  3. Ensure you are sending the outer `Request` envelope, not an inner message like `MakeTableRequest`, directly.
  4. 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

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)