{"record":{"id":"f2ebc999cf40c5e8","repo":"siyuan-note/siyuan","slug":"field-notebook-is-required","errorCode":null,"errorMessage":"Field [notebook] is required","messagePattern":"Field \\[notebook\\] is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/block_remaining.go","lineNumber":141,"sourceCode":"\t\t// 附带块 ID 只参与租约准入，沿用仅收集字符串的规则。\n\t\tvar entries []json.RawMessage\n\t\tif json.Unmarshal(fields[\"ids\"], &entries) == nil {\n\t\t\tfor _, entry := range entries {\n\t\t\t\tvar id string\n\t\t\t\tif !bytes.Equal(entry, []byte(\"null\")) && json.Unmarshal(entry, &id) == nil {\n\t\t\t\t\trequest.IDs = append(request.IDs, id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn request, errors.New(\"invalid block ref check scope\")\n\t}\n\tif raw := fields[\"notebook\"]; len(raw) != 0 && !bytes.Equal(raw, []byte(\"null\")) {\n\t\tif json.Unmarshal(raw, &request.Notebook) != nil {\n\t\t\treturn request, errors.New(\"Field [notebook] should be of type [String]\")\n\t\t}\n\t} else if request.Scope == \"notebook\" {\n\t\treturn request, errors.New(\"Field [notebook] is required\")\n\t}\n\tif request.Scope == \"notebook\" {\n\t\trequest.Notebook = strings.TrimSpace(request.Notebook)\n\t\tif request.Notebook == \"\" {\n\t\t\treturn request, errors.New(\"Field [notebook] must not be empty\")\n\t\t}\n\t}\n\t_ = json.Unmarshal(fields[\"id\"], &request.ID)\n\treturn\n}\n\nfunc decodeHeadingLevel(reader io.Reader) (request HeadingLevelRequest, err error) {\n\tfields, err := blockRequestFields(reader, \"/api/block/getHeadingLevelTransaction\")\n\tif err != nil {\n\t\treturn request, err\n\t}\n\tif len(fields[\"level\"]) == 0 || bytes.Equal(fields[\"level\"], []byte(\"null\")) || json.Unmarshal(fields[\"level\"], &request.Level) != nil {\n\t\treturn request, errors.New(\"Field [level] should be of type [Number]\")","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/block_remaining.go#L123-L159","documentation":"When /api/block/checkBlockRef is called with scope \"notebook\", the notebook field is mandatory. The kernel raises this error when notebook is absent, an empty string, or an explicit null while scope is \"notebook\". Scopes \"blocks\" and \"documents\" do not require notebook and never hit this branch.","triggerScenarios":"POST /api/block/checkBlockRef with {\"scope\":\"notebook\"} and no notebook key; {\"scope\":\"notebook\",\"notebook\":null}; {\"scope\":\"notebook\",\"notebook\":\"\"}.","commonSituations":"Refactoring code from scope \"blocks\" to \"notebook\" without adding the notebook parameter; a UI state where no notebook is selected yet the check still fires; null sent because an earlier layer conflated 'absent' with 'nullable'.","solutions":["Always include the notebook ID string when scope is \"notebook\"","Guard the call site: only send scope \"notebook\" after a notebook has been selected","Use scope \"blocks\" or \"documents\" instead if the operation is not notebook-wide"],"exampleFix":"// before\nawait fetchPost(\"/api/block/checkBlockRef\", { scope: \"notebook\", ids });\n// after\nawait fetchPost(\"/api/block/checkBlockRef\", { scope: \"notebook\", notebook, ids });","handlingStrategy":"validation","validationCode":"if (payload.scope === 'notebook' && !payload.notebook) throw new Error('notebook is required when scope is \"notebook\"');","typeGuard":"const notebookScopeReady = (p) => p.scope !== 'notebook' || (typeof p.notebook === 'string' && p.notebook.length > 0);","tryCatchPattern":"try { await fetchPost('/api/block/checkBlockRef', payload); } catch (e) { if (/Field \\[notebook\\] is required/.test(e?.message)) { /* prompt for notebook or switch scope */ } }","preventionTips":["Build payloads through a helper that enforces the scope/field pairing","Disable notebook-scope actions in the UI until a notebook is selected","Default to scope 'blocks' when no notebook context exists"],"tags":["api","validation","missing-field"],"backgroundTag":"missing-required-argument","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"}