{"record":{"id":"e8973a8725de94bc","repo":"siyuan-note/siyuan","slug":"multipart-form-is-missing","errorCode":null,"errorMessage":"multipart form is missing","messagePattern":"multipart form is missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/multipart.go","lineNumber":43,"sourceCode":"\t\tif field.Type != reflect.TypeFor[string]() && field.Type != reflect.TypeFor[*string]() && field.Type != reflect.TypeFor[*multipart.FileHeader]() && field.Type != reflect.TypeFor[[]*multipart.FileHeader]() {\n\t\t\treturn fmt.Errorf(\"unsupported multipart field: %s\", field.Name)\n\t\t}\n\t\tfor _, option := range strings.Split(field.Tag.Get(\"api\"), \",\") {\n\t\t\tif option != \"\" && option != \"optional\" && option != \"nonnullable\" {\n\t\t\t\treturn fmt.Errorf(\"unsupported multipart option: %s\", option)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// DecodeMultipart 保留表单重复字段取首值的行为，文件内容由业务入口按需读取。\nfunc (e Endpoint[Request, Data]) DecodeMultipart(form *multipart.Form) (request Request, err error) {\n\tif e.definition.Body != MultipartBody && e.definition.Body != FormBody {\n\t\treturn request, fmt.Errorf(\"endpoint does not accept multipart data\")\n\t}\n\tif form == nil {\n\t\treturn request, fmt.Errorf(\"multipart form is missing\")\n\t}\n\tvalue := reflect.ValueOf(&request).Elem()\n\tif value.Type() == reflect.TypeFor[ExtensionCopyRequest]() {\n\t\tdecoded, decodeErr := decodeExtensionCopyForm(form)\n\t\tif decodeErr != nil {\n\t\t\treturn request, decodeErr\n\t\t}\n\t\tvalue.Set(reflect.ValueOf(decoded))\n\t\treturn\n\t}\n\tif value.Type() == reflect.TypeFor[MultipartFields]() {\n\t\tvalue.Set(reflect.ValueOf(MultipartFields{Value: form.Value, File: form.File}))\n\t\treturn\n\t}\n\tif value.Kind() != reflect.Struct {\n\t\treturn request, fmt.Errorf(\"multipart request must be a struct\")\n\t}\n\tif err = validateMultipartRequest(value.Type()); err != nil {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/multipart.go#L25-L61","documentation":"DecodeMultipart requires a non-nil *multipart.Form; a nil form means the request carried no parseable multipart content. The decoder returns this error rather than dereferencing nil. It usually indicates the request body was empty or not actually multipart/form-data despite hitting a multipart endpoint.","triggerScenarios":"Calling a multipart endpoint with an empty body, with Content-Type not set to multipart/form-data (so the Gin parser produced no form), or programmatically invoking DecodeMultipart(nil).","commonSituations":"Client forgot to append any fields/files to FormData; missing enctype on an HTML form; a proxy stripped the multipart body; boundary parameter missing from Content-Type.","solutions":["Ensure the client sends a real multipart/form-data body with at least one field or file.","Set the correct Content-Type with boundary (let fetch/axios/curl -F build it automatically instead of setting the header manually).","Verify server-side middleware actually parsed the request into *multipart.Form before calling DecodeMultipart."],"exampleFix":"// before\nfetch(url, {method: \"POST\", headers: {\"Content-Type\": \"multipart/form-data\"}, body: formData}) // boundary lost\n// after\nfetch(url, {method: \"POST\", body: formData}) // browser sets Content-Type + boundary","handlingStrategy":"validation","validationCode":"function checkMultipartBody(formData) {\n  if (!(formData instanceof FormData) || [...formData.entries()].length === 0) {\n    throw new Error(\"multipart body is empty\");\n  }\n}","typeGuard":"const hasEntries = (f) => f != null && typeof f.entries === \"function\" && [...f.entries()].length > 0;","tryCatchPattern":"try { await api.post(url, formData); } catch (e) { if (String(e).includes(\"multipart form is missing\")) { throw new Error(\"request body was empty or not multipart/form-data: \" + e.message); } throw e; }","preventionTips":["Always append at least one field/file before sending FormData.","Let fetch/axios generate the multipart Content-Type with boundary.","Verify HTML forms use enctype=\"multipart/form-data\"."],"tags":["http","multipart","request"],"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"}