siyuan-note/siyuan · error

struct field Sync .

Error message

struct field Sync%s.

What it means

This rewritten error is produced by syncConfigError when decoding POST /api/sync/setSyncProviderS3, /setSyncProviderWebDAV, or /setSyncProviderLocal. The raw reflection error from unmarshalling the nested provider config mentions internal names like `struct field SyncS3.Endpoint`; the rewriter normalizes them to user-facing config names (`struct field S3.Endpoint`). The underlying problem is one field inside the provider object has the wrong JSON type.

Solutions

  1. Check the exact field named in the message and send the correct JSON type (numbers as numbers, booleans as booleans)
  2. Compare the payload against the documented SyncS3/SyncWebDAV/SyncLocal object shape
  3. Read the request.ConfigError() on the returned object — for these endpoints the error is attached to the response, not a hard failure

Example fix

// before
{"s3": {"endpoint": "https://s3.example.com", "timeout": "30"}}
// after
{"s3": {"endpoint": "https://s3.example.com", "timeout": 30}}
Defensive patterns

Strategy: validation

Validate before calling

const num = (v) => typeof v === 'number' && !isNaN(v); const validS3 = s => s && typeof s === 'object' && (s.timeout === undefined || num(s.timeout)) && (s.concurrentReqs === undefined || num(s.concurrentReqs));

Type guard

const isProviderConfig = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);

Try / catch

const req = await fetchPost('/api/sync/setSyncProviderS3', {s3: cfg}); if (req.code !== 0 || (req.data && req.data.configError)) console.error(req.msg);

Prevention

When it happens

Trigger: POSTing /api/sync/setSyncProviderS3 with e.g. {"s3":{"timeout":"30"}} (string where number expected) or {"s3":{"skipTlsVerify":"yes"}}; same for webdav/local nested objects.

Common situations: Config copied from a YAML/INI export where numbers became strings; a settings UI storing values as strings and forwarding them unconverted; API version drift between client payload shape and kernel struct.

Related errors


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

Appendix: source

Thrown at kernel/apicontract/sync.go:139

	Updated   string `json:"updated"`
	CloudName string `json:"cloudName"`
	SaveDir   string `json:"saveDir"`
}
type CloudSyncDirsData struct {
	SyncDirs       []*CloudSyncDir `json:"syncDirs"`
	HSize          string          `json:"hSize"`
	CheckedSyncDir string          `json:"checkedSyncDir"`
}
type SyncAssetDownloadModeData struct {
	AssetDownloadMode int `json:"assetDownloadMode"`
}

func syncConfigError(err error, name string) error {
	if err == nil {
		return nil
	}
	detail := strings.ReplaceAll(err.Error(), "apicontract.Sync"+name, "conf."+name)
	return errors.New(strings.ReplaceAll(detail, "struct field Sync"+name+".", "struct field "+name+"."))
}

// syncRequestFields 保留整份请求的 JSON 数字解析、重复字段和首个对象读取语义。
func syncRequestFields(reader io.Reader, path string) (fields map[string]json.RawMessage, err error) {
	var raw json.RawMessage
	err = json.NewDecoder(reader).Decode(&raw)
	if err == nil {
		fields, err = legacyJSONValue[map[string]json.RawMessage](raw)
	}
	if err != nil {
		if errors.Is(err, io.EOF) {
			err = errors.New("the request body is empty or truncated (EOF)")
		}
		detail := strings.ReplaceAll(err.Error(), "map[string]json.RawMessage", "map[string]interface {}")
		err = fmt.Errorf("Parses request [%s] failed: %s", path, detail)
	}
	return
}

View on GitHub (pinned to 9f775e8a12)