{"record":{"id":"ce072343adede6f2","repo":"siyuan-note/siyuan","slug":"rpc-batch-response-must-not-be-empty","errorCode":null,"errorMessage":"RPC batch response must not be empty","messagePattern":"RPC batch response must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/plugin_rpc.go","lineNumber":115,"sourceCode":"}\n\ntype PluginRPCResponse struct {\n\tsingle *PluginRPCReply\n\tbatch  []PluginRPCReply\n}\n\nfunc RPCSingleResponse(reply PluginRPCReply) PluginRPCResponse {\n\treturn PluginRPCResponse{single: &reply}\n}\nfunc RPCBatchResponse(replies []PluginRPCReply) PluginRPCResponse {\n\treturn PluginRPCResponse{batch: replies}\n}\nfunc (r PluginRPCResponse) MarshalJSON() ([]byte, error) {\n\tif r.single != nil {\n\t\treturn json.Marshal(r.single)\n\t}\n\tif len(r.batch) == 0 {\n\t\treturn nil, fmt.Errorf(\"RPC batch response must not be empty\")\n\t}\n\treturn json.Marshal(r.batch)\n}\n\ntype PluginRPCCall struct {\n\tMethod        string\n\tParams        JSONValue\n\tParamsPresent bool\n\tParamsNull    bool\n\tID            PluginRPCID\n\tIDPresent     bool\n}\n\ntype PluginRPCParsedCall struct {\n\tRequest *PluginRPCCall\n\tError   *PluginRPCFailure\n}\n","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/plugin_rpc.go#L97-L133","documentation":"PluginRPCResponse represents either a single reply or a JSON-RPC batch (array) response, selected by RPCSingleResponse() or RPCBatchResponse(). MarshalJSON throws when neither is set, or when RPCBatchResponse was called with an empty or nil slice: a batch response with zero entries is invalid per JSON-RPC 2.0 (an empty request array must yield a single error response, not an empty one).","triggerScenarios":"Marshaling a zero PluginRPCResponse, or calling RPCBatchResponse(nil)/RPCBatchResponse([]PluginRPCResponse{}{}) and serializing the result.","commonSituations":"Building replies in a loop that never executes (empty request batch handled elsewhere), accumulating batch replies into a nil slice, or default-constructing the response type before deciding on single vs batch.","solutions":["Only call RPCBatchResponse when len(replies) > 0; otherwise fall back to RPCSingleResponse","If the incoming batch is empty, return the protocol-defined error response instead of an empty batch","Guard marshal sites by checking that the response was constructed with one of the two builders"],"exampleFix":"// before\nvar replies []apicontract.PluginRPCReply\nreturn apicontract.RPCBatchResponse(replies)\n// after\nif len(replies) == 0 {\n    return apicontract.RPCSingleResponse(apicontract.RPCFailureReply(\n        apicontract.RPCErrorResponse(-32600, \"Invalid Request\", \"empty batch\")))\n}\nreturn apicontract.RPCBatchResponse(replies)","handlingStrategy":"validation","validationCode":"if len(replies) == 0 { return apicontract.RPCSingleResponse(apicontract.RPCFailureReply(\n    apicontract.RPCErrorResponse(-32600, \"Invalid Request\", \"empty batch\"))) }","typeGuard":null,"tryCatchPattern":"data, err := json.Marshal(resp)\nif err != nil {\n    resp = apicontract.RPCSingleResponse(apicontract.RPCFailureReply(\n        apicontract.RPCErrorResponse(-32603, \"Internal error\", err.Error())))\n    data, err = json.Marshal(resp)\n}","preventionTips":["Never call RPCBatchResponse with an empty slice; branch to a single error response","Remember JSON-RPC 2.0 forbids empty batch arrays on both request and response side","Assert non-empty batches in tests with at least one entry"],"tags":["json-rpc","serialization","go","empty-collection"],"backgroundTag":"empty-required-field","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}