siyuan-note/siyuan · error

config must contain exactly one setting; use separate calls…

Error message

config must contain exactly one setting; use separate calls for multiple settings

What it means

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.

Solutions

  1. Split the update into one API call per setting, e.g. one call for "name" and a separate one for "desc"
  2. Validate len(config) == 1 in client code before issuing the request
  3. If the empty-map case occurs, check upstream filtering logic that builds the config object

Example fix

// before
updateKeyConfig(avID, keyID, {"name": "Title", "desc": "main"})
// after
updateKeyConfig(avID, keyID, {"name": "Title"})
updateKeyConfig(avID, keyID, {"desc": "main"})
Defensive patterns

Strategy: validation

Validate before calling

function assertSingleSetting(config) { if (!config || typeof config !== 'object' || Object.keys(config).length !== 1) throw new Error('config must contain exactly one setting'); }

Try / catch

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; }

Prevention

When it happens

Trigger: Calling UpdateAttributeViewKeyConfig (via the databaseKeyUpdate API path) with a config map like {"name":"x","desc":"y"}, or with an empty map {}.

Common situations: Plugin authors batching multiple column-property updates in one request; generic config editors passing a whole settings object; accidental empty payload after filtering.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/f88d086bfffd7a52. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_key_config.go:30

	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"slices"
	"strings"

	"github.com/88250/lute/ast"
	"github.com/siyuan-note/siyuan/kernel/av"
)

var AttributeViewKeyNumberFormats = []string{"", "commas", "percent", "USD", "CNY", "EUR", "GBP", "JPY", "RUB", "INR", "KRW", "TRY", "CAD", "CHF", "THB", "AUD", "HKD", "TWD", "MOP", "SGD", "NZD", "ILS", "SKK"}

var 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"}

// UpdateAttributeViewKeyConfig 每次修改一个字段配置,先校验参数,再复用界面操作维护关联数据。
func UpdateAttributeViewKeyConfig(avID, keyID string, config map[string]any) (err error) {
	if 1 != len(config) {
		return errors.New("config must contain exactly one setting; use separate calls for multiple settings")
	}
	attrView, err := av.ParseAttributeView(avID)
	if nil != err {
		return err
	}
	key, err := attrView.GetKey(keyID)
	if nil != err {
		return err
	}
	op := &Operation{AvID: avID, ID: keyID, Typ: string(key.Type), Name: key.Name}
	for setting, value := range config {
		switch setting {
		case "name", "type", "icon", "desc", "numberFormat", "dateFormat", "template", "renderTemplate":
			text, ok := value.(string)
			if !ok {
				return fmt.Errorf("%s must be a string", setting)
			}
			op.Data, op.Format = text, text

View on GitHub (pinned to 9f775e8a12)