{"record":{"id":"4804dc0781b21d26","repo":"siyuan-note/siyuan","slug":"endpoint-does-not-accept-multipart-data","errorCode":null,"errorMessage":"endpoint does not accept multipart data","messagePattern":"endpoint does not accept multipart data","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/multipart.go","lineNumber":40,"sourceCode":"\t\tif !field.IsExported() || field.Anonymous || field.Tag.Get(\"json\") == \"\" {\n\t\t\treturn fmt.Errorf(\"multipart fields must be named and exported: %s\", field.Name)\n\t\t}\n\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 {","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/multipart.go#L22-L58","documentation":"DecodeMultipart is the generic multipart/form-data decoder for contract-bound endpoints. It first checks that the endpoint's declared body kind is MultipartBody or FormBody; if the endpoint was defined with another body type (e.g. JSON), multipart data is rejected. This prevents silently feeding form data to endpoints that expect a different content type.","triggerScenarios":"POSTing multipart/form-data to an endpoint whose contract definition declares a non-multipart, non-form body (e.g. JSONBody).","commonSituations":"A client sends FormData with fetch/axios to an API that expects a JSON body; the endpoint contract was changed or bound incorrectly; a plugin calls a JSON-only route with form encoding.","solutions":["Send the request as JSON (Content-Type: application/json) matching the endpoint's declared body type.","Use a different endpoint that is declared with MultipartBody or FormBody for file/form uploads.","If you own the endpoint, update its contract definition to MultipartBody/FormBody (and run the API contract generation/check steps)."],"exampleFix":"// before\nfetchPost(\"/api/endpoint\", formData) // endpoint expects JSON\n// after\nfetchPost(\"/api/endpoint\", {field: value}) // or use the declared multipart endpoint","handlingStrategy":"validation","validationCode":"function checkBodyKind(endpointDef, request) {\n  const isForm = request instanceof FormData;\n  const multipartOk = endpointDef.body === \"multipart\" || endpointDef.body === \"form\";\n  if (isForm && !multipartOk) throw new Error(\"endpoint \" + endpointDef.name + \" does not accept FormData\");\n}","typeGuard":"const isFormData = (b) => typeof FormData !== \"undefined\" && b instanceof FormData;","tryCatchPattern":"try { await api.post(url, body); } catch (e) { if (String(e).includes(\"does not accept multipart\")) { await api.post(url, await formDataToJson(body)); } else { throw e; } }","preventionTips":["Match the request encoding to the endpoint's declared body type.","Never set Content-Type manually when switching between JSON and FormData.","Consult the generated API contract to confirm which endpoints take multipart."],"tags":["http","multipart","api"],"backgroundTag":"incompatible-source-type","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"}