{"record":{"id":"f88d086bfffd7a52","repo":"siyuan-note/siyuan","slug":"config-must-contain-exactly-one-setting-use-separate-calls","errorCode":null,"errorMessage":"config must contain exactly one setting; use separate calls for multiple settings","messagePattern":"config must contain exactly one setting; use separate calls for multiple settings","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_key_config.go","lineNumber":30,"sourceCode":"\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/88250/lute/ast\"\n\t\"github.com/siyuan-note/siyuan/kernel/av\"\n)\n\nvar AttributeViewKeyNumberFormats = []string{\"\", \"commas\", \"percent\", \"USD\", \"CNY\", \"EUR\", \"GBP\", \"JPY\", \"RUB\", \"INR\", \"KRW\", \"TRY\", \"CAD\", \"CHF\", \"THB\", \"AUD\", \"HKD\", \"TWD\", \"MOP\", \"SGD\", \"NZD\", \"ILS\", \"SKK\"}\n\nvar AttributeViewKeyRollupOperators = []string{\"\", \"Unique values\", \"Count all\", \"Count values\", \"Count unique values\", \"Count empty\", \"Count not empty\", \"Percent empty\", \"Percent not empty\", \"Percent unique values\", \"Sum\", \"Average\", \"Median\", \"Min\", \"Max\", \"Range\", \"Earliest\", \"Latest\", \"Checked\", \"Unchecked\", \"Percent checked\", \"Percent unchecked\"}\n\n// UpdateAttributeViewKeyConfig 每次修改一个字段配置，先校验参数，再复用界面操作维护关联数据。\nfunc UpdateAttributeViewKeyConfig(avID, keyID string, config map[string]any) (err error) {\n\tif 1 != len(config) {\n\t\treturn errors.New(\"config must contain exactly one setting; use separate calls for multiple settings\")\n\t}\n\tattrView, err := av.ParseAttributeView(avID)\n\tif nil != err {\n\t\treturn err\n\t}\n\tkey, err := attrView.GetKey(keyID)\n\tif nil != err {\n\t\treturn err\n\t}\n\top := &Operation{AvID: avID, ID: keyID, Typ: string(key.Type), Name: key.Name}\n\tfor setting, value := range config {\n\t\tswitch setting {\n\t\tcase \"name\", \"type\", \"icon\", \"desc\", \"numberFormat\", \"dateFormat\", \"template\", \"renderTemplate\":\n\t\t\ttext, ok := value.(string)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"%s must be a string\", setting)\n\t\t\t}\n\t\t\top.Data, op.Format = text, text","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_key_config.go#L12-L48","documentation":"UpdateAttributeViewKeyConfig changes exactly one field (column) configuration per call. The config map must therefore contain exactly one key; passing zero or multiple settings is rejected up front so each change maps to one UI/undo operation and related data is maintained deterministically.","triggerScenarios":"Calling UpdateAttributeViewKeyConfig (via the databaseKeyUpdate API path) with a config map like {\"name\":\"x\",\"desc\":\"y\"}, or with an empty map {}.","commonSituations":"Plugin authors batching multiple column-property updates in one request; generic config editors passing a whole settings object; accidental empty payload after filtering.","solutions":["Split the update into one API call per setting, e.g. one call for \"name\" and a separate one for \"desc\"","Validate len(config) == 1 in client code before issuing the request","If the empty-map case occurs, check upstream filtering logic that builds the config object"],"exampleFix":"// before\nupdateKeyConfig(avID, keyID, {\"name\": \"Title\", \"desc\": \"main\"})\n// after\nupdateKeyConfig(avID, keyID, {\"name\": \"Title\"})\nupdateKeyConfig(avID, keyID, {\"desc\": \"main\"})","handlingStrategy":"validation","validationCode":"function assertSingleSetting(config) { if (!config || typeof config !== 'object' || Object.keys(config).length !== 1) throw new Error('config must contain exactly one setting'); }","typeGuard":null,"tryCatchPattern":"try { await updateKeyConfig(avID, keyID, cfg); } catch (e) { if (/exactly one setting/.test(e.message)) { for (const k of Object.keys(cfg)) await updateKeyConfig(avID, keyID, { [k]: cfg[k] }); } else throw e; }","preventionTips":["Build one call per setting in your API wrapper","Assert Object.keys(config).length === 1 before sending","Never send an empty config object"],"tags":["api","validation","attribute-view","argument-count"],"backgroundTag":"invalid-argument-value","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"}