{"record":{"id":"52cd0e6cdf332a90","repo":"siyuan-note/siyuan","slug":"field-file-or-url-must-not-be-empty","errorCode":null,"errorMessage":"field [file] or [url] must not be empty","messagePattern":"field \\[file\\] or \\[url\\] must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":323,"sourceCode":"\trelativePath, _ = filepath.Rel(emojisDir, emojiPath)\n\trelativePath = filepath.ToSlash(relativePath)\n\tret.Data = map[string]any{\"path\": relativePath}\n}\n\nfunc readCustomEmojiData(c *gin.Context) ([]byte, error) {\n\tfileHeader, fileErr := c.FormFile(\"file\")\n\tif fileErr == nil {\n\t\tfile, err := fileHeader.Open()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer file.Close()\n\t\treturn io.ReadAll(io.LimitReader(file, maxCustomEmojiSize+1))\n\t}\n\n\trawURL := strings.TrimSpace(c.PostForm(\"url\"))\n\tif rawURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"field [file] or [url] must not be empty\")\n\t}\n\treturn downloadCustomEmojiData(rawURL)\n}\n\nfunc downloadCustomEmojiData(rawURL string) ([]byte, error) {\n\tparsedURL, err := url.Parse(rawURL)\n\tif err != nil || (parsedURL.Scheme != \"http\" && parsedURL.Scheme != \"https\") || parsedURL.Host == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid custom emoji URL\")\n\t}\n\n\tresponse, err := util.NewCustomReqClient().R().Get(parsedURL.String())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"download custom emoji failed: %w\", err)\n\t}\n\tdefer response.Body.Close()\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"download custom emoji failed with status %d\", response.StatusCode)\n\t}","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/api/system.go#L305-L341","documentation":"Returned by readCustomEmojiData (system.go:323) when neither a multipart 'file' part nor a 'url' POST form field was supplied to the custom-emoji upload endpoint. The handler first tries c.FormFile(\"file\"); if that errors, it falls back to the 'url' form field; if that is also empty/whitespace, the request is rejected because there is nothing to import.","triggerScenarios":"Calling the custom-emoji endpoint with a multipart form missing both the 'file' part and a 'url' field, or a JSON body without a url and no file attachment. readCustomEmojiData at system.go:310 falls through both branches to the empty-URL guard at line 322.","commonSituations":"Frontend lets the user choose upload-or-URL but submits before either is filled. Curl invocation missing both -F 'file=@...' and -F 'url=http://...'. Form field named 'link'/'src' instead of 'url'. Content-Type set to JSON with {url: ''}.","solutions":["Provide at least one source: attach a 'file' part OR send a non-empty 'url' form field.","If using URL mode, POST as form-urlencoded/multipart with url=<http(s) address>.","Disable the submit control until one of the two inputs is non-empty."],"exampleFix":"// before\nfetchPost('/api/system/customEmoji', {})\n// after\nconst fd = new FormData(); fd.append('url', 'https://cdn/emoji.png')\nfetch('/api/system/customEmoji', { method:'POST', body: fd })","handlingStrategy":"validation","validationCode":"// Require either a file blob or a non-empty URL\nif (!fileBlob && !url) { throw new Error('provide a file or url'); }\nconst fd = new FormData();\nif (fileBlob) fd.append('file', fileBlob); else fd.append('url', url);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Disable submit until either a file is selected or a URL entered.","Use the exact field names 'file' / 'url'.","POST as multipart/form-data or form-urlencoded, not JSON."],"tags":["emoji","upload","validation","kernel"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}