{"record":{"id":"ec9347a67d8ce340","repo":"siyuan-note/siyuan","slug":"field-srcs-path-must-not-be-empty","errorCode":null,"errorMessage":"Field [srcs]: path must not be empty","messagePattern":"Field \\[srcs\\]: path must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/file_input.go","lineNumber":35,"sourceCode":"\tif err != nil {\n\t\treturn request, err\n\t}\n\tif len(srcs) == 0 {\n\t\treturn request, fmt.Errorf(\"Field [srcs] must not be empty\")\n\t}\n\trequest.DestDir, err = legacyField[string](fields, \"destDir\", \"String\", true)\n\tif err != nil {\n\t\treturn request, err\n\t}\n\tfor _, raw := range srcs {\n\t\tvar src string\n\t\tif bytes.Equal(raw, []byte(\"null\")) || json.Unmarshal(raw, &src) != nil {\n\t\t\treturn request, fmt.Errorf(\"Field [srcs]: each element should be of type [String]\")\n\t\t}\n\t\tif trim {\n\t\t\tsrc = strings.TrimSpace(src)\n\t\t\tif src == \"\" {\n\t\t\t\treturn request, fmt.Errorf(\"Field [srcs]: path must not be empty\")\n\t\t\t}\n\t\t}\n\t\trequest.Srcs = append(request.Srcs, src)\n\t}\n\treturn\n}\n\nfunc init() {\n\tGlobalCopyFiles.decodeRequest = func(reader io.Reader) (CopyFilesRequest, error) {\n\t\treturn decodeCopyFiles(reader, \"/api/file/globalCopyFiles\", false)\n\t}\n\tWorkspaceCopyFiles.decodeRequest = func(reader io.Reader) (CopyFilesRequest, error) {\n\t\treturn decodeCopyFiles(reader, \"/api/file/workspaceCopyFiles\", true)\n\t}\n}\n","sourceCodeStart":17,"sourceCodeEnd":51,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/file_input.go#L17-L51","documentation":"This error comes from the kernel's contract decoder for the copy-files style API (decodeCopyFiles). After JSON decoding, each element of the 'srcs' array is trimmed and validated; if a resulting path is an empty string the decoder rejects the whole request. The library enforces this because copying a file with an empty source path is meaningless and would otherwise be passed down to filesystem operations.","triggerScenarios":"Calling an API whose body contains a 'srcs' array where at least one element is \"\", or a whitespace-only string like \"   \" that trims to empty. Example: POST with {\"srcs\":[\"/data/notebook/a.sy\", \"\"]}.","commonSituations":"Client-side list building that appends empty entries from blank form fields or split(\"\\n\") on trailing newlines; batch scripts generated from spreadsheets with blank rows; joining paths where the base was empty.","solutions":["Remove empty or whitespace-only strings from the srcs array before sending the request","Check the loop that builds srcs on the client and skip blank inputs","If a blank entry is meaningful, supply the correct path instead of omitting it"],"exampleFix":"// before\nfetchPost(\"/api/filetree/copyFiles\", { srcs: [\"/nb/a.sy\", \"\"] });\n// after\nconst srcs = [\"/nb/a.sy\", \"\"].map(s => s.trim()).filter(s => s.length > 0);\nfetchPost(\"/api/filetree/copyFiles\", { srcs });","handlingStrategy":"validation","validationCode":"const srcs = (payload.srcs || []).map(s => String(s ?? \"\").trim()).filter(s => s.length > 0);\nif (srcs.length === 0) throw new Error(\"srcs must contain at least one non-empty path\");","typeGuard":"const isValidSrcs = (v) => Array.isArray(v) && v.length > 0 && v.every(s => typeof s === \"string\" && s.trim() !== \"\");","tryCatchPattern":null,"preventionTips":["Trim and filter path arrays before every API call","Build lists with split/filter that drops blank entries","Unit-test batch payloads for whitespace-only items"],"tags":["validation","api-request","empty-value"],"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"}