{"record":{"id":"ad12d92cdf2c45b7","repo":"siyuan-note/siyuan","slug":"field-assets-must-not-be-empty","errorCode":null,"errorMessage":"Field [assets] must not be empty","messagePattern":"Field \\[assets\\] must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/clipboard_input.go","lineNumber":22,"sourceCode":"\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\nfunc init() {\n\tPrepareRichText.decodeRequest = func(reader io.Reader) (request PrepareRichTextRequest, err error) {\n\t\tfields, err := blockRequestFields(reader, \"/api/clipboard/prepareRichText\")\n\t\tif err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\tassets, err := legacyField[[]json.RawMessage](fields, \"assets\", \"Array\", true)\n\t\tif err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\tif len(assets) == 0 {\n\t\t\treturn request, fmt.Errorf(\"Field [assets] must not be empty\")\n\t\t}\n\t\trequest.Assets = make([]RichClipboardAsset, 0, len(assets))\n\t\tfor i, raw := range assets {\n\t\t\tvar asset map[string]json.RawMessage\n\t\t\tif json.Unmarshal(raw, &asset) != nil || asset == nil {\n\t\t\t\treturn request, fmt.Errorf(\"Field [assets.%d] should be of type [Object]\", i)\n\t\t\t}\n\t\t\tvar index float64\n\t\t\tvar path, box string\n\t\t\tif bytes.Equal(asset[\"index\"], []byte(\"null\")) || json.Unmarshal(asset[\"index\"], &index) != nil || index < 0 || index != float64(int(index)) || json.Unmarshal(asset[\"path\"], &path) != nil || strings.TrimSpace(path) == \"\" {\n\t\t\t\treturn request, fmt.Errorf(\"Invalid rich clipboard asset at index [%d]\", i)\n\t\t\t}\n\t\t\tif value, exists := asset[\"box\"]; exists {\n\t\t\t\tif bytes.Equal(value, []byte(\"null\")) || json.Unmarshal(value, &box) != nil {\n\t\t\t\t\treturn request, fmt.Errorf(\"Field [assets.%d.box] should be of type [String]\", i)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequest.Assets = append(request.Assets, RichClipboardAsset{Index: int(index), Path: strings.TrimSpace(path), Box: strings.TrimSpace(box)})","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/clipboard_input.go#L4-L40","documentation":"The /api/clipboard/prepareRichText request decoder requires the assets array to contain at least one element. An empty assets array passes the type check (it is an Array) but is rejected because preparing rich text with no assets is meaningless.","triggerScenarios":"POSTing {\"assets\": []} to /api/clipboard/prepareRichText, or omitting asset entries while building the payload programmatically.","commonSituations":"Frontend collects no selected blocks/assets before calling the API; a loop that conditionally appends assets never runs; copying an empty selection.","solutions":["Ensure at least one asset object (with index and path) is included in the assets array","Check upstream selection logic so the API is only called when there is content to prepare","Handle the error client-side by skipping the call when the asset list is empty"],"exampleFix":"// before\nfetchPost(\"/api/clipboard/prepareRichText\", {assets: []})\n// after\nif (assets.length > 0) { fetchPost(\"/api/clipboard/prepareRichText\", {assets}) }","handlingStrategy":"validation","validationCode":"if (!Array.isArray(assets) || assets.length === 0) throw new Error('assets must be a non-empty array');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Guard for empty selections before calling prepareRichText","Only call the API when there is actual clipboard content","Treat empty assets as a skip condition, not an error to send"],"tags":["validation","clipboard","request-body"],"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"}