{"record":{"id":"afa1442512304c78","repo":"siyuan-note/siyuan","slug":"field-s-must-not-be-empty","errorCode":null,"errorMessage":"Field [%s] must not be empty","messagePattern":"Field \\[(.+?)\\] must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/ai_input.go","lineNumber":33,"sourceCode":"\t\tif err != nil {\n\t\t\tmessage := strings.ReplaceAll(err.Error(), reflect.TypeFor[Request]().Name()+\".\", legacyName+\".\")\n\t\t\tfor _, name := range []string{\"Reference\", \"EditorContext\", \"FrontendCapability\", \"ToolEffects\"} {\n\t\t\t\tmessage = strings.ReplaceAll(message, \"AI\"+name+\".\", name+\".\")\n\t\t\t}\n\t\t\terr = fmt.Errorf(\"invalid request: %s\", message)\n\t\t}\n\t\treturn\n\t}\n}\n\nfunc (r AIProviderRequest) ProviderError() error { return r.providerError }\n\nfunc aiLegacyString(fields fileTreeFields, key string, required, trim bool) (value string, err error) {\n\tvalue, err = legacyField[string](fields, key, \"String\", required)\n\tif err == nil && trim {\n\t\tvalue = strings.TrimSpace(value)\n\t\tif value == \"\" {\n\t\t\terr = fmt.Errorf(\"Field [%s] must not be empty\", key)\n\t\t}\n\t}\n\treturn\n}\n\nfunc aiJSONDecoder[Request, Data any](endpoint *Endpoint[Request, Data], bind func(fileTreeFields) (Request, error)) {\n\tendpoint.decodeRequest = func(reader io.Reader) (value Request, err error) {\n\t\tfields, err := fileTreeRequestFields(reader, endpoint.definition.Path)\n\t\tif err == nil {\n\t\t\tvalue, err = bind(fields)\n\t\t}\n\t\treturn\n\t}\n}\n\nfunc aiProviderFields(fields fileTreeFields) AIProviderRequest {\n\trequest := AIProviderRequest{}\n\t_ = json.Unmarshal(fields[\"provider\"], &request.Provider)","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/ai_input.go#L15-L51","documentation":"SiYuan kernel API contract layer rejects AI request fields marked as required-and-trimmed when, after whitespace trimming, the string value is empty. aiLegacyString first decodes the field as a String via legacyField, then applies strings.TrimSpace and fails with 'Field [<key>] must not be empty'. This guards endpoints like /api/ai/chatGPT from receiving blank prompt payloads.","triggerScenarios":"POSTing to the AI chat endpoint with the 'msg' field set to \"\", \"   \", \"\\n\\t\" or omitting it entirely when it is required (e.g. /api/ai/chatGPT with {\"msg\": \"  \"}).","commonSituations":"Bots or scripts that concatenate user input without checking it, prompt templates whose variable substitution produced empty strings, form/textarea bindings that submit whitespace, or clients sending null/missing fields to legacy field-based endpoints.","solutions":["Ensure the AI message field contains a non-empty, non-whitespace string before calling the endpoint","Trim or reject empty input on the client side before sending","If the field is genuinely optional in your flow, check whether the endpoint/field is marked optional; otherwise supply a placeholder value"],"exampleFix":"// before\nfetchPost(\"/api/ai/chatGPT\", {msg: userText});\n// after\nconst msg = (userText ?? \"\").trim();\nif (!msg) throw new Error(\"msg is required\");\nfetchPost(\"/api/ai/chatGPT\", {msg});","handlingStrategy":"validation","validationCode":"function assertNonEmpty(v, name) { const s = (v ?? \"\").trim(); if (!s) throw new Error(`Field [${name}] must not be empty`); return s; }\nconst msg = assertNonEmpty(userText, \"msg\");","typeGuard":"const isNonEmptyString = (v) => typeof v === \"string\" && v.trim().length > 0;","tryCatchPattern":"try { const data = await post(\"/api/ai/chatGPT\", {msg}); } catch (e) { if (/must not be empty/.test(e.message)) { /* prompt user for input */ } else throw e; }","preventionTips":["Trim and check user-supplied text before every AI API call","Bind UI inputs with required + minlength validation","Never interpolate possibly-undefined variables into request fields without a default"],"tags":["validation","api","empty-input"],"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"}